From 5c2bc3dbf9a5abcd6561e71ca4acfd3fe8f57f80 Mon Sep 17 00:00:00 2001 From: Mark Loveday Date: Thu, 30 Apr 2026 16:35:41 +0100 Subject: [PATCH 1/3] 1st commit from https://sourceforge.net/projects/spell-sat/files/ --- .bsp/sbt.json | 1 + .cproject | 600 ++++++ .dockerignore | 2 + .gitignore | 27 + .project | 62 + .pydevproject | 11 + .settings/language.settings.xml | 27 + .settings/org.eclipse.cdt.codan.core.prefs | 71 + INSTALL | 372 +--- LICENSE | 674 ++++++ Makefile.am | 58 +- README | 40 +- RELEASENOTES | 332 +++ SPELL Debug.launch | 46 + azure_pipelines/ci_logrotate.yaml | 139 ++ azure_pipelines/ci_spell_driver_scorpio.yaml | 139 ++ azure_pipelines/ci_spell_registry.yaml | 158 ++ azure_pipelines/ci_spell_server.yaml | 147 ++ azure_pipelines/ci_spell_ssh.yaml | 139 ++ azure_pipelines/variables/baseline.yaml | 8 + azure_pipelines/variables/logrotate.yaml | 5 + .../variables/spell_driver_scorpio.yaml | 5 + azure_pipelines/variables/spell_registry.yaml | 8 + azure_pipelines/variables/spell_server.yaml | 5 + azure_pipelines/variables/spell_ssh.yaml | 5 + build_driver.sh | 59 + build_spell.sh | 53 + build_spell_ubuntu.sh | 49 + common.mk | 17 +- config/Makefile.am | 76 +- config/contexts/context_example.xml | 83 + config/server/server_example.xml | 69 + config/spell/language.xml | 76 + configure.ac | 430 ++-- data/InputFiles/GroundDB/GDB.DB | 24 + data/InputFiles/ManeuvreMessage/TEST.IMP | 24 + data/InputFiles/SpacecraftDB/SCDB.DB | 2 + data/InputFiles/User/TEST.IMP | 24 + data/Makefile.am | 70 +- data/OutputFiles/AsRun/.gitignore | 0 data/OutputFiles/Warmstart/.gitignore | 0 data/Procedures/.gitignore | 0 data/Procedures/TestProcedure.py | 19 + data/Runtime/.gitignore | 0 data/UserLib/.gitignore | 0 deploy/bftd/ddcntl.yaml | 20 + deploy/bftd/externalsecret.yaml | 34 + deploy/bftd/flux-policies/imagepolicy.yaml | 27 + .../bftd/flux-policies/imagerepository.yaml | 23 + .../flux-policies/imageupdateautomation.yaml | 43 + deploy/bftd/flux-policies/nexus-ca.yaml | 6 + deploy/bftd/kube-mp01-values.yaml | 33 + deploy/bftd/kustomization.yaml | 42 + deploy/bftd/kustomizeconfig.yaml | 6 + deploy/bftd/mp01-kube-server.yaml | 22 + deploy/bftd/mp09-s3-server.yaml | 22 + deploy/bftd/s3-mp09-values.yaml | 34 + deploy/bftd/s3cntl.yaml | 20 + deploy/bftd/s5cntl.yaml | 20 + deploy/bftd/secretstore.yaml | 26 + deploy/bfts/externalsecret.yaml | 66 + deploy/bfts/kustomization.yaml | 30 + deploy/bfts/kustomizeconfig.yaml | 6 + .../missions/zs24a/kube-ses24-server.yaml | 18 + .../missions/zs24a/kube-ses24-values.yaml | 75 + deploy/bfts/missions/zs24a/kustomization.yaml | 18 + .../bfts/missions/zs24a/kustomizeconfig.yaml | 6 + deploy/bfts/missions/zs24a/ses24cntl.yaml | 11 + .../missions/zs24b/kube-ses24-server.yaml | 18 + .../missions/zs24b/kube-ses24-values.yaml | 75 + deploy/bfts/missions/zs24b/kustomization.yaml | 18 + .../bfts/missions/zs24b/kustomizeconfig.yaml | 6 + deploy/bfts/missions/zs24b/ses24cntl.yaml | 11 + .../missions/zs24c/kube-ses24-server.yaml | 18 + .../missions/zs24c/kube-ses24-values.yaml | 75 + deploy/bfts/missions/zs24c/kustomization.yaml | 18 + .../bfts/missions/zs24c/kustomizeconfig.yaml | 6 + deploy/bfts/missions/zs24c/ses24cntl.yaml | 11 + .../missions/zs24d/kube-ses24-server.yaml | 18 + .../missions/zs24d/kube-ses24-values.yaml | 75 + deploy/bfts/missions/zs24d/kustomization.yaml | 18 + .../bfts/missions/zs24d/kustomizeconfig.yaml | 6 + deploy/bfts/missions/zs24d/ses24cntl.yaml | 11 + .../missions/zs24e/kube-ses24-server.yaml | 18 + .../missions/zs24e/kube-ses24-values.yaml | 75 + deploy/bfts/missions/zs24e/kustomization.yaml | 18 + .../bfts/missions/zs24e/kustomizeconfig.yaml | 6 + deploy/bfts/missions/zs24e/ses24cntl.yaml | 11 + .../missions/zs24f/kube-ses24-server.yaml | 18 + .../missions/zs24f/kube-ses24-values.yaml | 75 + deploy/bfts/missions/zs24f/kustomization.yaml | 18 + .../bfts/missions/zs24f/kustomizeconfig.yaml | 6 + deploy/bfts/missions/zs24f/ses24cntl.yaml | 11 + deploy/bfts/secretstore.yaml | 26 + deploy/pfte/externalsecret.yaml | 34 + deploy/pfte/flux-policies/imagepolicy.yaml | 27 + .../pfte/flux-policies/imagerepository.yaml | 23 + .../flux-policies/imageupdateautomation.yaml | 43 + deploy/pfte/flux-policies/nexus-ca.yaml | 6 + deploy/pfte/kustomization.yaml | 26 + .../missions/ses24/kube-ses24-server.yaml | 51 + .../missions/ses24/kube-ses24-values.yaml | 74 + deploy/pfte/missions/ses24/kustomization.yaml | 18 + .../pfte/missions/ses24/kustomizeconfig.yaml | 6 + deploy/pfte/missions/ses24/ses24cntl.yaml | 12 + deploy/pfte/secretstore.yaml | 26 + deploy/pfts/externalsecret.yaml | 34 + deploy/pfts/flux-policies/imagepolicy.yaml | 27 + .../pfts/flux-policies/imagerepository.yaml | 23 + .../flux-policies/imageupdateautomation.yaml | 43 + deploy/pfts/flux-policies/nexus-ca.yaml | 6 + deploy/pfts/kustomization.yaml | 26 + .../missions/ses24/kube-ses24-server.yaml | 51 + .../missions/ses24/kube-ses24-values.yaml | 65 + deploy/pfts/missions/ses24/kustomization.yaml | 16 + .../pfts/missions/ses24/kustomizeconfig.yaml | 6 + deploy/pfts/missions/ses24/ses24cntl.yaml | 12 + deploy/pfts/secretstore.yaml | 26 + deploy/spell-registry/deployment.yaml | 60 + deploy/spell-registry/ingress.yaml | 26 + deploy/spell-registry/kustomization.yaml | 10 + deploy/spell-registry/role.yaml | 11 + deploy/spell-registry/rolebinding.yaml | 12 + deploy/spell-registry/sa.yaml | 13 + deploy/spell-registry/service.yaml | 17 + deploy/spell-server/.helmignore | 23 + deploy/spell-server/Chart.yaml | 23 + deploy/spell-server/configs/context.xml | 112 + deploy/spell-server/configs/driver.xml | 12 + deploy/spell-server/configs/language.xml | 236 +++ deploy/spell-server/configs/log.properties | 4 + .../spell-server/configs/scheduler_wasp.xml | 13 + deploy/spell-server/configs/server.xml | 84 + deploy/spell-server/templates/NOTES.txt | 5 + deploy/spell-server/templates/_helpers.tpl | 60 + deploy/spell-server/templates/cm.yaml | 28 + deploy/spell-server/templates/deployment.yaml | 410 ++++ deploy/spell-server/templates/pvc.yaml | 13 + deploy/spell-server/templates/service.yaml | 24 + .../templates/tests/test-connection.yaml | 15 + deploy/spell-server/values.yaml | 99 + docker/.dockerignore | 0 docker/Makefile | 14 + docker/drivers/Makefile | 13 + docker/drivers/example/Dockerfile | 26 + docker/drivers/example/Makefile | 20 + docker/drivers/fuzzer/Dockerfile | 25 + docker/drivers/fuzzer/Makefile | 20 + docker/drivers/hifly2/Dockerfile | 39 + docker/drivers/hifly2/Makefile | 21 + docker/drivers/scorpio/Dockerfile | 30 + docker/drivers/scorpio/Makefile | 20 + docker/logrotate/Dockerfile | 37 + docker/logrotate/Makefile | 21 + docker/logrotate/docker-entrypoint.sh | 41 + docker/logrotate/go-cron.tar.gz | Bin 0 -> 2099200 bytes docker/logrotate/logrotate.conf | 12 + docker/spell-registry/.gitignore | 32 + docker/spell-registry/.scalafmt.conf | 2 + docker/spell-registry/Makefile | 23 + docker/spell-registry/README.md | 85 + docker/spell-registry/build.sbt | 33 + .../spell-registry/project/build.properties | 1 + docker/spell-registry/project/plugins.sbt | 2 + .../src/main/resources/application.conf | 2 + .../src/main/scala/KubeAPI.scala | 78 + .../spell-registry/src/main/scala/Main.scala | 34 + .../src/main/scala/SpellServerService.scala | 121 ++ .../src/test/scala/MySuite.scala | 9 + docker/spell-server/Dockerfile | 43 + docker/spell-server/Makefile | 21 + docker/ssh-server/Dockerfile | 22 + docker/ssh-server/Dockerfile.centos | 25 + docker/ssh-server/Makefile | 20 + .../etc/yum.repos.d/bzenginst1.repo | 7 + docker/ssh-server/init.sh | 237 +++ drivers/Makefile.am | 42 +- drivers/example/Makefile.am | 77 +- drivers/example/config/driver_example.xml | 2 +- drivers/example/python/__init__.py | 25 + drivers/example/python/config.py | 100 + drivers/example/python/constants.py | 30 + drivers/example/python/ev.py | 104 + drivers/example/python/gcstime.py | 98 + drivers/example/python/modifiers.py | 29 + drivers/example/python/resources.py | 183 ++ drivers/example/python/task.py | 173 ++ drivers/example/python/tc.py | 138 ++ drivers/example/python/tm.py | 269 +++ drivers/example/python/usr.py | 130 ++ drivers/service/__init__.py | 0 drivers/service/cif_client.py | 47 + drivers/service/driverserver.py | 130 ++ drivers/service/ev_service.py | 37 + drivers/service/exec_client.py | 130 ++ drivers/service/info_service.py | 35 + drivers/service/mem_service.py | 103 + drivers/service/pcs_service.py | 166 ++ drivers/service/rng_service.py | 128 ++ drivers/service/rsc_service.py | 76 + drivers/service/task_service.py | 74 + drivers/service/tc_service.py | 102 + drivers/service/time_service.py | 37 + drivers/service/tm_service.py | 142 ++ drivers/service/user_service.py | 57 + include/SPELL_CFG/SPELLbrowsableLibMode.H | 2 +- include/SPELL_CFG/SPELLconfiguration.H | 2 +- include/SPELL_CFG/SPELLcontextConfig.H | 11 +- include/SPELL_CFG/SPELLdriverConfig.H | 2 +- include/SPELL_CFG/SPELLexecutorDefaults.H | 79 +- include/SPELL_CFG/SPELLxmlConfigReader.H | 2 +- .../SPELL_CFG/SPELLxmlConfigReaderFactory.H | 2 +- include/SPELL_CFG/SPELLxmlConfigReaderXC.H | 2 +- include/SPELL_CFG/SPELLxmlNode.H | 2 +- include/SPELL_CIF/SPELLasRun.H | 2 +- include/SPELL_CIF/SPELLcif.H | 31 +- include/SPELL_CIF/SPELLcifHelper.H | 2 +- include/SPELL_CIF/SPELLcifStartupInfo.H | 2 +- include/SPELL_CIF/SPELLnotifications.H | 2 +- include/SPELL_CIF/SPELLpromptDefinition.H | 2 +- include/SPELL_CIF/SPELLstatusInfo.H | 2 +- include/SPELL_CIFC/SPELLautomaticCif.H | 12 +- include/SPELL_CIFC/SPELLncursesShell.H | 5 +- include/SPELL_CIFC/SPELLstandaloneCif.H | 2 +- include/SPELL_CIFS/SPELLcifPromptHelper.H | 2 +- include/SPELL_CIFS/SPELLdisplayBuffer.H | 2 +- include/SPELL_CIFS/SPELLrequestProcessor.H | 29 +- include/SPELL_CIFS/SPELLresourceBuffer.H | 146 ++ include/SPELL_CIFS/SPELLserverCif.H | 25 +- include/SPELL_CIFS/SPELLvariableBuffer.H | 2 +- include/SPELL_CTX/SPELLclient.H | 2 +- include/SPELL_CTX/SPELLclientIPC.H | 2 +- include/SPELL_CTX/SPELLclientInterestList.H | 2 +- include/SPELL_CTX/SPELLclientListener.H | 2 +- include/SPELL_CTX/SPELLclientManager.H | 2 +- include/SPELL_CTX/SPELLclientMode.H | 2 +- include/SPELL_CTX/SPELLclientNotifier.H | 2 +- include/SPELL_CTX/SPELLclientOperation.H | 2 +- include/SPELL_CTX/SPELLcontext.H | 13 +- include/SPELL_CTX/SPELLcontextConfiguration.H | 2 +- include/SPELL_CTX/SPELLdataHelper.H | 2 +- include/SPELL_CTX/SPELLdataTable.H | 2 +- include/SPELL_CTX/SPELLexecutor.H | 93 +- include/SPELL_CTX/SPELLexecutorIPC.H | 2 +- include/SPELL_CTX/SPELLexecutorListener.H | 2 +- include/SPELL_CTX/SPELLexecutorManager.H | 4 +- include/SPELL_CTX/SPELLexecutorModel.H | 5 +- include/SPELL_CTX/SPELLexecutorOperation.H | 2 +- .../SPELL_CTX/SPELLexecutorStartupParams.H | 4 +- include/SPELL_CTX/SPELLlistenerIPC.H | 2 +- include/SPELL_CTX/SPELLopenMode.H | 2 +- include/SPELL_CTX/SPELLpersistencyTable.H | 2 +- include/SPELL_CTX/SPELLserverFile.H | 2 +- include/SPELL_CTX/SPELLservice.H | 89 + include/SPELL_CTX/SPELLserviceListener.H | 109 + include/SPELL_CTX/SPELLserviceManager.H | 250 +++ include/SPELL_CTX/SPELLserviceStartupParams.H | 143 ++ include/SPELL_CTX/SPELLsharedData.H | 4 +- include/SPELL_CTX/SPELLsharedDataOperation.H | 2 +- include/SPELL_DTA/SPELLdtaContainer.H | 2 +- include/SPELL_DTA/SPELLdtaContainerObject.H | 2 +- include/SPELL_DTA/SPELLdtaVariable.H | 2 +- include/SPELL_DTA/SPELLdtaVariableObject.H | 2 +- include/SPELL_EXC/SPELLastAnalyzer.H | 2 +- include/SPELL_EXC/SPELLbreakpoint.H | 2 +- include/SPELL_EXC/SPELLbreakpointType.H | 2 +- include/SPELL_EXC/SPELLbytecode.H | 7 +- include/SPELL_EXC/SPELLcallstack.H | 6 +- include/SPELL_EXC/SPELLcallstackIF.H | 6 +- include/SPELL_EXC/SPELLchildManager.H | 2 +- include/SPELL_EXC/SPELLcodeTreeLine.H | 2 +- include/SPELL_EXC/SPELLcodeTreeLineIF.H | 2 +- include/SPELL_EXC/SPELLcodeTreeNode.H | 2 +- include/SPELL_EXC/SPELLcodeTreeNodeIF.H | 2 +- include/SPELL_EXC/SPELLcommand.H | 4 +- include/SPELL_EXC/SPELLcommandMailbox.H | 2 +- include/SPELL_EXC/SPELLcontroller.H | 8 +- include/SPELL_EXC/SPELLcontrollerIF.H | 9 +- include/SPELL_EXC/SPELLcontrollerListener.H | 2 +- include/SPELL_EXC/SPELLdispatchListener.H | 2 +- include/SPELL_EXC/SPELLexecutionMode.H | 2 +- include/SPELL_EXC/SPELLexecutionModel.H | 2 +- include/SPELL_EXC/SPELLexecutionTrace.H | 2 +- include/SPELL_EXC/SPELLexecutor.H | 9 +- .../SPELL_EXC/SPELLexecutorConfiguration.H | 56 +- include/SPELL_EXC/SPELLexecutorIF.H | 16 +- include/SPELL_EXC/SPELLexecutorImpl.H | 18 +- include/SPELL_EXC/SPELLexecutorStatus.H | 2 +- include/SPELL_EXC/SPELLexecutorUtils.H | 2 +- include/SPELL_EXC/SPELLframeManager.H | 22 +- include/SPELL_EXC/SPELLgoto.H | 2 +- include/SPELL_EXC/SPELLimportChecker.H | 2 +- include/SPELL_EXC/SPELLinterpreter.H | 2 +- include/SPELL_EXC/SPELLlnotab.H | 2 +- include/SPELL_EXC/SPELLresourceInfo.H | 115 ++ include/SPELL_EXC/SPELLresourceManager.H | 147 ++ include/SPELL_EXC/SPELLresourceMonitor.H | 199 ++ include/SPELL_EXC/SPELLscheduleCondition.H | 3 +- include/SPELL_EXC/SPELLscheduler.H | 12 +- include/SPELL_EXC/SPELLschedulerIF.H | 7 +- include/SPELL_EXC/SPELLserviceImpl.H | 599 ++++++ include/SPELL_EXC/SPELLserviceInterpreter.H | 249 +++ include/SPELL_EXC/SPELLstepOverMode.H | 2 +- include/SPELL_EXC/SPELLuserAction.H | 2 +- include/SPELL_EXC/SPELLvarInfo.H | 2 +- include/SPELL_EXC/SPELLvariableManager.H | 2 +- include/SPELL_EXC/SPELLvariableMonitor.H | 2 +- include/SPELL_IPC/SPELLipc.H | 2 +- include/SPELL_IPC/SPELLipcChannel.H | 3 +- include/SPELL_IPC/SPELLipcClientInterface.H | 4 +- include/SPELL_IPC/SPELLipcDataChunk.H | 2 +- include/SPELL_IPC/SPELLipcError.H | 2 +- include/SPELL_IPC/SPELLipcHelper.H | 2 +- include/SPELL_IPC/SPELLipcIncoming.H | 13 +- include/SPELL_IPC/SPELLipcInput.H | 8 +- include/SPELL_IPC/SPELLipcInterface.H | 8 +- include/SPELL_IPC/SPELLipcInterfaceListener.H | 8 +- include/SPELL_IPC/SPELLipcMessage.H | 8 +- include/SPELL_IPC/SPELLipcMessageMailbox.H | 7 +- include/SPELL_IPC/SPELLipcOutput.H | 2 +- include/SPELL_IPC/SPELLipcServerInterface.H | 9 +- .../SPELL_IPC/SPELLipcServerInterfaceSingle.H | 8 +- include/SPELL_IPC/SPELLipcTrash.H | 4 +- include/SPELL_IPC/SPELLipc_Context.H | 16 +- include/SPELL_IPC/SPELLipc_Executor.H | 50 +- include/SPELL_IPC/SPELLipc_Listener.H | 2 +- include/SPELL_IPC/SPELLsocket.H | 12 +- include/SPELL_IPC/SPELLtimeoutValues.H | 2 +- include/SPELL_LST/SPELLlistener.H | 2 +- include/SPELL_LST/SPELLlistenerComm.H | 2 +- include/SPELL_LST/SPELLlistenerContext.H | 10 +- include/SPELL_LST/SPELLlistenerGui.H | 2 +- include/SPELL_PRC/SPELLprocess.H | 2 +- include/SPELL_PRC/SPELLprocessInfo.H | 2 +- include/SPELL_PRC/SPELLprocessManager.H | 20 +- include/SPELL_PRC/SPELLprocessStatus.H | 2 +- include/SPELL_PRD/SPELLlibrary.H | 4 +- include/SPELL_PRD/SPELLprocedure.H | 18 +- include/SPELL_PRD/SPELLprocedureManager.H | 358 ++-- include/SPELL_PRD/SPELLprocedureSourceCode.H | 20 +- include/SPELL_PRD/SPELLpyService.H | 160 ++ include/SPELL_PRD/SPELLservicePython.H | 59 + .../SPELL_PRD/SPELLserviceWatchdogPython.H | 57 + include/SPELL_SDB/SPELLdatabase.H | 2 +- include/SPELL_SDB/SPELLdatabaseFactory.H | 2 +- include/SPELL_SDB/SPELLdatabaseFile.H | 2 +- include/SPELL_SDB/SPELLdatabaseFileSPB.H | 2 +- include/SPELL_SDB/SPELLpyDatabaseObject.H | 7 +- include/SPELL_SYN/SPELLcondition.H | 2 +- include/SPELL_SYN/SPELLconditionFactory.H | 2 +- include/SPELL_SYN/SPELLconditionIF.H | 2 +- include/SPELL_SYN/SPELLconditionPOSIX.H | 2 +- include/SPELL_SYN/SPELLevent.H | 2 +- include/SPELL_SYN/SPELLmonitor.H | 2 +- include/SPELL_SYN/SPELLmutex.H | 2 +- include/SPELL_SYN/SPELLmutexFactory.H | 2 +- include/SPELL_SYN/SPELLmutexIF.H | 2 +- include/SPELL_SYN/SPELLmutexPOSIX.H | 2 +- include/SPELL_SYN/SPELLqueue.H | 2 +- include/SPELL_SYN/SPELLsyncError.H | 2 +- include/SPELL_SYN/SPELLthread.H | 2 +- include/SPELL_SYN/SPELLthreadPool.H | 2 +- include/SPELL_SYN/SPELLthreadWork.H | 2 +- include/SPELL_SYN/SPELLthreadWorker.H | 2 +- include/SPELL_SYN/SPELLtimer.H | 2 +- include/SPELL_UTIL/SPELLbase.H | 48 +- include/SPELL_UTIL/SPELLcompression.H | 2 +- include/SPELL_UTIL/SPELLerror.H | 2 +- include/SPELL_UTIL/SPELLlog.H | 12 +- include/SPELL_UTIL/SPELLpyArgs.H | 18 +- include/SPELL_UTIL/SPELLpythonHelper.H | 20 +- include/SPELL_UTIL/SPELLpythonMonitors.H | 65 +- include/SPELL_UTIL/SPELLtime.H | 45 +- include/SPELL_UTIL/SPELLutils.H | 19 +- include/SPELL_WRP/SPELLconfigDict.H | 2 +- include/SPELL_WRP/SPELLconstants.H | 42 +- include/SPELL_WRP/SPELLdatabaseManager.H | 2 +- include/SPELL_WRP/SPELLdriverManager.H | 2 +- include/SPELL_WRP/SPELLpyHandle.H | 2 +- include/SPELL_WRP/SPELLpyValue.H | 4 +- include/SPELL_WRP/SPELLregistry.H | 2 +- include/SPELL_WS/SPELLwarmstart.H | 2 +- include/SPELL_WS/SPELLwsClassDataHandler.H | 2 +- .../SPELL_WS/SPELLwsCustomTypeDataHandler.H | 2 +- include/SPELL_WS/SPELLwsDataCodes.H | 2 +- include/SPELL_WS/SPELLwsDataHandler.H | 2 +- include/SPELL_WS/SPELLwsDataHandlerFactory.H | 2 +- include/SPELL_WS/SPELLwsDbDataHandler.H | 2 +- include/SPELL_WS/SPELLwsDictDataHandler.H | 2 +- include/SPELL_WS/SPELLwsDynamicData.H | 2 +- include/SPELL_WS/SPELLwsFrame.H | 2 +- include/SPELL_WS/SPELLwsInstanceDataHandler.H | 2 +- include/SPELL_WS/SPELLwsListDataHandler.H | 2 +- include/SPELL_WS/SPELLwsNoneDataHandler.H | 2 +- include/SPELL_WS/SPELLwsObjectDataHandler.H | 2 +- include/SPELL_WS/SPELLwsStartupInfo.H | 2 +- include/SPELL_WS/SPELLwsStaticData.H | 2 +- include/SPELL_WS/SPELLwsStorage.H | 4 +- include/SPELL_WS/SPELLwsTimeDataHandler.H | 2 +- include/SPELL_WS/SPELLwsTmItemDataHandler.H | 2 +- include/SPELL_WS/SPELLwsTupleDataHandler.H | 2 +- include/SPELL_WS/SPELLwsWarmStartImpl.H | 2 +- include/SPELL_WS/SPELLwsWorkingMode.H | 2 +- lib/Makefile.am | 46 +- lib/SPELL_CFG/Makefile.am | 80 +- lib/SPELL_CFG/src/SPELLbrowsableLibMode.C | 6 +- lib/SPELL_CFG/src/SPELLconfiguration.C | 2 +- lib/SPELL_CFG/src/SPELLcontextConfig.C | 16 +- lib/SPELL_CFG/src/SPELLdriverConfig.C | 2 +- lib/SPELL_CFG/src/SPELLexecutorDefaults.C | 9 +- .../src/SPELLxmlConfigReaderFactory.C | 2 +- lib/SPELL_CFG/src/SPELLxmlConfigReaderXC.C | 2 +- lib/SPELL_CIF/Makefile.am | 77 +- lib/SPELL_CIF/src/SPELLasRun.C | 2 +- lib/SPELL_CIF/src/SPELLcif.C | 93 +- lib/SPELL_CIF/src/SPELLcifHelper.C | 24 +- lib/SPELL_CIF/src/SPELLcifPython.C | 46 +- lib/SPELL_CIF/src/SPELLnotifications.C | 2 +- lib/SPELL_CIFC/Makefile.am | 72 +- lib/SPELL_CIFC/src/SPELLautomaticCif.C | 23 +- lib/SPELL_CIFC/src/SPELLncursesShell.C | 8 +- lib/SPELL_CIFC/src/SPELLstandaloneCif.C | 2 +- lib/SPELL_CIFS/Makefile.am | 79 +- lib/SPELL_CIFS/src/SPELLcifPromptHelper.C | 4 +- lib/SPELL_CIFS/src/SPELLdisplayBuffer.C | 2 +- lib/SPELL_CIFS/src/SPELLrequestProcessor.C | 86 +- lib/SPELL_CIFS/src/SPELLresourceBuffer.C | 197 ++ lib/SPELL_CIFS/src/SPELLserverCif.C | 402 ++-- lib/SPELL_CIFS/src/SPELLvariableBuffer.C | 2 +- lib/SPELL_CTX/Makefile.am | 117 +- lib/SPELL_CTX/src/SPELLclient.C | 10 +- lib/SPELL_CTX/src/SPELLclientIPC.C | 18 +- lib/SPELL_CTX/src/SPELLclientInterestList.C | 2 +- lib/SPELL_CTX/src/SPELLclientManager.C | 75 +- lib/SPELL_CTX/src/SPELLclientNotifier.C | 2 +- lib/SPELL_CTX/src/SPELLcontext.C | 399 +++- lib/SPELL_CTX/src/SPELLdataHelper.C | 2 +- lib/SPELL_CTX/src/SPELLdataTable.C | 2 +- lib/SPELL_CTX/src/SPELLexecutor.C | 116 +- lib/SPELL_CTX/src/SPELLexecutorIPC.C | 46 +- lib/SPELL_CTX/src/SPELLexecutorManager.C | 89 +- lib/SPELL_CTX/src/SPELLexecutorModel.C | 2 +- lib/SPELL_CTX/src/SPELLexecutorOperation.C | 2 +- lib/SPELL_CTX/src/SPELLlistenerIPC.C | 3 +- lib/SPELL_CTX/src/SPELLpersistencyTable.C | 6 +- lib/SPELL_CTX/src/SPELLservice.C | 207 ++ lib/SPELL_CTX/src/SPELLserviceManager.C | 676 ++++++ lib/SPELL_CTX/src/SPELLsharedData.C | 2 +- lib/SPELL_DTA/Makefile.am | 75 +- lib/SPELL_DTA/src/SPELLdtaContainer.C | 6 +- .../src/SPELLdtaContainerObjectMethods.C | 66 +- lib/SPELL_DTA/src/SPELLdtaModule.C | 22 +- lib/SPELL_DTA/src/SPELLdtaVariable.C | 8 +- .../src/SPELLdtaVariableObjectMethods.C | 4 +- lib/SPELL_EXC/Makefile.am | 154 +- lib/SPELL_EXC/src/SPELLastAnalyzer.C | 4 +- lib/SPELL_EXC/src/SPELLbreakpoint.C | 2 +- lib/SPELL_EXC/src/SPELLbreakpointType.C | 2 +- lib/SPELL_EXC/src/SPELLbytecode.C | 54 +- lib/SPELL_EXC/src/SPELLcallstack.C | 9 +- lib/SPELL_EXC/src/SPELLchildManager.C | 2 +- lib/SPELL_EXC/src/SPELLcodeTreeLine.C | 2 +- lib/SPELL_EXC/src/SPELLcodeTreeNode.C | 2 +- lib/SPELL_EXC/src/SPELLcommandMailbox.C | 2 +- lib/SPELL_EXC/src/SPELLcontroller.C | 48 +- lib/SPELL_EXC/src/SPELLexecutionModel.C | 2 +- lib/SPELL_EXC/src/SPELLexecutionTrace.C | 2 +- lib/SPELL_EXC/src/SPELLexecutor.C | 2 +- .../src/SPELLexecutorConfiguration.C | 11 +- lib/SPELL_EXC/src/SPELLexecutorImpl.C | 155 +- lib/SPELL_EXC/src/SPELLexecutorPython.C | 58 +- lib/SPELL_EXC/src/SPELLexecutorUtils.C | 2 +- lib/SPELL_EXC/src/SPELLframeManager.C | 69 +- lib/SPELL_EXC/src/SPELLgoto.C | 65 +- lib/SPELL_EXC/src/SPELLgotoPython.C | 7 +- lib/SPELL_EXC/src/SPELLimportChecker.C | 2 +- lib/SPELL_EXC/src/SPELLinterpreter.C | 9 +- lib/SPELL_EXC/src/SPELLlnotab.C | 2 +- lib/SPELL_EXC/src/SPELLresourceManager.C | 190 ++ lib/SPELL_EXC/src/SPELLresourceMonitor.C | 474 +++++ lib/SPELL_EXC/src/SPELLscheduler.C | 160 +- lib/SPELL_EXC/src/SPELLserviceImpl.C | 1739 ++++++++++++++++ lib/SPELL_EXC/src/SPELLserviceInterpreter.C | 532 +++++ lib/SPELL_EXC/src/SPELLstepPython.C | 7 +- lib/SPELL_EXC/src/SPELLvariableManager.C | 2 +- lib/SPELL_EXC/src/SPELLvariableMonitor.C | 24 +- lib/SPELL_IPC/Makefile.am | 102 +- lib/SPELL_IPC/src/SPELLipcChannel.C | 47 +- lib/SPELL_IPC/src/SPELLipcClientInterface.C | 30 +- lib/SPELL_IPC/src/SPELLipcDataChunk.C | 2 +- lib/SPELL_IPC/src/SPELLipcHelper.C | 2 +- lib/SPELL_IPC/src/SPELLipcIncoming.C | 39 +- lib/SPELL_IPC/src/SPELLipcInput.C | 29 +- lib/SPELL_IPC/src/SPELLipcMessage.C | 11 +- lib/SPELL_IPC/src/SPELLipcMessageMailbox.C | 18 +- lib/SPELL_IPC/src/SPELLipcOutput.C | 69 +- lib/SPELL_IPC/src/SPELLipcServerInterface.C | 125 +- .../src/SPELLipcServerInterfaceSingle.C | 92 +- lib/SPELL_IPC/src/SPELLipcTrash.C | 28 +- lib/SPELL_IPC/src/SPELLsocket.C | 215 +- lib/SPELL_LST/Makefile.am | 71 +- lib/SPELL_LST/src/SPELLlistener.C | 13 +- lib/SPELL_LST/src/SPELLlistenerContext.C | 56 +- lib/SPELL_LST/src/SPELLlistenerGui.C | 5 +- lib/SPELL_PRC/Makefile.am | 67 +- lib/SPELL_PRC/src/SPELLprocess.C | 10 +- lib/SPELL_PRC/src/SPELLprocessManager.C | 68 +- lib/SPELL_PRD/Makefile.am | 77 +- lib/SPELL_PRD/src/SPELLlibrary.C | 30 +- lib/SPELL_PRD/src/SPELLprocedure.C | 129 +- lib/SPELL_PRD/src/SPELLprocedureManager.C | 174 +- lib/SPELL_PRD/src/SPELLpyService.C | 115 ++ .../src/SPELLserviceRegisterPython.C | 181 ++ .../src/SPELLserviceWatchdogPython.C | 192 ++ lib/SPELL_SDB/Makefile.am | 77 +- lib/SPELL_SDB/src/SPELLdatabase.C | 2 +- lib/SPELL_SDB/src/SPELLdatabaseFactory.C | 2 +- lib/SPELL_SDB/src/SPELLdatabaseFile.C | 2 +- lib/SPELL_SDB/src/SPELLdatabaseFileSPB.C | 2 +- lib/SPELL_SDB/src/SPELLpyDatabaseMethods.C | 39 +- lib/SPELL_SDB/src/SPELLpyDatabaseModule.C | 10 +- lib/SPELL_SYN/Makefile.am | 96 +- .../src/SPELLconditionFactoryPOSIX.C | 2 +- lib/SPELL_SYN/src/SPELLconditionPOSIX.C | 40 +- lib/SPELL_SYN/src/SPELLevent.C | 20 +- lib/SPELL_SYN/src/SPELLmutexFactoryPOSIX.C | 2 +- lib/SPELL_SYN/src/SPELLmutexPOSIX.C | 3 +- lib/SPELL_SYN/src/SPELLthread.C | 36 +- lib/SPELL_SYN/src/SPELLthreadPool.C | 2 +- lib/SPELL_SYN/src/SPELLthreadWorker.C | 3 +- lib/SPELL_SYN/src/SPELLtimer.C | 28 +- lib/SPELL_UTIL/Makefile.am | 84 +- lib/SPELL_UTIL/src/SPELLcompression.C | 2 +- lib/SPELL_UTIL/src/SPELLerror.C | 18 +- lib/SPELL_UTIL/src/SPELLlog.C | 4 +- lib/SPELL_UTIL/src/SPELLlogPython.C | 9 +- lib/SPELL_UTIL/src/SPELLpyArgs.C | 61 +- lib/SPELL_UTIL/src/SPELLpythonHelper.C | 643 +++++- lib/SPELL_UTIL/src/SPELLtime.C | 125 +- lib/SPELL_UTIL/src/SPELLutils.C | 138 +- lib/SPELL_WRP/Makefile.am | 76 +- lib/SPELL_WRP/src/SPELLconfigDict.C | 2 +- lib/SPELL_WRP/src/SPELLdatabaseManager.C | 35 +- lib/SPELL_WRP/src/SPELLdriverManager.C | 61 +- lib/SPELL_WRP/src/SPELLpyValue.C | 8 +- lib/SPELL_WRP/src/SPELLregistry.C | 2 +- lib/SPELL_WS/Makefile.am | 120 +- lib/SPELL_WS/src/SPELLwsClassDataHandler.C | 2 +- .../src/SPELLwsCustomTypeDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsDataCodes.C | 2 +- lib/SPELL_WS/src/SPELLwsDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsDataHandlerFactory.C | 8 +- lib/SPELL_WS/src/SPELLwsDbDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsDictDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsDynamicData.C | 189 +- lib/SPELL_WS/src/SPELLwsFrame.C | 7 +- lib/SPELL_WS/src/SPELLwsInstanceDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsListDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsObjectDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsStaticData.C | 2 +- lib/SPELL_WS/src/SPELLwsStorage.C | 4 +- lib/SPELL_WS/src/SPELLwsTimeDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsTmItemDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsTupleDataHandler.C | 2 +- lib/SPELL_WS/src/SPELLwsWarmStartImpl.C | 10 +- lib/SPELL_WS/src/SPELLwsWorkingMode.C | 2 +- m4/.gitignore | 5 + m4/ax_compare_version.m4 | 177 ++ m4/ax_python_devel.m4 | 106 +- m4/ax_python_devel_v3.m4 | 325 +++ proto/IBus.proto | 61 + proto/OBus.proto | 215 ++ proto/SpacecraftInfo.proto | 8 + proto/TelemetryFrame.proto | 57 + proto/TelemetryFrameLt.proto | 16 + proto/alerts.proto | 31 + proto/header.proto | 12 + proto/limits.proto | 28 + proto/messages.proto | 24 + proto/msgbus.proto | 47 + proto/tmt.proto | 139 ++ protos/SPELLDriver.proto | 566 +++++ protos/SPELLExecutor.proto | 25 + protos/SPELLService.proto | 16 + scripts/Makefile.am | 61 +- scripts/src/Kill-PACE | 74 + scripts/src/Kill-Server | 77 + scripts/src/PACE-Server | 70 + scripts/src/SPELL-Compile | 37 + scripts/src/SPELL-Disassemble | 100 + scripts/src/SPELL-Driver | 75 + scripts/src/SPELL-Server | 69 + scripts/src/SPELL-Server-Docker | 80 + scripts/src/Shell | 40 + scripts/src/generate-release-nfo.sh | 59 + scripts/src/setup-environment.sh | 122 ++ scripts/src/version.sh | 29 + src/Makefile.am | 73 +- src/context/Makefile.am | 74 +- src/context/src/SPELLcontextMain.C | 16 +- src/executor/Makefile.am | 97 +- src/executor/src/SPELLexecutorMain.C | 23 +- src/executorcmd/Makefile.am | 102 +- src/executorcmd/src/SPELLexecutorMain.C | 12 +- src/listener/Makefile.am | 65 +- src/listener/src/SPELLlistenerMain.C | 19 +- src/ping/Makefile.am | 44 + src/ping/src/SPELLPing.C | 133 ++ src/service/Makefile.am | 65 + src/service/src/SPELLserviceMain.C | 213 ++ src/shell/Makefile.am | 102 +- src/shell/src/SPELLshellCallstack.H | 6 +- src/shell/src/SPELLshellCif.C | 4 +- src/shell/src/SPELLshellCif.H | 4 +- src/shell/src/SPELLshellCifPython.C | 25 +- src/shell/src/SPELLshellController.H | 7 +- src/shell/src/SPELLshellExecutor.C | 4 +- src/shell/src/SPELLshellExecutor.H | 13 +- src/shell/src/SPELLshellExecutorPython.C | 7 +- src/shell/src/SPELLshellMain.C | 46 +- src/spell/Makefile.am | 76 +- src/spell/spell/__init__.py | 4 +- src/spell/spell/config/__init__.py | 4 +- src/spell/spell/config/base.py | 35 +- src/spell/spell/config/constants.py | 13 +- src/spell/spell/config/contexts.py | 177 +- src/spell/spell/config/drivers.py | 68 +- src/spell/spell/config/reader.py | 103 +- src/spell/spell/lang/__init__.py | 4 +- src/spell/spell/lang/constants.py | 95 +- src/spell/spell/lang/functions.py | 1812 ++++++++++++----- src/spell/spell/lang/helpers/__init__.py | 4 +- src/spell/spell/lang/helpers/basehelper.py | 184 +- src/spell/spell/lang/helpers/datahelper.py | 130 +- src/spell/spell/lang/helpers/evhelper.py | 26 +- src/spell/spell/lang/helpers/exechelper.py | 166 +- src/spell/spell/lang/helpers/filehelper.py | 395 +++- src/spell/spell/lang/helpers/genhelper.py | 188 +- src/spell/spell/lang/helpers/limhelper.py | 937 ++++++++- src/spell/spell/lang/helpers/memhelper.py | 384 +++- src/spell/spell/lang/helpers/pcshelper.py | 1295 ++++++++++++ src/spell/spell/lang/helpers/rnghelper.py | 162 +- src/spell/spell/lang/helpers/rschelper.py | 342 ++-- src/spell/spell/lang/helpers/tchelper.py | 384 +++- src/spell/spell/lang/helpers/tmhelper.py | 501 ++--- src/spell/spell/lang/helpers/tskhelper.py | 163 +- src/spell/spell/lang/helpers/usrhelper.py | 51 +- src/spell/spell/lang/modifiers.py | 152 +- src/spell/spell/lang/user.py | 67 +- src/spell/spell/lib/__init__.py | 4 +- src/spell/spell/lib/adapter/__init__.py | 4 +- src/spell/spell/lib/adapter/config.py | 40 +- .../spell/lib/adapter/constants/__init__.py | 4 +- src/spell/spell/lib/adapter/constants/core.py | 6 +- .../lib/adapter/constants/notification.py | 4 +- src/spell/spell/lib/adapter/data.py | 12 +- .../spell/lib/adapter/databases/__init__.py | 4 +- .../spell/lib/adapter/databases/database.py | 28 +- .../spell/lib/adapter/databases/datasource.py | 636 ++++++ .../spell/lib/adapter/databases/dbasrun.py | 26 +- .../spell/lib/adapter/databases/dbfile.py | 140 +- .../spell/lib/adapter/databases/dbfileeph.py | 284 +-- .../spell/lib/adapter/databases/dbfilespb.py | 87 +- .../spell/lib/adapter/databases/dbraw.py | 139 ++ .../spell/lib/adapter/databases/dbsvn.py | 27 +- .../spell/lib/adapter/databases/dbsvnspb.py | 27 +- src/spell/spell/lib/adapter/dbmgr.py | 149 +- src/spell/spell/lib/adapter/ev.py | 99 +- src/spell/spell/lib/adapter/expression.py | 69 +- src/spell/spell/lib/adapter/file.py | 120 +- src/spell/spell/lib/adapter/gcstime.py | 16 +- src/spell/spell/lib/adapter/interface.py | 10 +- src/spell/spell/lib/adapter/memory.py | 68 +- src/spell/spell/lib/adapter/pcs.py | 555 +++++ src/spell/spell/lib/adapter/pcs_item.py | 373 ++++ src/spell/spell/lib/adapter/ranging.py | 22 +- src/spell/spell/lib/adapter/resources.py | 83 +- src/spell/spell/lib/adapter/result.py | 135 +- src/spell/spell/lib/adapter/task.py | 57 +- src/spell/spell/lib/adapter/tc.py | 128 +- src/spell/spell/lib/adapter/tc_item.py | 38 +- src/spell/spell/lib/adapter/tm.py | 563 ++--- src/spell/spell/lib/adapter/tm_item.py | 256 +-- src/spell/spell/lib/adapter/usr.py | 36 +- src/spell/spell/lib/adapter/utctime.py | 223 +- src/spell/spell/lib/adapter/value.py | 25 +- src/spell/spell/lib/adapter/verifier.py | 70 +- src/spell/spell/lib/drivermgr.py | 67 +- src/spell/spell/lib/empty/__init__.py | 4 +- src/spell/spell/lib/empty/config.py | 10 +- src/spell/spell/lib/empty/ev.py | 18 +- src/spell/spell/lib/empty/memory.py | 24 +- src/spell/spell/lib/empty/pcs.py | 105 + src/spell/spell/lib/empty/ranging.py | 12 +- src/spell/spell/lib/empty/resources.py | 26 +- src/spell/spell/lib/empty/task.py | 16 +- src/spell/spell/lib/empty/tc.py | 13 +- src/spell/spell/lib/empty/tm.py | 18 +- src/spell/spell/lib/empty/usr.py | 16 +- src/spell/spell/lib/exception.py | 39 +- src/spell/spell/lib/factory.py | 94 +- src/spell/spell/lib/registry.py | 34 +- src/spell/spell/utils/__init__.py | 4 +- src/spell/spell/utils/corba.py | 227 +-- src/spell/spell/utils/customlog.py | 32 +- src/spell/spell/utils/getch.py | 8 +- src/spell/spell/utils/grpc_utils.py | 224 ++ src/spell/spell/utils/log.py | 57 +- src/spell/spell/utils/memoize.py | 4 +- src/spell/spell/utils/resources_utils.py | 214 ++ src/spell/spell/utils/rijndael.py | 90 +- src/spell/spell/utils/spell_service.py | 159 ++ src/spell/spell/utils/timing.py | 40 +- src/spell/spell/utils/vimport.py | 44 +- 714 files changed, 36937 insertions(+), 7258 deletions(-) create mode 100644 .bsp/sbt.json create mode 100644 .cproject create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .project create mode 100644 .pydevproject create mode 100644 .settings/language.settings.xml create mode 100644 .settings/org.eclipse.cdt.codan.core.prefs create mode 100644 LICENSE create mode 100644 RELEASENOTES create mode 100644 SPELL Debug.launch create mode 100644 azure_pipelines/ci_logrotate.yaml create mode 100644 azure_pipelines/ci_spell_driver_scorpio.yaml create mode 100644 azure_pipelines/ci_spell_registry.yaml create mode 100644 azure_pipelines/ci_spell_server.yaml create mode 100644 azure_pipelines/ci_spell_ssh.yaml create mode 100644 azure_pipelines/variables/baseline.yaml create mode 100644 azure_pipelines/variables/logrotate.yaml create mode 100644 azure_pipelines/variables/spell_driver_scorpio.yaml create mode 100644 azure_pipelines/variables/spell_registry.yaml create mode 100644 azure_pipelines/variables/spell_server.yaml create mode 100644 azure_pipelines/variables/spell_ssh.yaml create mode 100644 build_driver.sh create mode 100644 build_spell.sh create mode 100644 build_spell_ubuntu.sh create mode 100644 config/contexts/context_example.xml create mode 100644 config/server/server_example.xml create mode 100644 data/InputFiles/GroundDB/GDB.DB create mode 100644 data/InputFiles/ManeuvreMessage/TEST.IMP create mode 100644 data/InputFiles/SpacecraftDB/SCDB.DB create mode 100644 data/InputFiles/User/TEST.IMP create mode 100644 data/OutputFiles/AsRun/.gitignore create mode 100644 data/OutputFiles/Warmstart/.gitignore create mode 100644 data/Procedures/.gitignore create mode 100644 data/Procedures/TestProcedure.py create mode 100644 data/Runtime/.gitignore create mode 100644 data/UserLib/.gitignore create mode 100644 deploy/bftd/ddcntl.yaml create mode 100644 deploy/bftd/externalsecret.yaml create mode 100644 deploy/bftd/flux-policies/imagepolicy.yaml create mode 100644 deploy/bftd/flux-policies/imagerepository.yaml create mode 100644 deploy/bftd/flux-policies/imageupdateautomation.yaml create mode 100644 deploy/bftd/flux-policies/nexus-ca.yaml create mode 100644 deploy/bftd/kube-mp01-values.yaml create mode 100644 deploy/bftd/kustomization.yaml create mode 100644 deploy/bftd/kustomizeconfig.yaml create mode 100644 deploy/bftd/mp01-kube-server.yaml create mode 100644 deploy/bftd/mp09-s3-server.yaml create mode 100644 deploy/bftd/s3-mp09-values.yaml create mode 100644 deploy/bftd/s3cntl.yaml create mode 100644 deploy/bftd/s5cntl.yaml create mode 100644 deploy/bftd/secretstore.yaml create mode 100644 deploy/bfts/externalsecret.yaml create mode 100644 deploy/bfts/kustomization.yaml create mode 100644 deploy/bfts/kustomizeconfig.yaml create mode 100644 deploy/bfts/missions/zs24a/kube-ses24-server.yaml create mode 100644 deploy/bfts/missions/zs24a/kube-ses24-values.yaml create mode 100644 deploy/bfts/missions/zs24a/kustomization.yaml create mode 100644 deploy/bfts/missions/zs24a/kustomizeconfig.yaml create mode 100644 deploy/bfts/missions/zs24a/ses24cntl.yaml create mode 100644 deploy/bfts/missions/zs24b/kube-ses24-server.yaml create mode 100644 deploy/bfts/missions/zs24b/kube-ses24-values.yaml create mode 100644 deploy/bfts/missions/zs24b/kustomization.yaml create mode 100644 deploy/bfts/missions/zs24b/kustomizeconfig.yaml create mode 100644 deploy/bfts/missions/zs24b/ses24cntl.yaml create mode 100644 deploy/bfts/missions/zs24c/kube-ses24-server.yaml create mode 100644 deploy/bfts/missions/zs24c/kube-ses24-values.yaml create mode 100644 deploy/bfts/missions/zs24c/kustomization.yaml create mode 100644 deploy/bfts/missions/zs24c/kustomizeconfig.yaml create mode 100644 deploy/bfts/missions/zs24c/ses24cntl.yaml create mode 100644 deploy/bfts/missions/zs24d/kube-ses24-server.yaml create mode 100644 deploy/bfts/missions/zs24d/kube-ses24-values.yaml create mode 100644 deploy/bfts/missions/zs24d/kustomization.yaml create mode 100644 deploy/bfts/missions/zs24d/kustomizeconfig.yaml create mode 100644 deploy/bfts/missions/zs24d/ses24cntl.yaml create mode 100644 deploy/bfts/missions/zs24e/kube-ses24-server.yaml create mode 100644 deploy/bfts/missions/zs24e/kube-ses24-values.yaml create mode 100644 deploy/bfts/missions/zs24e/kustomization.yaml create mode 100644 deploy/bfts/missions/zs24e/kustomizeconfig.yaml create mode 100644 deploy/bfts/missions/zs24e/ses24cntl.yaml create mode 100644 deploy/bfts/missions/zs24f/kube-ses24-server.yaml create mode 100644 deploy/bfts/missions/zs24f/kube-ses24-values.yaml create mode 100644 deploy/bfts/missions/zs24f/kustomization.yaml create mode 100644 deploy/bfts/missions/zs24f/kustomizeconfig.yaml create mode 100644 deploy/bfts/missions/zs24f/ses24cntl.yaml create mode 100644 deploy/bfts/secretstore.yaml create mode 100644 deploy/pfte/externalsecret.yaml create mode 100644 deploy/pfte/flux-policies/imagepolicy.yaml create mode 100644 deploy/pfte/flux-policies/imagerepository.yaml create mode 100644 deploy/pfte/flux-policies/imageupdateautomation.yaml create mode 100644 deploy/pfte/flux-policies/nexus-ca.yaml create mode 100644 deploy/pfte/kustomization.yaml create mode 100644 deploy/pfte/missions/ses24/kube-ses24-server.yaml create mode 100644 deploy/pfte/missions/ses24/kube-ses24-values.yaml create mode 100644 deploy/pfte/missions/ses24/kustomization.yaml create mode 100644 deploy/pfte/missions/ses24/kustomizeconfig.yaml create mode 100644 deploy/pfte/missions/ses24/ses24cntl.yaml create mode 100644 deploy/pfte/secretstore.yaml create mode 100644 deploy/pfts/externalsecret.yaml create mode 100644 deploy/pfts/flux-policies/imagepolicy.yaml create mode 100644 deploy/pfts/flux-policies/imagerepository.yaml create mode 100644 deploy/pfts/flux-policies/imageupdateautomation.yaml create mode 100644 deploy/pfts/flux-policies/nexus-ca.yaml create mode 100644 deploy/pfts/kustomization.yaml create mode 100644 deploy/pfts/missions/ses24/kube-ses24-server.yaml create mode 100644 deploy/pfts/missions/ses24/kube-ses24-values.yaml create mode 100644 deploy/pfts/missions/ses24/kustomization.yaml create mode 100644 deploy/pfts/missions/ses24/kustomizeconfig.yaml create mode 100644 deploy/pfts/missions/ses24/ses24cntl.yaml create mode 100644 deploy/pfts/secretstore.yaml create mode 100644 deploy/spell-registry/deployment.yaml create mode 100644 deploy/spell-registry/ingress.yaml create mode 100644 deploy/spell-registry/kustomization.yaml create mode 100644 deploy/spell-registry/role.yaml create mode 100644 deploy/spell-registry/rolebinding.yaml create mode 100644 deploy/spell-registry/sa.yaml create mode 100644 deploy/spell-registry/service.yaml create mode 100644 deploy/spell-server/.helmignore create mode 100644 deploy/spell-server/Chart.yaml create mode 100644 deploy/spell-server/configs/context.xml create mode 100644 deploy/spell-server/configs/driver.xml create mode 100644 deploy/spell-server/configs/language.xml create mode 100644 deploy/spell-server/configs/log.properties create mode 100644 deploy/spell-server/configs/scheduler_wasp.xml create mode 100644 deploy/spell-server/configs/server.xml create mode 100644 deploy/spell-server/templates/NOTES.txt create mode 100644 deploy/spell-server/templates/_helpers.tpl create mode 100644 deploy/spell-server/templates/cm.yaml create mode 100644 deploy/spell-server/templates/deployment.yaml create mode 100644 deploy/spell-server/templates/pvc.yaml create mode 100644 deploy/spell-server/templates/service.yaml create mode 100644 deploy/spell-server/templates/tests/test-connection.yaml create mode 100644 deploy/spell-server/values.yaml create mode 100644 docker/.dockerignore create mode 100644 docker/Makefile create mode 100644 docker/drivers/Makefile create mode 100644 docker/drivers/example/Dockerfile create mode 100644 docker/drivers/example/Makefile create mode 100644 docker/drivers/fuzzer/Dockerfile create mode 100644 docker/drivers/fuzzer/Makefile create mode 100644 docker/drivers/hifly2/Dockerfile create mode 100644 docker/drivers/hifly2/Makefile create mode 100644 docker/drivers/scorpio/Dockerfile create mode 100644 docker/drivers/scorpio/Makefile create mode 100644 docker/logrotate/Dockerfile create mode 100644 docker/logrotate/Makefile create mode 100644 docker/logrotate/docker-entrypoint.sh create mode 100644 docker/logrotate/go-cron.tar.gz create mode 100644 docker/logrotate/logrotate.conf create mode 100644 docker/spell-registry/.gitignore create mode 100644 docker/spell-registry/.scalafmt.conf create mode 100644 docker/spell-registry/Makefile create mode 100644 docker/spell-registry/README.md create mode 100644 docker/spell-registry/build.sbt create mode 100644 docker/spell-registry/project/build.properties create mode 100644 docker/spell-registry/project/plugins.sbt create mode 100644 docker/spell-registry/src/main/resources/application.conf create mode 100644 docker/spell-registry/src/main/scala/KubeAPI.scala create mode 100644 docker/spell-registry/src/main/scala/Main.scala create mode 100644 docker/spell-registry/src/main/scala/SpellServerService.scala create mode 100644 docker/spell-registry/src/test/scala/MySuite.scala create mode 100644 docker/spell-server/Dockerfile create mode 100644 docker/spell-server/Makefile create mode 100644 docker/ssh-server/Dockerfile create mode 100644 docker/ssh-server/Dockerfile.centos create mode 100644 docker/ssh-server/Makefile create mode 100644 docker/ssh-server/etc/yum.repos.d/bzenginst1.repo create mode 100644 docker/ssh-server/init.sh create mode 100644 drivers/example/python/__init__.py create mode 100644 drivers/example/python/config.py create mode 100644 drivers/example/python/constants.py create mode 100644 drivers/example/python/ev.py create mode 100644 drivers/example/python/gcstime.py create mode 100644 drivers/example/python/modifiers.py create mode 100644 drivers/example/python/resources.py create mode 100644 drivers/example/python/task.py create mode 100644 drivers/example/python/tc.py create mode 100644 drivers/example/python/tm.py create mode 100644 drivers/example/python/usr.py create mode 100644 drivers/service/__init__.py create mode 100644 drivers/service/cif_client.py create mode 100644 drivers/service/driverserver.py create mode 100644 drivers/service/ev_service.py create mode 100644 drivers/service/exec_client.py create mode 100644 drivers/service/info_service.py create mode 100644 drivers/service/mem_service.py create mode 100644 drivers/service/pcs_service.py create mode 100644 drivers/service/rng_service.py create mode 100644 drivers/service/rsc_service.py create mode 100644 drivers/service/task_service.py create mode 100644 drivers/service/tc_service.py create mode 100644 drivers/service/time_service.py create mode 100644 drivers/service/tm_service.py create mode 100644 drivers/service/user_service.py create mode 100644 include/SPELL_CIFS/SPELLresourceBuffer.H create mode 100644 include/SPELL_CTX/SPELLservice.H create mode 100644 include/SPELL_CTX/SPELLserviceListener.H create mode 100644 include/SPELL_CTX/SPELLserviceManager.H create mode 100644 include/SPELL_CTX/SPELLserviceStartupParams.H create mode 100644 include/SPELL_EXC/SPELLresourceInfo.H create mode 100644 include/SPELL_EXC/SPELLresourceManager.H create mode 100644 include/SPELL_EXC/SPELLresourceMonitor.H create mode 100644 include/SPELL_EXC/SPELLserviceImpl.H create mode 100644 include/SPELL_EXC/SPELLserviceInterpreter.H create mode 100644 include/SPELL_PRD/SPELLpyService.H create mode 100644 include/SPELL_PRD/SPELLservicePython.H create mode 100644 include/SPELL_PRD/SPELLserviceWatchdogPython.H create mode 100644 lib/SPELL_CIFS/src/SPELLresourceBuffer.C create mode 100644 lib/SPELL_CTX/src/SPELLservice.C create mode 100644 lib/SPELL_CTX/src/SPELLserviceManager.C create mode 100644 lib/SPELL_EXC/src/SPELLresourceManager.C create mode 100644 lib/SPELL_EXC/src/SPELLresourceMonitor.C create mode 100644 lib/SPELL_EXC/src/SPELLserviceImpl.C create mode 100644 lib/SPELL_EXC/src/SPELLserviceInterpreter.C create mode 100644 lib/SPELL_PRD/src/SPELLpyService.C create mode 100644 lib/SPELL_PRD/src/SPELLserviceRegisterPython.C create mode 100644 lib/SPELL_PRD/src/SPELLserviceWatchdogPython.C create mode 100644 m4/.gitignore create mode 100644 m4/ax_compare_version.m4 create mode 100644 m4/ax_python_devel_v3.m4 create mode 100644 proto/IBus.proto create mode 100644 proto/OBus.proto create mode 100644 proto/SpacecraftInfo.proto create mode 100644 proto/TelemetryFrame.proto create mode 100644 proto/TelemetryFrameLt.proto create mode 100644 proto/alerts.proto create mode 100644 proto/header.proto create mode 100644 proto/limits.proto create mode 100644 proto/messages.proto create mode 100644 proto/msgbus.proto create mode 100644 proto/tmt.proto create mode 100644 protos/SPELLDriver.proto create mode 100644 protos/SPELLExecutor.proto create mode 100644 protos/SPELLService.proto create mode 100644 scripts/src/Kill-PACE create mode 100644 scripts/src/Kill-Server create mode 100644 scripts/src/PACE-Server create mode 100644 scripts/src/SPELL-Compile create mode 100644 scripts/src/SPELL-Disassemble create mode 100644 scripts/src/SPELL-Driver create mode 100644 scripts/src/SPELL-Server create mode 100644 scripts/src/SPELL-Server-Docker create mode 100644 scripts/src/Shell create mode 100644 scripts/src/generate-release-nfo.sh create mode 100644 scripts/src/setup-environment.sh create mode 100644 scripts/src/version.sh create mode 100644 src/ping/Makefile.am create mode 100644 src/ping/src/SPELLPing.C create mode 100644 src/service/Makefile.am create mode 100644 src/service/src/SPELLserviceMain.C create mode 100644 src/spell/spell/lang/helpers/pcshelper.py create mode 100644 src/spell/spell/lib/adapter/databases/datasource.py create mode 100644 src/spell/spell/lib/adapter/databases/dbraw.py create mode 100644 src/spell/spell/lib/adapter/pcs.py create mode 100644 src/spell/spell/lib/adapter/pcs_item.py create mode 100644 src/spell/spell/lib/empty/pcs.py create mode 100644 src/spell/spell/utils/grpc_utils.py create mode 100644 src/spell/spell/utils/resources_utils.py create mode 100644 src/spell/spell/utils/spell_service.py diff --git a/.bsp/sbt.json b/.bsp/sbt.json new file mode 100644 index 0000000..7c86a39 --- /dev/null +++ b/.bsp/sbt.json @@ -0,0 +1 @@ +{"name":"sbt","version":"1.7.1","bspVersion":"2.0.0-M5","languages":["scala"],"argv":["/home/jgall/.jabba/jdk/openjdk@1.17.0/bin/java","-Xms100m","-Xmx100m","-classpath","/home/jgall/bin/sbt-lib/bin/sbt-launch.jar","-Dsbt.script=/home/jgall/bin/sbt","xsbt.boot.Boot","-bsp"]} \ No newline at end of file diff --git a/.cproject b/.cproject new file mode 100644 index 0000000..3b2bb16 --- /dev/null +++ b/.cproject @@ -0,0 +1,600 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + make + + all + true + true + false + + + make + + am--refresh + true + true + false + + + make + + check + true + true + false + + + make + + clean + true + true + false + + + make + + clean-libtool + true + true + false + + + make + + config.h + true + true + false + + + make + + ctags + true + true + false + + + make + + ctags-recursive + true + true + false + + + make + + dist + true + true + false + + + make + + dist-all + true + true + false + + + make + + dist-bzip2 + true + true + false + + + make + + dist-gzip + true + true + false + + + make + + dist-lzma + true + true + false + + + make + + dist-shar + true + true + false + + + make + + dist-tarZ + true + true + false + + + make + + dist-zip + true + true + false + + + make + + distcheck + true + true + false + + + make + + distclean + true + true + false + + + make + + distclean-hdr + true + true + false + + + make + + distclean-libtool + true + true + false + + + make + + distclean-tags + true + true + false + + + make + + distcleancheck + true + true + false + + + make + + distdir + true + true + false + + + make + + distuninstallcheck + true + true + false + + + make + + dvi + true + true + false + + + make + + html + true + true + false + + + make + + info + true + true + false + + + make + + install + true + true + false + + + make + + install-data + true + true + false + + + make + + install-dvi + true + true + false + + + make + + install-exec + true + true + false + + + make + + install-exec-hook + true + true + false + + + make + + install-html + true + true + false + + + make + + install-info + true + true + false + + + make + + install-man + true + true + false + + + make + + install-pdf + true + true + false + + + make + + install-ps + true + true + false + + + make + + install-strip + true + true + false + + + make + + installcheck + true + true + false + + + make + + installdirs + true + true + false + + + make + + maintainer-clean + true + true + false + + + make + + Makefile + true + true + false + + + make + + mostlyclean + true + true + false + + + make + + mostlyclean-libtool + true + true + false + + + make + + pdf + true + true + false + + + make + + ps + true + true + false + + + make + + stamp-h1 + true + true + false + + + make + + tags + true + true + false + + + make + + tags-recursive + true + true + false + + + make + + uninstall + true + true + false + + + + diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..00788a8 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +docker +deploy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3f39719 --- /dev/null +++ b/.gitignore @@ -0,0 +1,27 @@ +*.pyc +*.swp +Makefile.in +*.o +*.pyo +*.lo +*.la +.deps +.libs +*.log +/build/ +/autom4te.cache/ +/aclocal.m4 +/config.guess +/config.h.in +/config.status +/config.sub +/configure +/depcomp +/install-sh +/libtool +/missing +/stamp-h1 +/config.h +/ltmain.sh +/ar-lib +workspace.code-workspace diff --git a/.project b/.project new file mode 100644 index 0000000..fab42e8 --- /dev/null +++ b/.project @@ -0,0 +1,62 @@ + + + spell + + + + + + org.eclipse.xtext.ui.shared.xtextBuilder + + + + + org.python.pydev.PyDevBuilder + + + + + org.eclipse.cdt.autotools.core.genmakebuilderV2 + + + + + org.eclipse.cdt.managedbuilder.core.genmakebuilder + clean,full,incremental, + + + + + org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder + full,incremental, + + + + + + org.eclipse.cdt.core.cnature + org.eclipse.cdt.core.ccnature + org.eclipse.cdt.managedbuilder.core.managedBuildNature + org.eclipse.cdt.autotools.core.autotoolsNatureV2 + org.eclipse.cdt.managedbuilder.core.ScannerConfigNature + org.python.pydev.pythonNature + org.eclipse.xtext.ui.shared.xtextNature + + + + python2.7 + 2 + /home/spell/SPELL-COTS2.5.7a/include/python3.7m + + + spell_stubs + 2 + /home/jgall/SPELL_TEST/spell + + + protos/google + 2 + /home/spell/spell_cots/lib/python2.7/site-packages/grpc_tools/_proto/google + + + diff --git a/.pydevproject b/.pydevproject new file mode 100644 index 0000000..389ee64 --- /dev/null +++ b/.pydevproject @@ -0,0 +1,11 @@ + + +Default +python 3.0 + +/${PROJECT_DIR_NAME}/src/spell +/${PROJECT_DIR_NAME}/spell_stubs +/${PROJECT_DIR_NAME}/drivers/remote_interface +/${PROJECT_DIR_NAME}/drivers/service + + diff --git a/.settings/language.settings.xml b/.settings/language.settings.xml new file mode 100644 index 0000000..adcc38a --- /dev/null +++ b/.settings/language.settings.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.settings/org.eclipse.cdt.codan.core.prefs b/.settings/org.eclipse.cdt.codan.core.prefs new file mode 100644 index 0000000..b5248c6 --- /dev/null +++ b/.settings/org.eclipse.cdt.codan.core.prefs @@ -0,0 +1,71 @@ +eclipse.preferences.version=1 +org.eclipse.cdt.codan.checkers.errnoreturn=Warning +org.eclipse.cdt.codan.checkers.errnoreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return\\")",implicit\=>false} +org.eclipse.cdt.codan.checkers.errreturnvalue=Error +org.eclipse.cdt.codan.checkers.errreturnvalue.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused return value\\")"} +org.eclipse.cdt.codan.checkers.nocommentinside=-Error +org.eclipse.cdt.codan.checkers.nocommentinside.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Nesting comments\\")"} +org.eclipse.cdt.codan.checkers.nolinecomment=-Error +org.eclipse.cdt.codan.checkers.nolinecomment.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Line comments\\")"} +org.eclipse.cdt.codan.checkers.noreturn=Error +org.eclipse.cdt.codan.checkers.noreturn.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No return value\\")",implicit\=>false} +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation=Error +org.eclipse.cdt.codan.internal.checkers.AbstractClassCreation.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Abstract class cannot be instantiated\\")"} +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem=Error +org.eclipse.cdt.codan.internal.checkers.AmbiguousProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Ambiguous problem\\")"} +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem=Warning +org.eclipse.cdt.codan.internal.checkers.AssignmentInConditionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment in condition\\")"} +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem=Error +org.eclipse.cdt.codan.internal.checkers.AssignmentToItselfProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Assignment to itself\\")"} +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem=Warning +org.eclipse.cdt.codan.internal.checkers.CaseBreakProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"No break at end of case\\")",no_break_comment\=>"no break",last_case_param\=>false,empty_case_param\=>false} +org.eclipse.cdt.codan.internal.checkers.CatchByReference=Warning +org.eclipse.cdt.codan.internal.checkers.CatchByReference.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Catching by reference is recommended\\")",unknown\=>false,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem=Error +org.eclipse.cdt.codan.internal.checkers.CircularReferenceProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Circular inheritance\\")"} +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization=Warning +org.eclipse.cdt.codan.internal.checkers.ClassMembersInitialization.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class members should be properly initialized\\")",skip\=>true} +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.FieldResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Field cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.FunctionResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Function cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.InvalidArguments=Error +org.eclipse.cdt.codan.internal.checkers.InvalidArguments.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid arguments\\")"} +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem=Error +org.eclipse.cdt.codan.internal.checkers.InvalidTemplateArgumentsProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid template argument\\")"} +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem=Error +org.eclipse.cdt.codan.internal.checkers.LabelStatementNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Label statement not found\\")"} +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem=Error +org.eclipse.cdt.codan.internal.checkers.MemberDeclarationNotFoundProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Member declaration not found\\")"} +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.MethodResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Method cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker=-Info +org.eclipse.cdt.codan.internal.checkers.NamingConventionFunctionChecker.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Name convention for function\\")",pattern\=>"^[a-z]",macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem=Warning +org.eclipse.cdt.codan.internal.checkers.NonVirtualDestructorProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Class has a virtual method and non-virtual destructor\\")"} +org.eclipse.cdt.codan.internal.checkers.OverloadProblem=Error +org.eclipse.cdt.codan.internal.checkers.OverloadProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid overload\\")"} +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem=Error +org.eclipse.cdt.codan.internal.checkers.RedeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redeclaration\\")"} +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem=Error +org.eclipse.cdt.codan.internal.checkers.RedefinitionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Invalid redefinition\\")"} +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ReturnStyleProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Return with parenthesis\\")"} +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem=-Warning +org.eclipse.cdt.codan.internal.checkers.ScanfFormatStringSecurityProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Format String Vulnerability\\")"} +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem=Warning +org.eclipse.cdt.codan.internal.checkers.StatementHasNoEffectProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Statement has no effect\\")",macro\=>true,exceptions\=>()} +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem=Warning +org.eclipse.cdt.codan.internal.checkers.SuggestedParenthesisProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suggested parenthesis around expression\\")",paramNot\=>false} +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem=Warning +org.eclipse.cdt.codan.internal.checkers.SuspiciousSemicolonProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Suspicious semicolon\\")",else\=>false,afterelse\=>false} +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.TypeResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Type cannot be resolved\\")"} +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedFunctionDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused function declaration\\")",macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedStaticFunctionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused static function\\")",macro\=>true} +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem=Warning +org.eclipse.cdt.codan.internal.checkers.UnusedVariableDeclarationProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Unused variable declaration in file scope\\")",macro\=>true,exceptions\=>("@(\#)","$Id")} +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem=Error +org.eclipse.cdt.codan.internal.checkers.VariableResolutionProblem.params={launchModes\=>{RUN_ON_FULL_BUILD\=>true,RUN_ON_INC_BUILD\=>true,RUN_ON_FILE_OPEN\=>false,RUN_ON_FILE_SAVE\=>false,RUN_AS_YOU_TYPE\=>true,RUN_ON_DEMAND\=>true},suppression_comment\=>"@suppress(\\"Symbol is not resolved\\")"} diff --git a/INSTALL b/INSTALL index 7d1c323..f214f5f 100644 --- a/INSTALL +++ b/INSTALL @@ -1,365 +1,17 @@ -Installation Instructions -************************* -Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, -2006, 2007, 2008, 2009 Free Software Foundation, Inc. +$ autoreconf -fi +$ mkdir build && cd build +$ ../configure --prefix=/path/to/install/dir - Copying and distribution of this file, with or without modification, -are permitted in any medium without royalty provided the copyright -notice and this notice are preserved. This file is offered as-is, -without warranty of any kind. +If some headers or libraries reside in exotic locations, add CPPFLAGS and LDFLAGS. +Export PATH and LD_LIBRARY_PATH for those libraries to be detected. For example, -Basic Installation -================== +$ SPELL_COTS=/path/to/spell/cots +$ export LD_LIBRARY_PATH=$SPELL_COTS/lib:$LD_LIBRARY_PATH +$ PATH=$SPELL_COTS/bin:$PATH +$ ../configure --prefix=/path/to/install/dir CPPFLAGS="-I$SPELL_COTS/include" LDFLAGS="-L$SPELL_COTS/lib" - Briefly, the shell commands `./configure; make; make install' should -configure, build, and install this package. The following -more-detailed instructions are generic; see the `README' file for -instructions specific to this package. Some packages provide this -`INSTALL' file but do not implement all of the features documented -below. The lack of an optional feature in a given package is not -necessarily a bug. More recommendations for GNU packages can be found -in *note Makefile Conventions: (standards)Makefile Conventions. - - The `configure' shell script attempts to guess correct values for -various system-dependent variables used during compilation. It uses -those values to create a `Makefile' in each directory of the package. -It may also create one or more `.h' files containing system-dependent -definitions. Finally, it creates a shell script `config.status' that -you can run in the future to recreate the current configuration, and a -file `config.log' containing compiler output (useful mainly for -debugging `configure'). - - It can also use an optional file (typically called `config.cache' -and enabled with `--cache-file=config.cache' or simply `-C') that saves -the results of its tests to speed up reconfiguring. Caching is -disabled by default to prevent problems with accidental use of stale -cache files. - - If you need to do unusual things to compile the package, please try -to figure out how `configure' could check whether to do them, and mail -diffs or instructions to the address given in the `README' so they can -be considered for the next release. If you are using the cache, and at -some point `config.cache' contains results you don't want to keep, you -may remove or edit it. - - The file `configure.ac' (or `configure.in') is used to create -`configure' by a program called `autoconf'. You need `configure.ac' if -you want to change it or regenerate `configure' using a newer version -of `autoconf'. - - The simplest way to compile this package is: - - 1. `cd' to the directory containing the package's source code and type - `./configure' to configure the package for your system. - - Running `configure' might take a while. While running, it prints - some messages telling which features it is checking for. - - 2. Type `make' to compile the package. - - 3. Optionally, type `make check' to run any self-tests that come with - the package, generally using the just-built uninstalled binaries. - - 4. Type `make install' to install the programs and any data files and - documentation. When installing into a prefix owned by root, it is - recommended that the package be configured and built as a regular - user, and only the `make install' phase executed with root - privileges. - - 5. Optionally, type `make installcheck' to repeat any self-tests, but - this time using the binaries in their final installed location. - This target does not install anything. Running this target as a - regular user, particularly if the prior `make install' required - root privileges, verifies that the installation completed - correctly. - - 6. You can remove the program binaries and object files from the - source code directory by typing `make clean'. To also remove the - files that `configure' created (so you can compile the package for - a different kind of computer), type `make distclean'. There is - also a `make maintainer-clean' target, but that is intended mainly - for the package's developers. If you use it, you may have to get - all sorts of other programs in order to regenerate files that came - with the distribution. - - 7. Often, you can also type `make uninstall' to remove the installed - files again. In practice, not all packages have tested that - uninstallation works correctly, even though it is required by the - GNU Coding Standards. - - 8. Some packages, particularly those that use Automake, provide `make - distcheck', which can by used by developers to test that all other - targets like `make install' and `make uninstall' work correctly. - This target is generally not run by end users. - -Compilers and Options -===================== - - Some systems require unusual options for compilation or linking that -the `configure' script does not know about. Run `./configure --help' -for details on some of the pertinent environment variables. - - You can give `configure' initial values for configuration parameters -by setting variables in the command line or in the environment. Here -is an example: - - ./configure CC=c99 CFLAGS=-g LIBS=-lposix - - *Note Defining Variables::, for more details. - -Compiling For Multiple Architectures -==================================== - - You can compile the package for more than one kind of computer at the -same time, by placing the object files for each architecture in their -own directory. To do this, you can use GNU `make'. `cd' to the -directory where you want the object files and executables to go and run -the `configure' script. `configure' automatically checks for the -source code in the directory that `configure' is in and in `..'. This -is known as a "VPATH" build. - - With a non-GNU `make', it is safer to compile the package for one -architecture at a time in the source code directory. After you have -installed the package for one architecture, use `make distclean' before -reconfiguring for another architecture. - - On MacOS X 10.5 and later systems, you can create libraries and -executables that work on multiple system types--known as "fat" or -"universal" binaries--by specifying multiple `-arch' options to the -compiler but only a single `-arch' option to the preprocessor. Like -this: - - ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ - CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ - CPP="gcc -E" CXXCPP="g++ -E" - - This is not guaranteed to produce working output in all cases, you -may have to build one architecture at a time and combine the results -using the `lipo' tool if you have problems. - -Installation Names -================== - - By default, `make install' installs the package's commands under -`/usr/local/bin', include files under `/usr/local/include', etc. You -can specify an installation prefix other than `/usr/local' by giving -`configure' the option `--prefix=PREFIX', where PREFIX must be an -absolute file name. - - You can specify separate installation prefixes for -architecture-specific files and architecture-independent files. If you -pass the option `--exec-prefix=PREFIX' to `configure', the package uses -PREFIX as the prefix for installing programs and libraries. -Documentation and other data files still use the regular prefix. - - In addition, if you use an unusual directory layout you can give -options like `--bindir=DIR' to specify different values for particular -kinds of files. Run `configure --help' for a list of the directories -you can set and what kinds of files go in them. In general, the -default for these options is expressed in terms of `${prefix}', so that -specifying just `--prefix' will affect all of the other directory -specifications that were not explicitly provided. - - The most portable way to affect installation locations is to pass the -correct locations to `configure'; however, many packages provide one or -both of the following shortcuts of passing variable assignments to the -`make install' command line to change installation locations without -having to reconfigure or recompile. - - The first method involves providing an override variable for each -affected directory. For example, `make install -prefix=/alternate/directory' will choose an alternate location for all -directory configuration variables that were expressed in terms of -`${prefix}'. Any directories that were specified during `configure', -but not in terms of `${prefix}', must each be overridden at install -time for the entire installation to be relocated. The approach of -makefile variable overrides for each directory variable is required by -the GNU Coding Standards, and ideally causes no recompilation. -However, some platforms have known limitations with the semantics of -shared libraries that end up requiring recompilation when using this -method, particularly noticeable in packages that use GNU Libtool. - - The second method involves providing the `DESTDIR' variable. For -example, `make install DESTDIR=/alternate/directory' will prepend -`/alternate/directory' before all installation names. The approach of -`DESTDIR' overrides is not required by the GNU Coding Standards, and -does not work on platforms that have drive letters. On the other hand, -it does better at avoiding recompilation issues, and works well even -when some directory options were not specified in terms of `${prefix}' -at `configure' time. - -Optional Features -================= - - If the package supports it, you can cause programs to be installed -with an extra prefix or suffix on their names by giving `configure' the -option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. - - Some packages pay attention to `--enable-FEATURE' options to -`configure', where FEATURE indicates an optional part of the package. -They may also pay attention to `--with-PACKAGE' options, where PACKAGE -is something like `gnu-as' or `x' (for the X Window System). The -`README' should mention any `--enable-' and `--with-' options that the -package recognizes. - - For packages that use the X Window System, `configure' can usually -find the X include and library files automatically, but if it doesn't, -you can use the `configure' options `--x-includes=DIR' and -`--x-libraries=DIR' to specify their locations. - - Some packages offer the ability to configure how verbose the -execution of `make' will be. For these packages, running `./configure ---enable-silent-rules' sets the default to minimal output, which can be -overridden with `make V=1'; while running `./configure ---disable-silent-rules' sets the default to verbose, which can be -overridden with `make V=0'. - -Particular systems -================== - - On HP-UX, the default C compiler is not ANSI C compatible. If GNU -CC is not installed, it is recommended to use the following options in -order to use an ANSI C compiler: - - ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" - -and if that doesn't work, install pre-built binaries of GCC for HP-UX. - - On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot -parse its `' header file. The option `-nodtk' can be used as -a workaround. If GNU CC is not installed, it is therefore recommended -to try - - ./configure CC="cc" - -and if that doesn't work, try - - ./configure CC="cc -nodtk" - - On Solaris, don't put `/usr/ucb' early in your `PATH'. This -directory contains several dysfunctional programs; working variants of -these programs are available in `/usr/bin'. So, if you need `/usr/ucb' -in your `PATH', put it _after_ `/usr/bin'. - - On Haiku, software installed for all users goes in `/boot/common', -not `/usr/local'. It is recommended to use the following options: - - ./configure --prefix=/boot/common - -Specifying the System Type -========================== - - There may be some features `configure' cannot figure out -automatically, but needs to determine by the type of machine the package -will run on. Usually, assuming the package is built to be run on the -_same_ architectures, `configure' can figure that out, but if it prints -a message saying it cannot guess the machine type, give it the -`--build=TYPE' option. TYPE can either be a short name for the system -type, such as `sun4', or a canonical name which has the form: - - CPU-COMPANY-SYSTEM - -where SYSTEM can have one of these forms: - - OS - KERNEL-OS - - See the file `config.sub' for the possible values of each field. If -`config.sub' isn't included in this package, then this package doesn't -need to know the machine type. - - If you are _building_ compiler tools for cross-compiling, you should -use the option `--target=TYPE' to select the type of system they will -produce code for. - - If you want to _use_ a cross compiler, that generates code for a -platform different from the build platform, you should specify the -"host" platform (i.e., that on which the generated programs will -eventually be run) with `--host=TYPE'. - -Sharing Defaults -================ - - If you want to set default values for `configure' scripts to share, -you can create a site shell script called `config.site' that gives -default values for variables like `CC', `cache_file', and `prefix'. -`configure' looks for `PREFIX/share/config.site' if it exists, then -`PREFIX/etc/config.site' if it exists. Or, you can set the -`CONFIG_SITE' environment variable to the location of the site script. -A warning: not all `configure' scripts look for a site script. - -Defining Variables -================== - - Variables not defined in a site shell script can be set in the -environment passed to `configure'. However, some packages may run -configure again during the build, and the customized values of these -variables may be lost. In order to avoid this problem, you should set -them in the `configure' command line, using `VAR=value'. For example: - - ./configure CC=/usr/local2/bin/gcc - -causes the specified `gcc' to be used as the C compiler (unless it is -overridden in the site shell script). - -Unfortunately, this technique does not work for `CONFIG_SHELL' due to -an Autoconf bug. Until the bug is fixed you can use this workaround: - - CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash - -`configure' Invocation -====================== - - `configure' recognizes the following options to control how it -operates. - -`--help' -`-h' - Print a summary of all of the options to `configure', and exit. - -`--help=short' -`--help=recursive' - Print a summary of the options unique to this package's - `configure', and exit. The `short' variant lists options used - only in the top level, while the `recursive' variant lists options - also present in any nested packages. - -`--version' -`-V' - Print the version of Autoconf used to generate the `configure' - script, and exit. - -`--cache-file=FILE' - Enable the cache: use and save the results of the tests in FILE, - traditionally `config.cache'. FILE defaults to `/dev/null' to - disable caching. - -`--config-cache' -`-C' - Alias for `--cache-file=config.cache'. - -`--quiet' -`--silent' -`-q' - Do not print messages saying which checks are being made. To - suppress all normal output, redirect it to `/dev/null' (any error - messages will still be shown). - -`--srcdir=DIR' - Look for the package's source code in directory DIR. Usually - `configure' can determine that directory automatically. - -`--prefix=DIR' - Use DIR as the installation prefix. *note Installation Names:: - for more details, including other options available for fine-tuning - the installation locations. - -`--no-create' -`-n' - Run the configure checks, but stop before creating any output - files. - -`configure' also accepts some other, not widely useful, options. Run -`configure --help' for more details. +To enable debugging, add -DWITH_DEBUG to CPPFLAGS. +$ make +$ make install diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile.am b/Makefile.am index 6935578..2e436d2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,34 +1,34 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2012 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ +AUTOMAKE_OPTIONS = subdir-objects foreign + +include ${top_srcdir}/common.mk ACLOCAL_AMFLAGS = -I m4 -AUTOMAKE_OPTIONS = foreign -SUBDIRS = lib drivers src config doc rcp scripts procs data -install-exec-hook: - mkdir -p ${exec_prefix}/log +SUBDIRS = lib drivers src config data scripts +RELEASE_NFO=${exec_prefix}/release.nfo +install-exec-hook: + mkdir -p ${exec_prefix}/log + chmod g+w ${exec_prefix}/log + ${top_srcdir}/scripts/src/generate-release-nfo.sh ${top_srcdir}/RELEASENOTES ${PACKAGE_VERSION} ${exec_prefix} > ${RELEASE_NFO} diff --git a/README b/README index 1143bda..5a50bff 100644 --- a/README +++ b/README @@ -1,20 +1,20 @@ - - Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. - - This file is part of SPELL. - - SPELL is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - SPELL 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with SPELL. If not, see . - - Comments are welcome. - +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ diff --git a/RELEASENOTES b/RELEASENOTES new file mode 100644 index 0000000..7dcc485 --- /dev/null +++ b/RELEASENOTES @@ -0,0 +1,332 @@ +--------- +CHANGELOG +--------- + +What's new in SPELL 2.6.8.4 +========================== +** Task + * Fix procedure listener cleanup + * Fix port assignment for on prem deployment + * Fix badly formatted WaitFor + * Fix loading of SCDB and GDB entries + +What's new in SPELL 2.6.8.2 +========================== +** Task + * [SPELL-650] - Issue when taking control of a background (ARC) service running for a long time + +What's new in SPELL 2.6.8.1 +========================== +** Bug + * [SPELL-644] - AOS_TM_TC_MONITOR stuck on ARC BB and SPELL Froze + +** Enhancement + * [SPELL-547] - Containerize SPELL to ease deployment + +What's new in SPELL 2.6.8 +========================== +** Bug + * [SPELL-631] - Merge file import does not read content correctly + * [SPELL-641] - TIME object can't be created using a long anymore + +What's new in SPELL 2.6.7.8 +========================== +** Bug + * [SPELL-640] - SPELL Listener and SPELL context crash on Disconnect + +What's new in SPELL 2.6.7.7 +========================== +** Enhancement + * [SPELL-635] - Implement RestoreNormalLimits using FleetLimits + * [SPELL-637] - allow fleetlimits and archiva to coexist in the driver config + +What's new in SPELL 2.6.7.6 +========================== +** Bug + * [SPELL-636] - SPELL Scorpio driver crashes when loosing connection to GCS + +What's new in SPELL 2.6.7.5 +========================== +** Bug + * [SPELL-631] - Merge file import does not read content correctly + +What's new in SPELL 2.6.7.4 +========================== +** Bug + * [SPELL-625] - IPA_CALIBRATION procedure crashed for MP01 on M2 + * [SPELL-631] - Merge file import does not read content correctly + +** Enhancement + * [FLEET-3342] - Use a clientname to enable sticky sessions + +What's new in SPELL 2.6.7.3 +========================== +** Bug + * [SPELL-626] - PACE activities failing and not being able to reopen in SPELL + +What's new in SPELL 2.6.7.2 +========================== +** Bug + * [SPELL-622] - Regression - Issues with Prompt after ECHO CV Failure + +What's new in SPELL 2.6.7.1 +========================== +** Bug + * [SPELL-622] - Issues with Prompt after ECHO CV Failure + +What's new in SPELL 2.6.7 +========================== +** Bug + * [SPELL-624] - Retry Command Prompts are broken + +What's new in SPELL 2.6.6.5 +========================== +** Bug + * [SPELL-618] - stranded crtxcmdb processes on m2cntl - SPELL false CV failures + +What's new in SPELL 2.6.6.4 +========================== +** Bug + * [SPELL-619] - NORTH_SOUTH_STATIONKEEPING proc crash due to Execution aborted: Key error + +What's new in SPELL 2.6.6.3 +========================== +** Bug + * [SPELL-616] - Undefined error variable in Scoprio driver causes memory corruption in SPELL + +What's new in SPELL 2.6.6.2 +========================== +** Bug + * [SPELL-615] - ORT Uplink Check script failing. (additional fix) + +What's new in SPELL 2.6.6.1 +========================== +** Bug + * [SPELL-615] - ORT Uplink Check script failing. + +What's new in SPELL 2.6.6 +========================== +** Bug + * [SPELL-608] - IPC not connected message for long running procedures + * [SPELL-610] - Recurrent failures trying to acquire ground variables + * [SPELL-611] - Use After Free Crash of scorpio driver interface + * [SPELL-614] - GAR13043-Procedure 31104_North_South_Stationkeeping aborted and could not be unrecovered + +What's new in SPELL 2.6.5.5 +========================== +** Bug + * [SPELL-307] - Sorting of files at the import file viewer/edit calling arguments (Server file selection)Sorting of files at the import file viewer/edit calling arguments (Server file selection) + * [Commit-cb233664787] Fix-regression-in-sce-toggle-functionality + +What's new in SPELL 2.6.5.4 +========================== +** Bug + * [SPELL-599] - SPELL error : Lost connection with executor - Process crashed. + +** New Requirement + * [SPELL-590] - Retrieve and output current limit, msg ab algo version and time stamp and output it in SPELL + * [MPD-705] - Update Lookup_DataObject with Field fswpackage + +What's new in SPELL 2.6.5.3 +========================== +** Enhancement + * [FLEET-720] - Activation of ephemeral and Overlay limits from SPELL + * [SCO-2992] - Trigger TPU restart from SPELL script + +What's new in SPELL 2.6.5.2 +========================== +** Enhancement + * [SCO-3740] - SPELL prompt options after command failure + +What's new in SPELL 2.6.5.1 +========================== +** Bug + * [SES2223-190] - Spell instruction WaitFor cannot be interrupted + +What's new in SPELL 2.6.5 +========================== +** Bug + * [SPELL-582] - Avoid calling doStep when doSkip fails + +What's new in SPELL 2.6.4.3 +========================== +** Bug + * [SPELL-573] - SPELL Crash on Python 3 Abort() + +What's new in SPELL 2.6.4.2 +========================== +** Bug + * [SPELL-571] - Setting DUVs is only supported on the SES Mnemonic + +What's new in SPELL 2.6.4.1 +========================== +** Bug + * [SPELL-557] - SPELL Deadlock when Pausing a Procedure inside a WaitFor with TM checks + * [SPELL-560] - WaitFor TM instruction crashed upon REPEAT + * [SPELL-568] - FleetTM connector is reutrning 999999 as a raw value for DPs + + +** New Requirement + * [SPELL-565] - Ability to set the Spell Execution by STEP in the procedure + +What's new in SPELL 2.6.4 +========================== +** Bug + * [SPELL-554] - Fix SB4000 tduinfo global rewrite + + + ** New Requirement + * [SPELL-517] - SPELL procedure should be able to restore manually applied limits + +What's new in SPELL 2.6.3.3 +========================== +** Enhancement + * [SPELL-551] - Make SPELL driver more robust when accessing Scorpio variables + +What's new in SPELL 2.6.3.2 +========================== +** Hotfix + * [SPELL-549] - SPELL Unable to Read\Set Resources - Fix for endless loop when receiving bad data + +What's new in SPELL 2.6.3.1 +========================== +** Bug + * [SPELL-549] - SPELL Unable to Read\Set Resources + +What's new in SPELL 2.6.3 +========================== +** Bug + * [SPELL-539] - Send ping to Collector + * [SPELL-542] - SetGroundParameter can't be executed + * [SPELL-544] - SPELL did not send all commands during a PACE Bi-weekly operations (PR #261). + +What's new in SPELL 2.6.2.3 +========================== +** Bug + * [SPELL-544] - SPELL did not send all commands during a PACE Bi-weekly operations. + +What's new in SPELL 2.6.2.2 +========================== +** Bug + * [SPELL-537] - Properly handle Collector connection error and failures + * [SPELL-475] - Enable LoadDictionary to load Data from Pharos + +What's new in SPELL 2.6.2.1 +========================== +** Bug + * [SPELL-536] - SPELL SCE toggle error checking mistake success with error + + + ** New Requirement + * [SPELL-475] - Enable LoadDictionary to load Data from Pharos + +What's new in SPELL 2.6.2 +========================== + +** Bug + * [SPELL-505] - Unable to command after a TC failure + * [SPELL-512] - Deadlock caused by ping not receiving any data + + +** New Requirement + * [SPELL-516] - SPELL back-end to query for applied overlay limit files + +** Enhancement + * [SPELL-456] - GCS Enhancement Request - Automated Uplink Checks via SPELL + * [SPELL-484] - Raise an alarm (if possible) if procedure loses connection to context + * [SPELL-527] - Collector interfacing for SES20/21 + * [SPELL-533] - Add bzcesim collector node number to scorpio driver + +What's new in SPELL 2.6.1.1 +========================== + +** Bug + * [SPELL-513] - SPELL failure to command at P2 for AMC18 + +What's new in SPELL 2.6.1 +========================== + +** New Requirement + * [SPELL-457] - New message from SPELL to CWS to request VCC bump + * [SPELL-500] - New SPELL function to retrieve TM packets + +** Bug + * [SPELL-465] - Fix Python3 Memory lookup functions in Scorpio driver + * [SPELL-482] - SPELL crash during SN preparation + * [SPELL-489] - Python3: OpenFile with MODE=WRITE doesn't create a new file + * [SPELL-493] - IVARS and ARGS missing millisecond and microsecond + * [SPELL-496] - SPELL Context locks up when more then one service needs to get started + * [SPELL-506] - Missing import of TIME in the implementation of GetTMPackets makes the SPELL context crash + +** Enhancement + * [SPELL-426] - SPELL resource display (to check and set local TC options) + * [SPELL-455] - SES17: Improve CWS timeout message when TC packets are pending + * [SPELL-464] - Catch Common key/value parsing errors + * [SPELL-471] - Add M1/M2 node names + * [SPELL-473] - Add proto Git submodule + * [SPELL-494] - Improve SPELL Scorpio driver to make available exception messages to ARC + * [SPELL-507] - Meaningful error message on SCE toggle driver function error + * [SPELL-508] - Do not enforce the Collector availability for a procedure to run with mPower + * [SPELL-510] - Helper wrapping library to easily make GPS communication in Scorpio Driver + +What's new in SPELL 2.6.0.1 +========================== + +** Bug + * [SPELL-466] - SN1313: Process crashed during manoeuvre set up + +What's new in SPELL 2.6.0 +========================== + +** New Requirement + * [SPELL-337] - Make SPELL Python 3 compatible + * [SPELL-338] - Implement SES17 command capability + * [SPELL-344] - New SPELL functions to access Ground DTP Image + +** Bug + * [SPELL-281] - Don't do busy waiting while trying to receive an Ack in Scorpio driver + * [SPELL-340] - No audible for Spell Prompts + * [SPELL-356] - SES17 SetResource doesn't work and gives SPELL exeception + * [SPELL-357] - PACE alarm : Backup seat GE (g2anal02) not reachable: listener: login: send request: response timeout) + * [SPELL-358] - AMC07 & AMC08 SPELL Crash During BATMON Script + * [SPELL-359] - P.A3B.SEASON_MANAGEMENT procedure got stuck in the WaitFor statement + * [SPELL-365] - SPELL WaitFor TM crashes when there are issues + * [SPELL-373] - SPELl Service not copmpatible with Python3 + * [SPELL-377] - Prompt will cuase SPELL v3 to crash + * [SPELL-383] - GOTO doesn't work on Python 3 + * [SPELL-388] - Update STC.i for increased commanding buffer + * [SPELL-389] - Default sec/millis must be positive + * [SPELL-390] - Setting further TC options fails after encryption is set to ON + * [SPELL-392] - Python 3 breaks the Watch variables feature + * [SPELL-393] - SaveDictionary is not python 3 compatible + * [SPELL-394] - SPELL Warmstart crashes on throwing an excpetion within SPELL library code + * [SPELL-395] - SetUserAction crashes in Python 3 + * [SPELL-396] - SendDelay/ReleaseTime modifiers don't work + * [SPELL-398] - Group command sending fails + * [SPELL-399] - mPower - big command crashes procedure + * [SPELL-401] - The Dynamic GRPCThreadPool used for GRPC connections and the SPELL Service is not working in Python 3 + * [SPELL-403] - LoadExternalData doesn't work in new SPELL version + * [SPELL-406] - BuildTC command options are dropped in a command group of size one + * [SPELL-407] - SPELL-Driver start-up fails due to relative and missing imports + * [SPELL-413] - PACE: SPELL-Driver does not like relative parts + * [SPELL-430] - FleetTM output bus: With Wait=True, gRPC timeout does not work + * [SPELL-434] - SES17/mPower: Reset TC group after skip/cancel Prompt (Q, K, C) + * [SPELL-451] - Add missing sc= keyword in the GPS message + * [SPELL-460] - SES17 TC acceptance: n/a messges are not correctly handled resulting in timeouts + * [SPELL-463] - Long CWS messages crash Common client in the driver + +** Enhancement + * [SPELL-341] - FLEET - Enable SPELL access to PHAROS + * [SPELL-346] - Kill SPELL-Service as well when stopping SPELL + * [SPELL-348] - Add mission name to SPELL driver log file name + * [SPELL-354] - Add subschedule_id flag to SBNEO time tag command format + * [SPELL-370] - Add millisecond support for SES17 (SBNEO) platform + * [SPELL-371] - Make TC options and other ground resources local to the procedure + * [SPELL-372] - Update Scorpio TM Archiva interface to new Python3 module + * [SPELL-376] - Notify = False is not implemented for BuildTC + * [SPELL-385] - Teach mPower/sbneo drivers to use the convert_arguments_validate function + * [SPELL-391] - Add TC acceptance & verification flags to SBNEO + * [SPELL-429] - FleetTM output bus: Add gRPC request retries before throwing an exception + * [SPELL-433] - Check CWS request buffer size to avoid overflow in Common and STC + * [SPELL-453] - Refresh GPS connections after (long) commanding to avoid disconnections + * [SPELL-454] - Make Handle exception inherit from Exception diff --git a/SPELL Debug.launch b/SPELL Debug.launch new file mode 100644 index 0000000..7cd2f83 --- /dev/null +++ b/SPELL Debug.launch @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/azure_pipelines/ci_logrotate.yaml b/azure_pipelines/ci_logrotate.yaml new file mode 100644 index 0000000..2403643 --- /dev/null +++ b/azure_pipelines/ci_logrotate.yaml @@ -0,0 +1,139 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + + +trigger: + branches: + include: + - develop + paths: + include: + - azure_pipelines/variables/logrotate.yaml + - azure_pipelines/variables/baseline.yaml + - azure_pipelines/ci_logrotate.yaml + - docker/logrotate/** + + +name: "SPELL Logrotate CI Pipeline" + +pool: + name: weu1-d-keda + +variables: +- template: variables/baseline.yaml +- name: tags + value: "$(Build.DefinitionName)_$(Build.DefinitionVersion)_$(Build.BuildId)_$(Build.BuildNumber)" +- name: DOCKER_BUILDKIT + value: 1 +- name: NODE_TLS_REJECT_UNAUTHORIZED + value: 0 +- template: variables/logrotate.yaml +- group: nexus-auth +- name: NEXUS_CONTAINER_SCANNING_SCANNER_IMAGE + value: neuvector/scanner +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_URL + value: https://docker-weu.spo.internal.ses.com +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) + +stages: +- stage: + displayName: Nexus IQ Check + jobs: + - job: + displayName: Docker image + steps: + - task: Bash@3 + displayName: Extract Version + inputs: + targetType: inline + script: | + get_next_version() { + local increment_type="$1" + local RE='([0-9]*)[.]([0-9]*)[.]([0-9]*)([.-][0-9A-Za-z.-]+)?' + + if [ -z "$increment_type" ]; then + increment_type="patch" + fi + if [[ "$2" =~ $RE ]]; then + local MAJOR="${BASH_REMATCH[1]}" + local MINOR="${BASH_REMATCH[2]}" + local PATCH="${BASH_REMATCH[3]}" + local pre="" + else + local MAJOR=0 + local MINOR=0 + local PATCH=0 + local pre="" + fi + + case "$increment_type" in + major) + ((MAJOR += 1)) + ((MINOR = 0)) + ((PATCH = 0)) + ;; + minor) + ((MINOR += 1)) + ((PATCH = 0)) + ;; + patch) + ((PATCH += 1)) + ;; + esac + + local NEXT_VERSION="$MAJOR.$MINOR.$PATCH" + echo "$NEXT_VERSION" + } + + HEAD=`git rev-parse --short HEAD` && echo HEAD: ${HEAD} + VERSION_TAG=`git describe --abbrev=0 --tags ${HEAD} 2>/dev/null || true` && echo VERSION_TAG: ${VERSION_TAG} + TAG_COMMIT=`git rev-list -n 1 --abbrev-commit ${VERSION_TAG}` && echo TAG_COMMIT: ${TAG_COMMIT} + if [[ "$HEAD" != "$TAG_COMMIT" ]] + then + VERSION_TAG=`get_next_version patch ${VERSION_TAG}` + fi + echo "##vso[task.setvariable variable=VERSION_TAG]$VERSION_TAG" + echo "ADO Tag: $(tags)" + echo "New Version to be built: ${VERSION_TAG}" + + workingDirectory: "$(system.defaultWorkingDirectory)/" + - task: Docker@2 + displayName: Build + inputs: + command: build + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + buildContext: "**$(DOCKER_BUILD_CONTEXT)" + Dockerfile: "$(DOCKER_FILEPATH)/Dockerfile" + tags: "$(VERSION_TAG)" + arguments: "--build-arg RUNTIME_IMAGE=$(RUNTIME_IMAGE) --build-arg BUILD_IMAGE=$(BUILD_IMAGE) --build-arg http_proxy=$(PROXY) --build-arg https_proxy=$(PROXY) --build-arg no_proxy=$(NO_PROXY)" + - task: Docker@2 + displayName: Push + inputs: + command: push + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + tags: "$(VERSION_TAG)" + - task: NexusIqPipelineTask@1 + continueOnError: true + env: + NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + inputs: + nexusIqService: 'nexus-iq' + organizationId: 'b57c0bd6dfd247fa964dbc4cbfdf2f2a' + applicationId: '$(NEXUS_APPLICATION)' + stage: 'Build' + scanTargets: 'container:https://$(NEXUS_REGISTRY_URL)/$(DOCKER_IMAGE):$(VERSION_TAG)' + enableDebugLogging: true + ignoreSystemError: true + ignoreScanningError: true \ No newline at end of file diff --git a/azure_pipelines/ci_spell_driver_scorpio.yaml b/azure_pipelines/ci_spell_driver_scorpio.yaml new file mode 100644 index 0000000..0a29170 --- /dev/null +++ b/azure_pipelines/ci_spell_driver_scorpio.yaml @@ -0,0 +1,139 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: + branches: + include: + - develop + paths: + include: + - azure_pipelines/variables/spell_driver_scorpio.yaml + - azure_pipelines/variables/baseline.yaml + - azure_pipelines/ci_spell_driver_scorpio.yaml + - drivers/scorpio/** + - docker/drivers/scorpio/** + - include/** + +name: "SPELL Scorpio Driver CI Pipeline" + +pool: + name: weu1-d-keda + +variables: +- template: variables/baseline.yaml +- name: tags + value: "$(Build.DefinitionName)_$(Build.DefinitionVersion)_$(Build.BuildId)_$(Build.BuildNumber)" +- name: DOCKER_BUILDKIT + value: 1 +- name: NODE_TLS_REJECT_UNAUTHORIZED + value: 0 +- template: variables/spell_driver_scorpio.yaml +- group: nexus-auth +- name: NEXUS_CONTAINER_SCANNING_SCANNER_IMAGE + value: neuvector/scanner +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_URL + value: https://docker-weu.spo.internal.ses.com +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) + +stages: +- stage: + displayName: Nexus IQ Check + jobs: + - job: + displayName: Docker image + steps: + - task: Bash@3 + displayName: Extract Version + inputs: + targetType: inline + script: | + get_next_version() { + local increment_type="$1" + local RE='([0-9]*)[.]([0-9]*)[.]([0-9]*)([.-][0-9A-Za-z.-]+)?' + + if [ -z "$increment_type" ]; then + increment_type="patch" + fi + if [[ "$2" =~ $RE ]]; then + local MAJOR="${BASH_REMATCH[1]}" + local MINOR="${BASH_REMATCH[2]}" + local PATCH="${BASH_REMATCH[3]}" + local pre="" + else + local MAJOR=0 + local MINOR=0 + local PATCH=0 + local pre="" + fi + + case "$increment_type" in + major) + ((MAJOR += 1)) + ((MINOR = 0)) + ((PATCH = 0)) + ;; + minor) + ((MINOR += 1)) + ((PATCH = 0)) + ;; + patch) + ((PATCH += 1)) + ;; + esac + + local NEXT_VERSION="$MAJOR.$MINOR.$PATCH" + echo "$NEXT_VERSION" + } + + HEAD=`git rev-parse --short HEAD` && echo HEAD: ${HEAD} + VERSION_TAG=`git describe --abbrev=0 --tags ${HEAD} 2>/dev/null || true` && echo VERSION_TAG: ${VERSION_TAG} + TAG_COMMIT=`git rev-list -n 1 --abbrev-commit ${VERSION_TAG}` && echo TAG_COMMIT: ${TAG_COMMIT} + if [[ "$HEAD" != "$TAG_COMMIT" ]] + then + VERSION_TAG=`get_next_version patch ${VERSION_TAG}` + fi + echo "##vso[task.setvariable variable=VERSION_TAG]$VERSION_TAG" + echo "ADO Tag: $(tags)" + echo "New Version to be built: ${VERSION_TAG}" + + workingDirectory: "$(system.defaultWorkingDirectory)/" + - task: Docker@2 + displayName: Build + inputs: + command: build + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + buildContext: "**$(DOCKER_BUILD_CONTEXT)" + Dockerfile: "$(DOCKER_FILEPATH)/Dockerfile" + tags: "$(VERSION_TAG)" + arguments: "--build-arg RUNTIME_IMAGE=$(RUNTIME_IMAGE) --build-arg BUILD_IMAGE=$(BUILD_IMAGE) --build-arg http_proxy=$(PROXY) --build-arg https_proxy=$(PROXY) --build-arg no_proxy=$(NO_PROXY)" + - task: Docker@2 + displayName: Push + inputs: + command: push + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + tags: "$(VERSION_TAG)" + - task: NexusIqPipelineTask@1 + continueOnError: true + env: + NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + inputs: + nexusIqService: 'nexus-iq' + organizationId: 'b57c0bd6dfd247fa964dbc4cbfdf2f2a' + applicationId: '$(NEXUS_APPLICATION)' + stage: 'Build' + scanTargets: 'container:https://$(NEXUS_REGISTRY_URL)/$(DOCKER_IMAGE):$(VERSION_TAG)' + enableDebugLogging: true + ignoreSystemError: true + ignoreScanningError: true diff --git a/azure_pipelines/ci_spell_registry.yaml b/azure_pipelines/ci_spell_registry.yaml new file mode 100644 index 0000000..4e378ed --- /dev/null +++ b/azure_pipelines/ci_spell_registry.yaml @@ -0,0 +1,158 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + + +trigger: + branches: + include: + - develop + paths: + include: + - azure_pipelines/variables/spell_registry.yaml + - azure_pipelines/variables/baseline.yaml + - azure_pipelines/ci_spell_registry.yaml + - docker/spell-registry/** + +name: "SPELL Registry CI Pipeline" + +pool: + name: weu1-d-keda + +variables: +- template: variables/baseline.yaml +- name: tags + value: "$(Build.DefinitionName)_$(Build.DefinitionVersion)_$(Build.BuildId)_$(Build.BuildNumber)" +- name: DOCKER_BUILDKIT + value: 1 +- name: NODE_TLS_REJECT_UNAUTHORIZED + value: 0 +- template: variables/spell_registry.yaml +- group: nexus-auth +- name: NEXUS_CONTAINER_SCANNING_SCANNER_IMAGE + value: neuvector/scanner +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_URL + value: https://docker-weu.spo.internal.ses.com +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) + +stages: +- stage: + displayName: Nexus IQ Check + jobs: + - job: + displayName: Docker image + steps: + - task: Bash@3 + displayName: Extract Version + inputs: + targetType: inline + script: | + get_next_version() { + local increment_type="$1" + local RE='v?[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)\([0-9A-Za-z-]*\)' + + if [ -z "$increment_type" ]; then + increment_type="patch" + fi + + local base=$(node -pe 'require("./package.json")["version"]') + + local MAJOR=$(echo $base | sed -e "s#$RE#\1#") + local MINOR=$(echo $base | sed -e "s#$RE#\2#") + local PATCH=$(echo $base | sed -e "s#$RE#\3#") + local HOTFIX=$(echo $base | sed -e "s#$RE#\4#") + + case "$increment_type" in + major) + ((MAJOR += 1)) + ((MINOR = 0)) + ((PATCH = 0)) + ((HOTFIX = 0)) + ;; + minor) + ((MINOR += 1)) + ((PATCH = 0)) + ((HOTFIX = 0)) + ;; + patch) + ((PATCH += 1)) + ((HOTFIX = 0)) + ;; + hotfix) + ((HOTFIX += 1)) + ;; + esac + + local NEXT_VERSION="$MAJOR.$MINOR.$PATCH.$HOTFIX" + echo "$NEXT_VERSION" + } + + HEAD=`git rev-parse --short HEAD` && echo HEAD: ${HEAD} + VERSION_TAG=`git describe --abbrev=0 --tags ${HEAD} 2>/dev/null || true` && echo VERSION_TAG: ${VERSION_TAG} + TAG_COMMIT=`git rev-list -n 1 --abbrev-commit ${VERSION_TAG}` && echo TAG_COMMIT: ${TAG_COMMIT} + if [[ "$HEAD" != "$TAG_COMMIT" ]] + then + VERSION_TAG=${VERSION_TAG}-dev + fi + echo "##vso[task.setvariable variable=VERSION_TAG]$VERSION_TAG" + echo "ADO Tag: $(tags)" + + workingDirectory: "$(system.defaultWorkingDirectory)/" + - task: Docker@2 + inputs: + containerRegistry: 'nexus-docker-weu' + command: 'login' + - task: Bash@3 + displayName: Build + inputs: + targetType: inline + script: | + export HTTP_PROXY=$(PROXY) + export HTTPS_PROXY=$(PROXY) + export NO_PROXY=$(NO_PROXY) + cd $(DOCKER_FILEPATH) + ls -al + echo $(PWD) + docker pull docker-weu.spo.internal.ses.com/eclipse-temurin:19.0.1_10-jre-alpine + docker run --rm -e VERSION=$(VERSION_TAG) -e http_proxy=$(PROXY) -e https_proxy=$(PROXY) -e no_proxy=$(NO_PROXY) -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v `pwd`:/app -w /app $(BUILD_IMAGE) sbt docker:publishLocal + + workingDirectory: "$(system.defaultWorkingDirectory)/" + + # - task: Docker@2 + # displayName: Build + # inputs: + # command: build + # repository: "$(DOCKER_IMAGE)" + # containerRegistry: "$(NEXUS_REGISTRY)" + # buildContext: "**$(DOCKER_BUILD_CONTEXT)" + # Dockerfile: "$(DOCKER_FILEPATH)/Dockerfile" + # tags: "$(VERSION_TAG)" + # arguments: "--build-arg http_proxy=$(PROXY) --build-arg https_proxy=$(PROXY) --build-arg no_proxy=$(NO_PROXY)" + - task: Docker@2 + displayName: Push + inputs: + command: push + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + tags: "$(VERSION_TAG)" + - task: NexusIqPipelineTask@1 + env: + NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + inputs: + nexusIqService: 'nexus-iq' + organizationId: 'b57c0bd6dfd247fa964dbc4cbfdf2f2a' + applicationId: '$(NEXUS_APPLICATION)' + stage: 'Build' + scanTargets: 'container:https://$(NEXUS_REGISTRY_URL)/$(DOCKER_IMAGE):$(VERSION_TAG)' + enableDebugLogging: true + ignoreSystemError: true + ignoreScanningError: true diff --git a/azure_pipelines/ci_spell_server.yaml b/azure_pipelines/ci_spell_server.yaml new file mode 100644 index 0000000..3b1f681 --- /dev/null +++ b/azure_pipelines/ci_spell_server.yaml @@ -0,0 +1,147 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + +trigger: + branches: + include: + - develop + paths: + include: + - azure_pipelines/variables/spell_server.yaml + - azure_pipelines/variables/baseline.yaml + - azure_pipelines/ci_spell_server.yaml + - lib/** + - src/** + - include/** + - configure.ac + - RELEASENOTES + - build_spell_ubuntu.sh + - docker/spell-server/** + - proto/** + - protos/** + + +name: "SPELL Server CI Pipeline" + +pool: + name: weu1-d-keda + +variables: +- template: variables/baseline.yaml +- name: tags + value: "$(Build.DefinitionName)_$(Build.DefinitionVersion)_$(Build.BuildId)_$(Build.BuildNumber)" +- name: DOCKER_BUILDKIT + value: 1 +- name: NODE_TLS_REJECT_UNAUTHORIZED + value: 0 +- template: variables/spell_server.yaml +- group: nexus-auth +- name: NEXUS_CONTAINER_SCANNING_SCANNER_IMAGE + value: neuvector/scanner +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_URL + value: https://docker-weu.spo.internal.ses.com +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) + + +stages: +- stage: + displayName: Nexus IQ Check + jobs: + - job: + displayName: Docker image + steps: + - task: Bash@3 + displayName: Extract Version + inputs: + targetType: inline + script: | + get_next_version() { + local increment_type="$1" + local RE='([0-9]*)[.]([0-9]*)[.]([0-9]*)([.-][0-9A-Za-z.-]+)?' + + if [ -z "$increment_type" ]; then + increment_type="patch" + fi + if [[ "$2" =~ $RE ]]; then + local MAJOR="${BASH_REMATCH[1]}" + local MINOR="${BASH_REMATCH[2]}" + local PATCH="${BASH_REMATCH[3]}" + local pre="" + else + local MAJOR=0 + local MINOR=0 + local PATCH=0 + local pre="" + fi + + case "$increment_type" in + major) + ((MAJOR += 1)) + ((MINOR = 0)) + ((PATCH = 0)) + ;; + minor) + ((MINOR += 1)) + ((PATCH = 0)) + ;; + patch) + ((PATCH += 1)) + ;; + esac + + local NEXT_VERSION="$MAJOR.$MINOR.$PATCH" + echo "$NEXT_VERSION" + } + + HEAD=`git rev-parse --short HEAD` && echo HEAD: ${HEAD} + VERSION_TAG=`git describe --abbrev=0 --tags ${HEAD} 2>/dev/null || true` && echo VERSION_TAG: ${VERSION_TAG} + TAG_COMMIT=`git rev-list -n 1 --abbrev-commit ${VERSION_TAG}` && echo TAG_COMMIT: ${TAG_COMMIT} + if [[ "$HEAD" != "$TAG_COMMIT" ]] + then + VERSION_TAG=`get_next_version patch ${VERSION_TAG}` + fi + echo "##vso[task.setvariable variable=VERSION_TAG]$VERSION_TAG" + echo "ADO Tag: $(tags)" + echo "New Version to be built: ${VERSION_TAG}" + + workingDirectory: "$(system.defaultWorkingDirectory)/" + - task: Docker@2 + displayName: Build + inputs: + command: build + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + buildContext: "**$(DOCKER_BUILD_CONTEXT)" + Dockerfile: "$(DOCKER_FILEPATH)/Dockerfile" + tags: "$(VERSION_TAG)" + arguments: "--build-arg RUNTIME_IMAGE=$(RUNTIME_IMAGE) --build-arg VERSION_TAG=$(VERSION_TAG) --build-arg BUILD_IMAGE=$(BUILD_IMAGE) --build-arg http_proxy=$(PROXY) --build-arg https_proxy=$(PROXY) --build-arg no_proxy=$(NO_PROXY)" + - task: Docker@2 + displayName: Push + inputs: + command: push + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + tags: "$(VERSION_TAG)" + - task: NexusIqPipelineTask@1 + continueOnError: true + env: + NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + inputs: + nexusIqService: 'nexus-iq' + organizationId: 'b57c0bd6dfd247fa964dbc4cbfdf2f2a' + applicationId: '$(NEXUS_APPLICATION)' + stage: 'Build' + scanTargets: 'container:https://$(NEXUS_REGISTRY_URL)/$(DOCKER_IMAGE):$(VERSION_TAG)' + enableDebugLogging: true + ignoreSystemError: true + ignoreScanningError: true diff --git a/azure_pipelines/ci_spell_ssh.yaml b/azure_pipelines/ci_spell_ssh.yaml new file mode 100644 index 0000000..50f3b6f --- /dev/null +++ b/azure_pipelines/ci_spell_ssh.yaml @@ -0,0 +1,139 @@ +# Starter pipeline +# Start with a minimal pipeline that you can customize to build and deploy your code. +# Add steps that build, run tests, deploy, and more: +# https://aka.ms/yaml + + +trigger: + branches: + include: + - develop + paths: + include: + - azure_pipelines/variables/spell_ssh.yaml + - azure_pipelines/variables/baseline.yaml + - azure_pipelines/ci_spell_ssh.yaml + - docker/ssh-server/** + + +name: "SPELL SSH Server CI Pipeline" + +pool: + name: weu1-d-keda + +variables: +- template: variables/baseline.yaml +- name: tags + value: "$(Build.DefinitionName)_$(Build.DefinitionVersion)_$(Build.BuildId)_$(Build.BuildNumber)" +- name: DOCKER_BUILDKIT + value: 1 +- name: NODE_TLS_REJECT_UNAUTHORIZED + value: 0 +- template: variables/spell_ssh.yaml +- group: nexus-auth +- name: NEXUS_CONTAINER_SCANNING_SCANNER_IMAGE + value: neuvector/scanner +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_URL + value: https://docker-weu.spo.internal.ses.com +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_USER + value: $(NEXUS_USER) +- name: NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD + value: $(NEXUS_PASSWORD) + +stages: +- stage: + displayName: Nexus IQ Check + jobs: + - job: + displayName: Docker image + steps: + - task: Bash@3 + displayName: Extract Version + inputs: + targetType: inline + script: | + get_next_version() { + local increment_type="$1" + local RE='([0-9]*)[.]([0-9]*)[.]([0-9]*)([.-][0-9A-Za-z.-]+)?' + + if [ -z "$increment_type" ]; then + increment_type="patch" + fi + if [[ "$2" =~ $RE ]]; then + local MAJOR="${BASH_REMATCH[1]}" + local MINOR="${BASH_REMATCH[2]}" + local PATCH="${BASH_REMATCH[3]}" + local pre="" + else + local MAJOR=0 + local MINOR=0 + local PATCH=0 + local pre="" + fi + + case "$increment_type" in + major) + ((MAJOR += 1)) + ((MINOR = 0)) + ((PATCH = 0)) + ;; + minor) + ((MINOR += 1)) + ((PATCH = 0)) + ;; + patch) + ((PATCH += 1)) + ;; + esac + + local NEXT_VERSION="$MAJOR.$MINOR.$PATCH" + echo "$NEXT_VERSION" + } + + HEAD=`git rev-parse --short HEAD` && echo HEAD: ${HEAD} + VERSION_TAG=`git describe --abbrev=0 --tags ${HEAD} 2>/dev/null || true` && echo VERSION_TAG: ${VERSION_TAG} + TAG_COMMIT=`git rev-list -n 1 --abbrev-commit ${VERSION_TAG}` && echo TAG_COMMIT: ${TAG_COMMIT} + if [[ "$HEAD" != "$TAG_COMMIT" ]] + then + VERSION_TAG=`get_next_version patch ${VERSION_TAG}` + fi + echo "##vso[task.setvariable variable=VERSION_TAG]$VERSION_TAG" + echo "ADO Tag: $(tags)" + echo "New Version to be built: ${VERSION_TAG}" + + workingDirectory: "$(system.defaultWorkingDirectory)/" + - task: Docker@2 + displayName: Build + inputs: + command: build + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + buildContext: "**$(DOCKER_BUILD_CONTEXT)" + Dockerfile: "$(DOCKER_FILEPATH)/Dockerfile" + tags: "$(VERSION_TAG)" + arguments: "--build-arg RUNTIME_IMAGE=$(RUNTIME_IMAGE) --build-arg BUILD_IMAGE=$(BUILD_IMAGE) --build-arg http_proxy=$(PROXY) --build-arg https_proxy=$(PROXY) --build-arg no_proxy=$(NO_PROXY)" + - task: Docker@2 + displayName: Push + inputs: + command: push + repository: "$(DOCKER_IMAGE)" + containerRegistry: "$(NEXUS_REGISTRY)" + tags: "$(VERSION_TAG)" + - task: NexusIqPipelineTask@1 + continueOnError: true + env: + NEXUS_CONTAINER_IMAGE_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + NEXUS_CONTAINER_SCANNING_REGISTRY_PASSWORD: $(NEXUS_PASSWORD) + inputs: + nexusIqService: 'nexus-iq' + organizationId: 'b57c0bd6dfd247fa964dbc4cbfdf2f2a' + applicationId: '$(NEXUS_APPLICATION)' + stage: 'Build' + scanTargets: 'container:https://$(NEXUS_REGISTRY_URL)/$(DOCKER_IMAGE):$(VERSION_TAG)' + enableDebugLogging: true + ignoreSystemError: true + ignoreScanningError: true \ No newline at end of file diff --git a/azure_pipelines/variables/baseline.yaml b/azure_pipelines/variables/baseline.yaml new file mode 100644 index 0000000..42845a7 --- /dev/null +++ b/azure_pipelines/variables/baseline.yaml @@ -0,0 +1,8 @@ +variables: + NEXUS_REGISTRY: nexus-docker-weu + NEXUS_REGISTRY_URL: docker-weu.spo.internal.ses.com + BUILD_IMAGE: docker-weu.spo.internal.ses.com/spell/spell-cots-ubuntu-build:1.0.1 + RUNTIME_IMAGE: docker-weu.spo.internal.ses.com/spell/spell-cots-ubuntu-runtime:1.0.1 + PROXY: "${HTTPS_PROXY}" + NO_PROXY: "${NO_PROXY}" + \ No newline at end of file diff --git a/azure_pipelines/variables/logrotate.yaml b/azure_pipelines/variables/logrotate.yaml new file mode 100644 index 0000000..1db6d1f --- /dev/null +++ b/azure_pipelines/variables/logrotate.yaml @@ -0,0 +1,5 @@ +variables: + DOCKER_FILEPATH: docker/logrotate + DOCKER_IMAGE: spell/logrotate-ubuntu + NEXUS_APPLICATION: spell-logrotate + DOCKER_BUILD_CONTEXT: "" \ No newline at end of file diff --git a/azure_pipelines/variables/spell_driver_scorpio.yaml b/azure_pipelines/variables/spell_driver_scorpio.yaml new file mode 100644 index 0000000..26d1700 --- /dev/null +++ b/azure_pipelines/variables/spell_driver_scorpio.yaml @@ -0,0 +1,5 @@ +variables: + DOCKER_FILEPATH: docker/drivers/scorpio + DOCKER_IMAGE: spell/spell-driver-ubuntu-scorpio + NEXUS_APPLICATION: spell-scorpio-driver + DOCKER_BUILD_CONTEXT: "/../" \ No newline at end of file diff --git a/azure_pipelines/variables/spell_registry.yaml b/azure_pipelines/variables/spell_registry.yaml new file mode 100644 index 0000000..627aec2 --- /dev/null +++ b/azure_pipelines/variables/spell_registry.yaml @@ -0,0 +1,8 @@ +variables: + DOCKER_FILEPATH: docker/spell-registry + DOCKER_IMAGE: spell/spell-registry + NEXUS_APPLICATION: spell-registry + DOCKER_BUILD_CONTEXT: "." + BUILD_IMAGE: docker-weu.spo.internal.ses.com/sbtscala/scala-sbt:eclipse-temurin-19_36_1.8.0_3.2.1 + #scala-sbt:eclipse-temurin-focal-17.0.10_7_1.9.9_3.4.0 + \ No newline at end of file diff --git a/azure_pipelines/variables/spell_server.yaml b/azure_pipelines/variables/spell_server.yaml new file mode 100644 index 0000000..b359dc8 --- /dev/null +++ b/azure_pipelines/variables/spell_server.yaml @@ -0,0 +1,5 @@ +variables: + DOCKER_FILEPATH: docker/spell-server + DOCKER_IMAGE: spell/spell-server-ubuntu + NEXUS_APPLICATION: spell-server + DOCKER_BUILD_CONTEXT: "/../" \ No newline at end of file diff --git a/azure_pipelines/variables/spell_ssh.yaml b/azure_pipelines/variables/spell_ssh.yaml new file mode 100644 index 0000000..29aebca --- /dev/null +++ b/azure_pipelines/variables/spell_ssh.yaml @@ -0,0 +1,5 @@ +variables: + DOCKER_FILEPATH: docker/ssh-server + DOCKER_IMAGE: spell/ssh-server-ubuntu + NEXUS_APPLICATION: spell-ssh-server + DOCKER_BUILD_CONTEXT: "" \ No newline at end of file diff --git a/build_driver.sh b/build_driver.sh new file mode 100644 index 0000000..1a3c113 --- /dev/null +++ b/build_driver.sh @@ -0,0 +1,59 @@ +#!/bin/bash + +function die() { + echo -e >&2 "ERROR: $@" + echo "Usage: $(basename $0) install-path hifly|scorpio|fuzzer" + exit 1 +} + +# sanity checks +[ $# -ge 2 ] || die "Invalid arguments.\n\ +Please specify installation path and the driver." +SPELL_HOME=$1 +[ -d $SPELL_HOME ] || die "cannot find the given installation directory: $SPELL_HOME" + +shift +DRIVER=$1 +[[ "$DRIVER" == "hifly" ]] || [[ "$DRIVER" == "scorpio" ]] || [[ "$DRIVER" == "fuzzer" ]] || [[ "$DRIVER" == "example" ]] || die "Unknown driver \"$DRIVER\"" +shift +DEPS=$1 +shift + +! [ -z $SPELL_COTS ] || die "Set SPELL_COTS environment variable" +[ -d $SPELL_COTS ] || die "cannot find the SPELL_COTS directory: $SPELL_COTS" +[ "$(ls -A $SPELL_COTS)" ] || die "SPELL_COTS directory $SPELL_COTS is empty" + +export LD_LIBRARY_PATH=$SPELL_COTS/lib +export PATH=$SPELL_COTS/bin:$PATH + + +if [[ "$DRIVER" == "hifly" ]]; then + DRIVER="--enable-hiflydriver --with-exif=6" + DRIVER_FOLDER="hifly2" +elif [[ "$DRIVER" == "scorpio" ]]; then + DRIVER="--enable-scorpiodriver" + DRIVER_FOLDER="scorpio" +elif [[ "$DRIVER" == "fuzzer" ]]; then + DRIVER="--enable-fuzzerdriver" + DRIVER_FOLDER="fuzzer" +elif [[ "$DRIVER" == "example" ]]; then + DRIVER="--enable-exampledriver" + DRIVER_FOLDER="example" +fi + +autoreconf -fi .|| die "autoreconf failed!" +mkdir -p build +rm -rf build/* > /dev/null 2>&1 +cd build +set -x +LSAN_OPTIONS=verbosity=1:log_threads=1 ASAN_OPTIONS=halt_on_error=0 ../configure --disable-dependency-tracking --enable-address-sanitizer --prefix=$SPELL_HOME $DRIVER --enable-pace CPPFLAGS="-w -I$SPELL_COTS/include $@" LDFLAGS="-Wl,--copy-dt-needed-entries -L$SPELL_COTS/lib" + +for dep in ${DEPS}; do + pushd lib/${dep} + make -j4 -s && make install + popd +done + +cd drivers/${DRIVER_FOLDER} +set +x +make -j4 -s && make install diff --git a/build_spell.sh b/build_spell.sh new file mode 100644 index 0000000..fb5099a --- /dev/null +++ b/build_spell.sh @@ -0,0 +1,53 @@ +#!/bin/bash + +function die() { + echo -e >&2 "ERROR: $@" + echo "Usage: $(basename $0) install-path hifly|scorpio|fuzzer" + exit 1 +} + +# sanity checks +[ $# -ge 2 ] || die "Invalid arguments.\n\ +Please specify installation path and the driver." +SPELL_HOME=$1 +[ -d $SPELL_HOME ] || die "cannot find the given installation directory: $SPELL_HOME" +! [ "$(ls -A $SPELL_HOME)" ] || die "installation directory $SPELL_HOME must be empty" +shift +DRIVER=$1 +[[ "$DRIVER" == "hifly" ]] || [[ "$DRIVER" == "scorpio" ]] || [[ "$DRIVER" == "fuzzer" ]] || [[ "$DRIVER" == "none" ]] || die "Unknown driver \"$DRIVER\"" +shift + +! [ -z $SPELL_COTS ] || die "Set SPELL_COTS environment variable" +[ -d $SPELL_COTS ] || die "cannot find the SPELL_COTS directory: $SPELL_COTS" +[ "$(ls -A $SPELL_COTS)" ] || die "SPELL_COTS directory $SPELL_COTS is empty" + +export LD_LIBRARY_PATH=$SPELL_COTS/lib +export PATH=$SPELL_COTS/bin:$PATH + +export CC="gcc -m32" +export CXX="g++ -m32" + +if [[ "$DRIVER" == "hifly" ]]; then + DRIVER="--enable-hiflydriver --with-exif=6" +elif [[ "$DRIVER" == "scorpio" ]]; then + DRIVER="--enable-scorpiodriver" +elif [[ "$DRIVER" == "fuzzer" ]]; then + DRIVER="--enable-fuzzerdriver" +elif [[ "$DRIVER" == "none" ]]; then + DRIVER="" +fi + +git submodule init +git submodule update +cd proto +git checkout develop +cd .. + +autoreconf -fi +mkdir -p build +rm -rf build/* > /dev/null 2>&1 +cd build +set -x +../configure --prefix=$SPELL_HOME $DRIVER --enable-pace CPPFLAGS="-std=c++11 -I$SPELL_COTS/include $@" LDFLAGS="-L$SPELL_COTS/lib" +set +x +make -j4 -s && make install diff --git a/build_spell_ubuntu.sh b/build_spell_ubuntu.sh new file mode 100644 index 0000000..f80f2db --- /dev/null +++ b/build_spell_ubuntu.sh @@ -0,0 +1,49 @@ +#!/bin/bash + +function die() { + echo -e >&2 "ERROR: $@" + echo "Usage: $(basename $0) install-path hifly|scorpio|fuzzer" + exit 1 +} + +# sanity checks +[ $# -ge 2 ] || die "Invalid arguments.\n\ +Please specify installation path and the driver." +SPELL_HOME=$1 +[ -d $SPELL_HOME ] || die "cannot find the given installation directory: $SPELL_HOME" +! [ "$(ls -A $SPELL_HOME)" ] || die "installation directory $SPELL_HOME must be empty" +shift +DRIVER=$1 +[[ "$DRIVER" == "hifly" ]] || [[ "$DRIVER" == "scorpio" ]] || [[ "$DRIVER" == "fuzzer" ]] || [[ "$DRIVER" == "none" ]] || die "Unknown driver \"$DRIVER\"" +shift + +! [ -z $SPELL_COTS ] || die "Set SPELL_COTS environment variable" +[ -d $SPELL_COTS ] || die "cannot find the SPELL_COTS directory: $SPELL_COTS" +[ "$(ls -A $SPELL_COTS)" ] || die "SPELL_COTS directory $SPELL_COTS is empty" + +export LD_LIBRARY_PATH=$SPELL_COTS/lib +export PATH=$SPELL_COTS/bin:$PATH + +if [[ "$DRIVER" == "hifly" ]]; then + DRIVER="--enable-hiflydriver --with-exif=6" +elif [[ "$DRIVER" == "scorpio" ]]; then + DRIVER="--enable-scorpiodriver" +elif [[ "$DRIVER" == "fuzzer" ]]; then + DRIVER="--enable-fuzzerdriver" +elif [[ "$DRIVER" == "none" ]]; then + DRIVER="" +fi + +mkdir -p build +rm -rf build/* > /dev/null 2>&1 + + +autoreconf -fi . || die "autoconf failed" + +cd build +set -x +../configure --enable-address-sanitizer --prefix=$SPELL_HOME $DRIVER --enable-pace CPPFLAGS="-I$SPELL_COTS/include -fno-omit-frame-pointer $@" LDFLAGS="-rdynamic -ldw -Wl,--copy-dt-needed-entries -L$SPELL_COTS/lib" || die "configure failed" +set +x +make -j4 -s || die "build failed" + +make install || die "install failed" diff --git a/common.mk b/common.mk index e9701b6..b0d85e1 100644 --- a/common.mk +++ b/common.mk @@ -1,11 +1,11 @@ ################################################################################### ## FILE : common.mk -## DATE : Mar 17, 2011 +## DATE : May 11, 2017 ## PROJECT : SPELL -## DESCRIPTION: Automake rules +## DESCRIPTION: Common compilation and linking flags ## -------------------------------------------------------------------------------- ## -## Copyright (C) 2008, 2012 SES ENGINEERING, Luxembourg S.A.R.L. +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. ## ## This file is part of SPELL. ## @@ -23,10 +23,7 @@ ## along with SPELL. If not, see . ## ################################################################################### - -AUTOMAKE_OPTIONS=foreign -#REMOVED -O2 -AM_CPPFLAGS = -I$(top_srcdir)/include -I${SPELL_COTS}/include $(PYTHON_CPPFLAGS) -I${WITH_LOG4CPLUS}/include -fPIC -fno-strict-aliasing -g3 -Wall -c -fmessage-length=0 -MMD -MP -AM_LDFLAGS = $(PYTHON_LDFLAGS) $(PYTHON_EXTRA_LIBS) $(PYTHON_EXTRA_LDFLAGS) -L/usr/local/lib -L${WITH_LOG4CPLUS}/lib -llog4cplus -RSYNC=rsync -av --exclude-from=${top_srcdir}/exclude.list - +AUTOMAKE_OPTIONS = subdir-objects foreign +AM_CPPFLAGS = -I${top_srcdir}/include -Wall -fno-strict-aliasing -fmessage-length=0 -MMD +AM_LDFLAGS = ${LIBS} -lxerces-c -lz -lrt -llog4cplus +RELEASE_LDFLAG = -release ${PACKAGE_VERSION} diff --git a/config/Makefile.am b/config/Makefile.am index ba52126..76b24e8 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -1,53 +1,29 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ -include ${top_srcdir}/common.mk +SRC=${srcdir} +DST=${exec_prefix}/config install-exec-hook: - mkdir -p ${exec_prefix}/config -if HAVE_RSYNC -if WITH_LIBS - $(RSYNC) ${top_srcdir}/config/server ${exec_prefix}/config/ - $(RSYNC) ${top_srcdir}/config/contexts ${exec_prefix}/config/ -endif - $(RSYNC) ${top_srcdir}/config/spell ${exec_prefix}/config/ -if WITH_GUI - $(RSYNC) ${top_srcdir}/config/gui ${exec_prefix}/config/ -endif - -else - -if WITH_LIBS - cp -r ${top_srcdir}/config/server ${exec_prefix}/config/. - cp -r ${top_srcdir}/config/contexts ${exec_prefix}/config/. -endif - cp -r ${top_srcdir}/config/spell ${exec_prefix}/config/. -if WITH_GUI - cp -r ${top_srcdir}/config/gui ${exec_prefix}/config/. -endif - find ${exec_prefix}/config -name ".svn" | xargs rm -rf -endif - + mkdir -p ${DST} + cp -ur ${SRC}/contexts ${DST} + cp -ur ${SRC}/server ${DST} + cp -ur ${SRC}/spell ${DST} diff --git a/config/contexts/context_example.xml b/config/contexts/context_example.xml new file mode 100644 index 0000000..61be975 --- /dev/null +++ b/config/contexts/context_example.xml @@ -0,0 +1,83 @@ + + + + + + Example context + Exmaple context description + + + + + example + + + + + Y + Spacecraft + X + N/A + + + + + $SPELL_DATA/Procedures + $SPELL_DATA/UserLib + $SPELL_DATA + $SPELL_DATA + + + + + + + + + + + + 0 + 30 + True + False + False + 5 + + DISABLED + DISABLED + DISABLED + False + + + + + + + + SpacecraftDB + GroundDB + ManeuvreMessage + User + OutputFiles/AsRun + OutputFiles/Warmstart + SCDB + GDB + + + + + + + + + + + + + + + + + + diff --git a/config/server/server_example.xml b/config/server/server_example.xml new file mode 100644 index 0000000..b228ac6 --- /dev/null +++ b/config/server/server_example.xml @@ -0,0 +1,69 @@ + + + + + + + ENABLED + PROC + + 9 + + + + + + + language.xml + + + + + + + 9980 + 9981 + + + + $SPELL_HOME/bin/SPELL-Executor + false + + + + + + + 0 + True + False + True + 5 + ON_LINE + ENABLED + ENABLED + False + + + + + + + driver_example.xml + + + + + + + context_example.xml + + + + + + + Y + + + diff --git a/config/spell/language.xml b/config/spell/language.xml index 575d25c..ab9f0c2 100644 --- a/config/spell/language.xml +++ b/config/spell/language.xml @@ -29,6 +29,70 @@ False False + + + + + ABORT|SKIP|RESEND|CANCEL + True + 0 + 30 + 0.1 + None + False + True + ABORT|SKIP|RESEND|CANCEL + NOACTION + False + False + + + + ABORT|SKIP|REPEAT + True + 1 + + + + ABORT|SKIP|REPEAT + True + 1 + + + + ABORT|SKIP|REPEAT + True + 1 + + + + ABORT|SKIP|REPEAT + True + 1 + + + + ABORT|REPEAT|RESEND|RECHECK|SKIP|CANCEL + ABORT|REPEAT|RESEND|RECHECK|SKIP|CANCEL + True + 1 + + + + ABORT|REPEAT|RESEND|RECHECK|SKIP|CANCEL + ABORT|REPEAT|RESEND|RECHECK|SKIP|CANCEL + True + 1 + + + + ABORT|SKIP|REPEAT + True + 1 + + + + ABORT|SKIP|REPEAT|CANCEL @@ -210,6 +274,18 @@ ABORT|SKIP|REPEAT + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + ABORT|SKIP|REPEAT diff --git a/configure.ac b/configure.ac index ab949b0..221a86b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,263 +1,271 @@ ################################################################################ -# -# FILE : configure.ac -# DESCRIPTION: automake and autoconf configuration file, and source for the -# 'configure' script. Describes the structure and organization -# of all makefiles to be generated in order to build SPELL. -# -# LICENSE ------------------------------------------------------------------ -# -# -# Copyright (C) 2008, 2012 SES ENGINEERING, Luxembourg S.A.R.L. # +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# # This file is part of SPELL. -# -# SPELL is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# SPELL 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 General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with SPELL. If not, see . +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . # ################################################################################ +AC_INIT([SPELL], + [m4_esyscmd([scripts/src/version.sh])], + [juergen.gall@ses.com, raimondas.sasnauskas@ses.com]) +AM_INIT_AUTOMAKE([1.9 tar-pax -Wall -Werror foreign subdir-objects]) -AC_INIT([SPELL], [2.4], []) -AM_INIT_AUTOMAKE([1.9 tar-pax -Wall -Werror foreign]) +AC_CONFIG_SRCDIR([configure.ac]) AC_CONFIG_HEADERS([config.h]) AC_CONFIG_MACRO_DIR([m4]) -CXXFLAGS= - +AM_DISABLE_STATIC #------------------------------------------------------------------------------# # Checks for basic standard programs #------------------------------------------------------------------------------# AC_PROG_CC AC_PROG_CXX +m4_ifdef([AM_PROG_AR], [AM_PROG_AR]) AC_PROG_LIBTOOL AC_PROG_INSTALL AC_PROG_MAKE_SET -AM_PATH_PYTHON +#AM_PATH_PYTHON + +AC_CHECK_PROG(protoc, protoc, true) +if [ test x$protoc != xtrue ] +then + AC_MSG_ERROR([Protobuf compiler protoc required to compile SPELL]) +fi #------------------------------------------------------------------------------# -# Checks for some development headers (POSIX-threads,C++ standard library) +# Checks for some development headers (POSIX-threads, C++ standard library) #------------------------------------------------------------------------------# AC_HEADER_STDC AC_LANG_CPLUSPLUS -AC_CHECK_HEADER(pthread.h,,AC_MSG_ERROR(POSIX threads library is required)) - -AC_CHECK_LIB([rt] , [clock_gettime] ,,[AC_MSG_ERROR(rt library is required)] ) -AC_CHECK_LIB([dl] , [dlopen] ,,[AC_MSG_ERROR(dl library is required)] ) -AC_CHECK_LIB([util] , [openpty] ,,[AC_MSG_ERROR(util library is required)] ) -AC_CHECK_LIB([xerces-c] , [_init] ,,[AC_MSG_ERROR(xerces-c library is required)]) -AX_PYTHON_DEVEL([]) - - -#------------------------------------------------------------------------------# -# Conditional: check the platform in order to know which scripts to use -#------------------------------------------------------------------------------# -AM_CONDITIONAL(PLATFORM_LINUX, test ! -z `echo $host_os | grep "linux"`) - -#------------------------------------------------------------------------------# -# Conditional: check rsync availability -#------------------------------------------------------------------------------# -AM_CONDITIONAL(HAVE_RSYNC, test `which rsync`) - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the SPELL Executor component -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([executor], - [AS_HELP_STRING([--enable-executor],[Compile the executor process (default is yes) ])] , - [executor=true], - [executor=true]) -AM_CONDITIONAL([WITH_EXECUTOR], [test x$executor = xtrue]) - - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the SPELL Executor Command Line -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([executorcmd], - [AS_HELP_STRING([--enable-executorcmd],[Compile the command line executor (default is no) ])], - [executorcmd=true], - [executorcmd=false]) -AM_CONDITIONAL([WITH_EXECUTOR_CMD], [test x$executorcmd = xtrue]) - - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the SPELL GUI component -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([gui], - [AS_HELP_STRING([--enable-gui],[Compile the SPELL-GUI (default is no) ])], - [gui=true], - [gui=false]) -AM_CONDITIONAL([WITH_GUI], [test x$gui = xtrue]) - - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the SPELL standalone driver -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([driverstd], - [AS_HELP_STRING([--enable-driverstd],[Compile the SPELL standalone driver (default is no) ])], - [driverstd=true], - [driverstd=false]) -AM_CONDITIONAL([WITH_DRIVER_STD], [test x$driverstd = xtrue]) - - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the SPELL Shell component -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([shell], - [AS_HELP_STRING([--enable-shell],[Compile the SPELL shell (default is yes) ])], - [shell=true], - [shell=true]) -AM_CONDITIONAL([WITH_SHELL], [test x$shell = xtrue]) - - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the SPELL Listener component -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([listener], - [AS_HELP_STRING([--enable-listener],[Compile the SPELL listener process (default is yes) ])], - [listener=true], - [listener=true]) -AM_CONDITIONAL([WITH_LISTENER], [test x$listener = xtrue]) - - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the SPELL Context component -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([context], - [AS_HELP_STRING([--enable-context],[Compile the SPELL context process (default is yes) ])], - [context=true], - [context=true]) -AM_CONDITIONAL([WITH_CONTEXT], [test x$context = xtrue]) - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the documentation build -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([documentation], - [AS_HELP_STRING([--enable-documentation],[Compile the SPELL documentation (default is no) ])], - [documentation=true], - [documentation=false]) -AM_CONDITIONAL([WITH_DOCUMENTATION], [test x$documentation = xtrue]) - - -#------------------------------------------------------------------------------# -# ENABLE argument to activate or deactivate the sample procedures -#------------------------------------------------------------------------------# -AC_ARG_ENABLE([procs], - [AS_HELP_STRING([--enable-procs],[Include the sample procedures (default is no) ])], - [procs=true], - [procs=false]) -AM_CONDITIONAL([WITH_PROCS], [test x$procs = xtrue]) - -#------------------------------------------------------------------------------# -# WITH argument to indicate the path to the Eclipse installation used to -# compile the RCP applications. Shall be of version 3.7. -#------------------------------------------------------------------------------# -AC_ARG_WITH([eclipse], - [AS_HELP_STRING([--with-eclipse=PATH],[eclipse installation used to compile RCP components])], - [ - echo "using eclipse at ${withval}" - if [[ ! -d ${withval} ]] - then - AC_MSG_ERROR([Unable to find the eclipse installation: ${withval}]) - fi - with_eclipse_inst=${withval} +PYTHON2_VERSION=2 +AC_PYTHON_DEVEL_V2 +PYTHON3_VERSION=3 +AC_PYTHON_DEVEL_V3 +AC_CHECK_HEADER(pthread.h,,AC_MSG_ERROR([pthread.h is required (POSIX threads library)])) +AC_CHECK_HEADER(ncurses.h,,AC_MSG_ERROR([ncurses.h is required])) +PKG_PROG_PKG_CONFIG + +AC_CHECK_LIB([rt] , [clock_gettime] ,[:],[AC_MSG_ERROR(rt library is required)]) +AC_CHECK_LIB([dl] , [dlopen] ,[:],[AC_MSG_ERROR(dl library is required)]) +AC_CHECK_LIB([util] , [openpty] ,[:],[AC_MSG_ERROR(util library is required)]) +AC_CHECK_LIB([xerces-c] , [_init] ,[:],[AC_MSG_ERROR(xerces-c library is required)]) +AC_CHECK_LIB([z] , [inflate] ,[:],[AC_MSG_ERROR(zlib library is required)]) +AC_CHECK_LIB([menu] , [_init] ,[:],[AC_MSG_ERROR(menu library is required)]) +AC_CHECK_LIB([ncurses] , [initscr] ,[:],[AC_MSG_ERROR(ncurses library is required)]) +AC_CHECK_LIB([ncursesw] , [initscr] ,[:],[AC_MSG_ERROR(ncursesw library is required)]) +AC_CHECK_LIB([log4cplus], [_init] ,[:],[AC_MSG_ERROR(log4cplus library is required)]) + +#------------------------------------------------------------------------------# +# ENABLE argument to activate or deactivate the SPELL hifly driver +#------------------------------------------------------------------------------# +AC_ARG_ENABLE([hiflydriver], + [AS_HELP_STRING([--enable-hiflydriver],[Compile the hifly driver (default is no) ])], + [hiflydriver=true], + [hiflydriver=false]) +AM_CONDITIONAL([WITH_DRIVER_HIFLY], [test x$hiflydriver = xtrue]) + +#------------------------------------------------------------------------------# +# WITH argument to indicate the EXIF version for the hifly driver 2.0 +#------------------------------------------------------------------------------# +AC_ARG_WITH([exif], + [AS_HELP_STRING([--with-exif=5 or 6],[hifly EXIF version to be used ])], + [ + case "${withval}" in + 5) with_exif_version=EXIF5 ;; + 6) with_exif_version=EXIF6 ;; + *) AC_MSG_ERROR([bad value ${withval} for --with-exif]) ;; + esac ], []) -WITH_ECLIPSE= -AS_IF([ test "x$with_eclipse_inst" != x], - [AC_SUBST([WITH_ECLIPSE],[${with_eclipse_inst}])], +WITH_EXIF= +AS_IF([ test "x$with_exif_version" != x], + [AC_SUBST([WITH_EXIF],[${with_exif_version}])], []) #------------------------------------------------------------------------------# -# WITH argument to indicate the path to the Log4CPlus library +# Hifly driver checks #------------------------------------------------------------------------------# -AC_ARG_WITH([log4cplus], - [AS_HELP_STRING([--with-log4cplus=PATH],[path to the Log4C++ libraries])], - [ - echo "using Log4C++ libraries at ${withval}" - if [[ ! -d ${withval} ]] - then - AC_MSG_ERROR([Unable to find the Log$C++ libraries directory: ${withval}]) - fi - with_log4cplus=${withval} - ], - [ - echo "using default Log4C++ libraries path (/usr)" - with_log4cplus=/usr +AS_IF([test x$hiflydriver = xtrue], + [ + if [ test -z "$WITH_EXIF" ] + then + AC_MSG_ERROR([Must provide the EXIF version (--with-exif) to compile the hifly driver]) + else + echo "using EXIF version $WITH_EXIF" + fi + ], + []) + +AS_IF([test x$hiflydriver = xtrue], + [ + # just check for existence, don't create -l flags + AC_CHECK_LIB([boost_system], [_init], [AC_SUBST([BOOST_SYSTEM],[boost_system])], [ + AC_CHECK_LIB([boost-system], [_init], [AC_SUBST([BOOST_SYSTEM],[boost-system])], [ + AC_MSG_ERROR(boost-system library is required) ]) + ]) -WITH_LOG4CPLUS= -AS_IF([ test "x$with_log4cplus" != x], - [AC_SUBST([WITH_LOG4CPLUS],[${with_log4cplus}])], + AC_CHECK_LIB([boost_thread], [_init], [AC_SUBST([BOOST_THREAD],[boost_thread])], [ + AC_CHECK_LIB([boost-thread], [_init], [AC_SUBST([BOOST_THREAD],[boost-thread])], [ + AC_MSG_ERROR(boost-thread library is required) + ]) + ]) + AC_CHECK_LIB([boost_chrono], [_init], [AC_SUBST([BOOST_CHRONO],[boost_chrono])], [ + AC_CHECK_LIB([boost-chrono], [_init], [AC_SUBST([BOOST_CHRONO],[boost-chrono])], [ + AC_MSG_ERROR(boost-chrono library is required) + ]) + ]) + AC_CHECK_LIB([omniORB4] , [_init], [:], [AC_MSG_ERROR(omniORB4 library is required)]) + AC_CHECK_LIB([omnithread] , [_init], [:], [AC_MSG_ERROR(omnithread library is required)]) + ], []) #------------------------------------------------------------------------------# -# Some checks to do in case of compiling SPELL-DEV or SPELL-GUI +# ENABLE argument to activate or deactivate the scorpio driver +#------------------------------------------------------------------------------# +AC_ARG_ENABLE([scorpiodriver], + [AS_HELP_STRING([--enable-scorpiodriver],[Compile the scorpio driver (default is no) ])], + [scorpiodriver=true], + [scorpiodriver=false]) +AM_CONDITIONAL([WITH_DRIVER_SCORPIO], [test x$scorpiodriver = xtrue]) + +#------------------------------------------------------------------------------# +# Scorpio driver checks #------------------------------------------------------------------------------# -AS_IF([ test x$gui = xtrue], +AS_IF([test x$scorpiodriver = xtrue], [ - if [[ -z "$WITH_ECLIPSE" ]] + AC_CHECK_LIB([zmq], [_init], [:], [AC_MSG_ERROR(zmq library is required)]) + AC_CHECK_PROG(swig, swig, true) + if [ test x$swig != xtrue ] then - AC_MSG_ERROR([Must provide an eclipse installation path (--with-eclipse) to compile the SPELL-GUI]) + AC_MSG_ERROR([SWIG required to compile the scorpio driver]) fi + SWIGFULL=`swig -version | grep "SWIG Version" | cut -d' ' -f3` + AX_COMPARE_VERSION([$SWIGFULL],[ge],[1.3.36],[], + [AC_MSG_ERROR(SWIG version >= 1.3.36 required)]) ], []) #------------------------------------------------------------------------------# -# Some checks to do in case of compiling tests +# ENABLE argument to activate or deactivate the SPELL Unit Tests component #------------------------------------------------------------------------------# -AS_IF([ test x$tests = xtrue], - [ - AC_MSG_WARN(Tests and unit tests programs enabled) - AC_CHECK_LIB([cppunit],[_init],,[AC_MSG_ERROR(cppunit library is required to compile unit tests)]) - ], - [ - AC_MSG_WARN(Tests and unit tests programs disabled) - ]) +AC_ARG_ENABLE([tests], + [AS_HELP_STRING([--enable-tests],[Compile the SPELL tests (default is no) ])], + [tests=true], + [tests=false]) +AM_CONDITIONAL([WITH_TESTS], [test x$tests = xtrue]) -#------------------------------------------------------------------------------# -# Some checks to do in case of compiling SPELL executor command line -#------------------------------------------------------------------------------# -AS_IF([ test x$executorcmd = xtrue], +AS_IF([test x$tests = xtrue], [ - AC_CHECK_LIB([menu],[_init],,[AC_MSG_ERROR(menu library is required to compile the command line executor)]) - - # Checks for ncurses. - AC_CHECK_HEADERS([ncurses.h], [ - AC_CHECK_LIB(ncurses, initscr, [ - LIBS="$LIBS -lncurses" - AC_DEFINE(HAVE_LIBNCURSES, 1, [Define to 1 if you have the 'ncurses' library (-lncurses).]) - ], AC_MSG_ERROR(The ncurses library is required in order to build the command line executor!)) - ], AC_MSG_ERROR(The ncurses header is required in order to build the command line executor!)) + AC_CHECK_LIB([cppunit], [_init], [:], [AC_MSG_ERROR(cppunit library is required)]) ], []) -AM_CONDITIONAL([WITH_LIBS], [test x$executor$executorcmd$shell$tests != xfalsefalsefalsefalse ]) - -#------------------------------------------------------------------------------# -# Some checks to do in case of compiling documentation #------------------------------------------------------------------------------# -AM_CONDITIONAL([HAVE_DOXYGEN], [test x$`which doxygen &> /dev/null` != x]) +# ENABLE argument to include PACE +#------------------------------------------------------------------------------# +AC_ARG_ENABLE([pace], + [AS_HELP_STRING([--enable-pace],[Include PACE (default is no) ])], + [pace=true], + [pace=false]) +AM_CONDITIONAL([WITH_PACE], [test x$pace = xtrue]) + + +# Address Sanitizer + +AC_MSG_CHECKING([whether to build with Address Sanitizer support]) +AC_ARG_ENABLE(address-sanitizer, + [AS_HELP_STRING([--enable-address-sanitizer],[Enable Address Sanitizer support @<:@default=no@:>@.])], + [ + case "${enableval}" in + no|yes) + enable_address_sanitizer=${enableval} + ;; + *) + AC_MSG_ERROR([Invalid value ${enableval} for --enable-address-sanitizer]) + ;; + esac + ], + [enable_address_sanitizer=no]) +AC_MSG_RESULT(${enable_address_sanitizer}) + + + +convert_warning_flags_to_positive() { + flags_to_check="" + warning_flag_backup="" + for option in ${1}; do + case "$option" + in + -Wno-*) + flags_to_check="${flags_to_check} -W${option##-Wno-}" + warning_flag_backup="${warning_flag_backup} ${option##-Wno-}";; + *) + flags_to_check="${flags_to_check} ${option}";; + esac + done +} + +restore_negative_form_of_warning_flags() { + final_flags=${1} + for positive_option in ${warning_flag_backup}; do + final_flags=$(echo $ECHO_N $final_flags | $SED "s|-W${positive_option}|-Wno-${positive_option}|g") + done + unset flags_to_check + unset warning_flag_backup +} + +check_prospective_CFLAGS() { + convert_warning_flags_to_positive "${PROSPECTIVE_CFLAGS}" + #AX_CHECK_COMPILER_OPTIONS([C], ${flags_to_check}) + restore_negative_form_of_warning_flags "${CFLAGS}" + CFLAGS=${final_flags} + unset final_flags + +} + +check_prospective_CXXFLAGS() { + convert_warning_flags_to_positive "${PROSPECTIVE_CXXFLAGS}" + #AX_CHECK_COMPILER_OPTIONS([C++], ${flags_to_check}) + restore_negative_form_of_warning_flags "${CXXFLAGS}" + CXXFLAGS=${final_flags} + unset final_flags +} + +if test "${enable_address_sanitizer}" = "yes" ; then + + PROSPECTIVE_CFLAGS="-fsanitize-recover=address -fsanitize=address -O1 -fno-omit-frame-pointer -g" + PROSPECTIVE_CXXFLAGS="-fsanitize-recover=address -fsanitize=address -O1 -fno-omit-frame-pointer -g " + + # Check if the compilers support address sanitizer + check_prospective_CFLAGS + check_prospective_CXXFLAGS +fi + #------------------------------------------------------------------------------# # Specification of all the makefiles to be generated. #------------------------------------------------------------------------------# - MAKEFILES=Makefile AC_CONFIG_FILES([Makefile \ - lib/Makefile \ + lib/Makefile \ lib/SPELL_CIF/Makefile \ lib/SPELL_CTX/Makefile \ @@ -276,25 +284,23 @@ AC_CONFIG_FILES([Makefile \ lib/SPELL_SDB/Makefile \ lib/SPELL_DTA/Makefile \ - doc/Makefile \ - procs/Makefile \ config/Makefile \ - rcp/Makefile \ data/Makefile \ scripts/Makefile \ - scripts/linux/Makefile \ src/Makefile \ - src/spell/Makefile \ + src/context/Makefile \ src/executor/Makefile \ src/executorcmd/Makefile\ - src/context/Makefile \ src/listener/Makefile \ + src/service/Makefile \ src/shell/Makefile \ - src/spel-gui/Makefile \ + src/spell/Makefile \ + src/ping/Makefile \ drivers/Makefile \ - drivers/standalone/Makefile \ + drivers/example/Makefile\ + drivers/hifly2/Makefile \ + drivers/scorpio/Makefile ]) -AC_OUTPUT() - +AC_OUTPUT diff --git a/data/InputFiles/GroundDB/GDB.DB b/data/InputFiles/GroundDB/GDB.DB new file mode 100644 index 0000000..24cfc40 --- /dev/null +++ b/data/InputFiles/GroundDB/GDB.DB @@ -0,0 +1,24 @@ +# Sample boolean +BOOL1 True +# Sample forced string +STR1 A string +# Sample string +STR2 "A string" +# Sample list +LIST1 [1,2,3] +# Sample dictionary +DICT1 {"A":1,"B":2} +# Sample hexadecimal value +HEX1 0xA +# Sample float value +FLT1 -0.435 +# Sample float value +FLT2 0.34 +# Sample integer value +INT1 345 +# Sample octal value +OCT1 041 +# Sample date/time value +DAT1 01/01/2001 00:11:22 +# Sample binary value +BIN1 0b00101 diff --git a/data/InputFiles/ManeuvreMessage/TEST.IMP b/data/InputFiles/ManeuvreMessage/TEST.IMP new file mode 100644 index 0000000..24cfc40 --- /dev/null +++ b/data/InputFiles/ManeuvreMessage/TEST.IMP @@ -0,0 +1,24 @@ +# Sample boolean +BOOL1 True +# Sample forced string +STR1 A string +# Sample string +STR2 "A string" +# Sample list +LIST1 [1,2,3] +# Sample dictionary +DICT1 {"A":1,"B":2} +# Sample hexadecimal value +HEX1 0xA +# Sample float value +FLT1 -0.435 +# Sample float value +FLT2 0.34 +# Sample integer value +INT1 345 +# Sample octal value +OCT1 041 +# Sample date/time value +DAT1 01/01/2001 00:11:22 +# Sample binary value +BIN1 0b00101 diff --git a/data/InputFiles/SpacecraftDB/SCDB.DB b/data/InputFiles/SpacecraftDB/SCDB.DB new file mode 100644 index 0000000..2b72837 --- /dev/null +++ b/data/InputFiles/SpacecraftDB/SCDB.DB @@ -0,0 +1,2 @@ +Number_Of_EQUs 2 +Available_EQUs ['EQU1', 'EQU2'] diff --git a/data/InputFiles/User/TEST.IMP b/data/InputFiles/User/TEST.IMP new file mode 100644 index 0000000..24cfc40 --- /dev/null +++ b/data/InputFiles/User/TEST.IMP @@ -0,0 +1,24 @@ +# Sample boolean +BOOL1 True +# Sample forced string +STR1 A string +# Sample string +STR2 "A string" +# Sample list +LIST1 [1,2,3] +# Sample dictionary +DICT1 {"A":1,"B":2} +# Sample hexadecimal value +HEX1 0xA +# Sample float value +FLT1 -0.435 +# Sample float value +FLT2 0.34 +# Sample integer value +INT1 345 +# Sample octal value +OCT1 041 +# Sample date/time value +DAT1 01/01/2001 00:11:22 +# Sample binary value +BIN1 0b00101 diff --git a/data/Makefile.am b/data/Makefile.am index 2f22bf0..d76907e 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -1,46 +1,30 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ -include ${top_srcdir}/common.mk +SRC=${srcdir} +DST=${exec_prefix}/data install-exec-hook: - mkdir -p ${exec_prefix}/data -if HAVE_RSYNC - $(RSYNC) ${top_srcdir}/data/DriverData ${exec_prefix}/data/ - $(RSYNC) ${top_srcdir}/data/InputFiles ${exec_prefix}/data/ - $(RSYNC) ${top_srcdir}/data/OutputFiles ${exec_prefix}/data/ - $(RSYNC) ${top_srcdir}/data/Runtime ${exec_prefix}/data/ - $(RSYNC) ${top_srcdir}/data/Simulator ${exec_prefix}/data/ -else - cp -r ${top_srcdir}/data/DriverData ${exec_prefix}/data/. - cp -r ${top_srcdir}/data/InputFiles ${exec_prefix}/data/. - cp -r ${top_srcdir}/data/OutputFiles ${exec_prefix}/data/. - cp -r ${top_srcdir}/data/Runtime ${exec_prefix}/data/. - cp -r ${top_srcdir}/data/Simulator ${exec_prefix}/data/. - find ${exec_prefix}/data -name ".svn" | xargs rm -rf - rm -f ${exec_prefix}/data/Makefile* -endif - + cp -ur ${SRC}/InputFiles ${DST} + cp -ur ${SRC}/OutputFiles ${DST} + cp -ur ${SRC}/Procedures ${DST} + cp -ur ${SRC}/UserLib ${DST} + cp -ur ${SRC}/Runtime ${DST} diff --git a/data/OutputFiles/AsRun/.gitignore b/data/OutputFiles/AsRun/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/data/OutputFiles/Warmstart/.gitignore b/data/OutputFiles/Warmstart/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/data/Procedures/.gitignore b/data/Procedures/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/data/Procedures/TestProcedure.py b/data/Procedures/TestProcedure.py new file mode 100644 index 0000000..1590f7b --- /dev/null +++ b/data/Procedures/TestProcedure.py @@ -0,0 +1,19 @@ +################################################################################ +# +# NAME : TestProcedure +# DESCRIPTION : This is a sample procedure +# +# FILE : TestProcedure.py +# +# SPACECRAFT : Y +# +# SPECIFICATION : +# REVISION HISTORY: +# +################################################################################ + +Step('INIT', 'Initializing test procedure') + +Display('Hello, world!') + +Finish('Done!') diff --git a/data/Runtime/.gitignore b/data/Runtime/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/data/UserLib/.gitignore b/data/UserLib/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/deploy/bftd/ddcntl.yaml b/deploy/bftd/ddcntl.yaml new file mode 100644 index 0000000..5a15356 --- /dev/null +++ b/deploy/bftd/ddcntl.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: ddcntl + labels: + app.kubernetes.io/name: spell-cws-scorpio-ddcntl + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "v2.6.5.1-dev" # {"$imagepolicy": "spell:spell-server:tag"} +spec: + type: ExternalName + externalName: cws-lb.scorpio.svc.cluster.local +# --- +# apiVersion: v1 +# kind: Endpoints +# metadata: +# name: ddcntl +# subsets: +# - addresses: +# - hostname: ddcntl +# ip: 10.35.46.170 diff --git a/deploy/bftd/externalsecret.yaml b/deploy/bftd/externalsecret.yaml new file mode 100644 index 0000000..7a21cf4 --- /dev/null +++ b/deploy/bftd/externalsecret.yaml @@ -0,0 +1,34 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: spell-git-access +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + data: + - secretKey: id_rsa + remoteRef: + key: /spell-git-access + property: id_rsa + - secretKey: id_rsa.pub + remoteRef: + key: /spell-git-access + property: id_rsa.pub +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ldap-secret +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + data: + - secretKey: ldap-secret + remoteRef: + key: /ldap-secret + property: ldap-secret \ No newline at end of file diff --git a/deploy/bftd/flux-policies/imagepolicy.yaml b/deploy/bftd/flux-policies/imagepolicy.yaml new file mode 100644 index 0000000..4223022 --- /dev/null +++ b/deploy/bftd/flux-policies/imagepolicy.yaml @@ -0,0 +1,27 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImagePolicy +metadata: + name: spell-registry +spec: + imageRepositoryRef: + name: spell-registry + filterTags: + pattern: '^v(?P[^.]+(\.[^.]+)?(\.[^.]+)?)\.?(?P[^-]*)' + extract: '$semver$minor' + policy: + semver: + range: x.x.x +--- +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImagePolicy +metadata: + name: spell-server +spec: + imageRepositoryRef: + name: spell-server + filterTags: + pattern: '^v(?P[^.]+(\.[^.]+)?(\.[^.]+)?)\.?(?P[^-]*)' + extract: '$semver$minor' + policy: + semver: + range: x.x.x \ No newline at end of file diff --git a/deploy/bftd/flux-policies/imagerepository.yaml b/deploy/bftd/flux-policies/imagerepository.yaml new file mode 100644 index 0000000..b18478b --- /dev/null +++ b/deploy/bftd/flux-policies/imagerepository.yaml @@ -0,0 +1,23 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageRepository +metadata: + name: spell-registry +spec: + certSecretRef: + name: nexus-ca + image: bzengnexus1.idmz.eng.ses/spell/spell-registry + interval: 1m0s + secretRef: + name: bzengnexus-secret +--- +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageRepository +metadata: + name: spell-server +spec: + certSecretRef: + name: nexus-ca + image: bzengnexus1.idmz.eng.ses/spell/spell-server + interval: 1m0s + secretRef: + name: bzengnexus-secret \ No newline at end of file diff --git a/deploy/bftd/flux-policies/imageupdateautomation.yaml b/deploy/bftd/flux-policies/imageupdateautomation.yaml new file mode 100644 index 0000000..61fa4e2 --- /dev/null +++ b/deploy/bftd/flux-policies/imageupdateautomation.yaml @@ -0,0 +1,43 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageUpdateAutomation +metadata: + name: spell-repo + namespace: spell +spec: + interval: 1m0s + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + git: + checkout: + ref: + branch: jgall/container-updates + commit: + author: + email: fluxcdbot@users.noreply.internal.ses.com + name: fluxcdbot + messageTemplate: | + Automated image update + + Automation name: {{ .AutomationObject }} + + Files: + {{ range $filename, $_ := .Updated.Files -}} + - {{ $filename }} + {{ end -}} + + Objects: + {{ range $resource, $_ := .Updated.Objects -}} + - {{ $resource.Kind }} {{ $resource.Name }} + {{ end -}} + + Images: + {{ range .Updated.Images -}} + - {{.}} + {{ end -}} + push: + branch: flux-image-version-update + update: + path: ./deploy/bfts + strategy: Setters \ No newline at end of file diff --git a/deploy/bftd/flux-policies/nexus-ca.yaml b/deploy/bftd/flux-policies/nexus-ca.yaml new file mode 100644 index 0000000..977340d --- /dev/null +++ b/deploy/bftd/flux-policies/nexus-ca.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +data: + caFile: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVtVENDQTRHZ0F3SUJBZ0lUTlFBQUFVeWxRZW1sNGwrcVdRQUFBQUFCVERBTkJna3Foa2lHOXcwQkFRc0YKQURBak1TRXdId1lEVlFRREV4aFRSVk5UWldOMWNtbDBlVWx6YzNWcGJtZERRVXhWTURFd0hoY05NakF3T0RBMgpNakUwTkRReldoY05NalF3T0RBMk1qRTFORFF6V2pDQmh6RUxNQWtHQTFVRUJoTUNURlV4RVRBUEJnTlZCQWNUCkNFSmxkSHBrYjNKbU1Rd3dDZ1lEVlFRS0V3TlRSVk14RERBS0JnTlZCQXNUQTFOUFJURWhNQjhHQTFVRUF4TVkKWW5wbGJtZHVaWGgxY3pFdWFXUnRlaTVsYm1jdWMyVnpNU1l3SkFZSktvWklodmNOQVFrQkZoZEZaSGRoY21RdQpRbUZ6ZEdWc2JHbEFjMlZ6TG1OdmJUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCCkFMbndHKzdJSTBtVCtidVp5cXU4TEd4V1NWQ3hMc1JJREpsYkZwUlNUOFdPdjh5SlpzVDBQMkNwbXZlUk9XNnMKNGNuOGs4RnBZdVRBZndLRVVTM3l5S1FyTjZobjlDZllJWVcrQ1FpcVJPQldiQWVQNVFQaDJmc3o3NURjRW5DTQoxeXRtOHZTcE05ejVHSm45b1VObTVyMEh0dmFUdVNoNzgxdEt6TEVtaHhRbE9kSWdqYzluZ2lLb1RqUC9zV2tuCkM0aU0rVEVFemtKYTNZaUZacVRsT3M5Q0JNVnhmK0IxTUMzODRvcU51YmtzSDRIVUZNbnp5cm9UOGJoVThiN1cKQk4vRUk0aEV6NDZ2ODlyZHFrdHVJZGwveklKejUrMm1ZYXRpWnc1M2VYRm1pVEV5VWNqem96YWFIdEpDWG1teQpUeDJuWnNIYllDeEs3Q3R4UWtMUTJXRUNBd0VBQWFPQ0FWOHdnZ0ZiTUIwR0ExVWREZ1FXQkJTYVc0WTUzbU1TCk1hZTJodm1qYzJhbmJIRGZNVEFmQmdOVkhTTUVHREFXZ0JTa3g2TVp3SGxoejlNaHRWWUVTRnByeC83YzBEQngKQmdOVkhSOEVhakJvTUdhZ1pLQmloaTlvZEhSd09pOHZZM0pzTG5ObGN5NWpiMjB2VTBWVFUyVmpkWEpwZEhsSgpjM04xYVc1blEwRk1WVEF4TG1OeWJJWXZhSFIwY0RvdkwyTnliQzV3YTJrdWMyVnpMMU5GVTFObFkzVnlhWFI1ClNYTnpkV2x1WjBOQlRGVXdNUzVqY213d1N3WUlLd1lCQlFVSEFRRUVQekE5TURzR0NDc0dBUVVGQnpBQ2hpOW8KZEhSd09pOHZZM0pzTG5CcmFTNXpaWE12VTBWVFUyVmpkWEpwZEhsSmMzTjFhVzVuUTBGTVZUQXhMbU55ZERBTQpCZ05WSFJNQkFmOEVBakFBTUNNR0ExVWRFUVFjTUJxQ0dHSjZaVzVuYm1WNGRYTXhMbWxrYlhvdVpXNW5Mbk5sCmN6QU9CZ05WSFE4QkFmOEVCQU1DQmFBd0ZnWURWUjBsQVFIL0JBd3dDZ1lJS3dZQkJRVUhBd0V3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFCVlA4Z1hiVmxVTzVLNExQL1lrWXY5Ykp4MnNSODdHbjBRMi85dWZDOXpHMjJOdApYZUlCcDdqc2RDTWpNYzd4QzRWUHhTNktrSjRiUk9WZWI3V3BZK2tFbTRsSklreDNSQlhsemp4aitaT2JvVmhECjNoblNrWTRhNFFzdlJURTlucjQrYlRpSGN4ck90VGNIM244NEtFZXhjRDBwaktkN29yVWVGWHl6MjBJdTN4QjYKT0w0aHB4aEUrVkNTd1I0NXhVSFRLckt3K3pJa3VUV1NYMzBVQ0hZblExRWNTQkFKdHdLNWNkbXZtdUlXazJSdgpWSlVHSjlRQnFOV0padElUZlh3Zk4yT0ljT1VweHRPK3lXckQ5RHZGak1SYVZiUEIwMkIzNW5OcXA2QWN1N21hCnVsNjRicjlSc1grS2w2SGQ1eWltTTBieWJNTXZ4QUtpczNDaGFSMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= +kind: Secret +metadata: + name: nexus-ca diff --git a/deploy/bftd/kube-mp01-values.yaml b/deploy/bftd/kube-mp01-values.yaml new file mode 100644 index 0000000..af698d5 --- /dev/null +++ b/deploy/bftd/kube-mp01-values.yaml @@ -0,0 +1,33 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.6.5-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + properties: + key: value + settings: + cws: "ddcntl" + #archiva: "{{ .Values.spell_server.seat | lower }}dbs01.scorpio.eng.ses" + obus: obus-server.obus.svc:54407 + maxprocs: 15 + mission: MP01 + family: BSS702SP + seat: DEVB + applicable_missions: + MPOWER: MP01,ZMP01,ZMP02,ZMP03,ZMP04,ZMP05,ZMP06,ZMP07,ZMP08,ZMP09,ZMP10,ZMP11 + input_folder: "{{ .Values.spell_server.mission }}/InputFiles/{{ .Values.spell_server.mission }}" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles/{{ .Values.spell_server.mission }}" +procedures: + size: 5Gi + repo_folder: MPOWER + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT diff --git a/deploy/bftd/kustomization.yaml b/deploy/bftd/kustomization.yaml new file mode 100644 index 0000000..d2c53a2 --- /dev/null +++ b/deploy/bftd/kustomization.yaml @@ -0,0 +1,42 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- ../spell-registry/ +- secretstore.yaml +- externalsecret.yaml +- mp09-s3-server.yaml +- mp01-kube-server.yaml +- s3cntl.yaml +- s5cntl.yaml +- flux-policies/imagepolicy.yaml +- flux-policies/imagerepository.yaml +- flux-policies/imageupdateautomation.yaml +- flux-policies/nexus-ca.yaml +- ddcntl.yaml + +configMapGenerator: +- name: s3-mp09-spell-server + files: + - s3-mp09-values.yaml +- name: kube-mp01-spell-server + files: + - kube-mp01-values.yaml + +configurations: +- kustomizeconfig.yaml + + +patchesJson6902: + - target: + kind: Ingress + name: spell-registry + version: v1 + patch: |- + - op: replace + path: "/spec/rules/0/host" + value: "spell-registry.ing-lu-d.spo.internal.ses.com" + - op: replace + path: "/spec/tls/0/hosts/0" + value: "spell-registry.ing-lu-d.spo.internal.ses.com" \ No newline at end of file diff --git a/deploy/bftd/kustomizeconfig.yaml b/deploy/bftd/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/bftd/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/bftd/mp01-kube-server.yaml b/deploy/bftd/mp01-kube-server.yaml new file mode 100644 index 0000000..deb3470 --- /dev/null +++ b/deploy/bftd/mp01-kube-server.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-mp01-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + reconcileStrategy: Revision + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-mp01-spell-server + valuesKey: kube-mp01-values.yaml + + + diff --git a/deploy/bftd/mp09-s3-server.yaml b/deploy/bftd/mp09-s3-server.yaml new file mode 100644 index 0000000..93d1b4b --- /dev/null +++ b/deploy/bftd/mp09-s3-server.yaml @@ -0,0 +1,22 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: s3-mp09-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + reconcileStrategy: Revision + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: s3-mp09-spell-server + valuesKey: s3-mp09-values.yaml + + + diff --git a/deploy/bftd/s3-mp09-values.yaml b/deploy/bftd/s3-mp09-values.yaml new file mode 100644 index 0000000..cfdc3aa --- /dev/null +++ b/deploy/bftd/s3-mp09-values.yaml @@ -0,0 +1,34 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.5.1-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + properties: + key: value + settings: + cws: "{{ .Values.spell_server.seat | lower }}cntl" + archiva: "{{ .Values.spell_server.seat | lower }}dbs01.scorpio.eng.ses" + obus: bfts-ob.eng.ses:54407 + maxprocs: 15 + ssh_port: 22 + mission: ZMP09 + family: BSS702SP + seat: S3 + applicable_missions: + MPOWER: MP01,ZMP01,ZMP02,ZMP03,ZMP04,ZMP05,ZMP06,ZMP07,ZMP08,ZMP09,ZMP10,ZMP11 + input_folder: "{{ .Values.spell_server.mission }}/InputFiles/{{ substr 1 99999 .Values.spell_server.mission }}" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles/{{ substr 1 99999 .Values.spell_server.mission }}" +procedures: + size: 5Gi + repo_folder: MPOWER + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT diff --git a/deploy/bftd/s3cntl.yaml b/deploy/bftd/s3cntl.yaml new file mode 100644 index 0000000..f2fcd66 --- /dev/null +++ b/deploy/bftd/s3cntl.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: s3cntl + labels: + app.kubernetes.io/name: spell-cws-scorpio-s3cntl + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "v2.6.5.1-dev" # {"$imagepolicy": "spell:spell-server:tag"} +spec: + type: ExternalName + externalName: s3cntl.scorpio.eng.ses +# --- +# apiVersion: v1 +# kind: Endpoints +# metadata: +# name: s3cntl +# subsets: +# - addresses: +# - hostname: s3cntl +# ip: 10.35.24.30 diff --git a/deploy/bftd/s5cntl.yaml b/deploy/bftd/s5cntl.yaml new file mode 100644 index 0000000..200f1ad --- /dev/null +++ b/deploy/bftd/s5cntl.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: s5cntl + labels: + app.kubernetes.io/name: spell-cws-scorpio-s5cntl + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "v2.6.5.1-dev" # {"$imagepolicy": "spell:spell-server:tag"} +spec: + type: ExternalName + externalName: s5cntl.scorpio.eng.ses +# --- +# apiVersion: v1 +# kind: Endpoints +# metadata: +# name: s5cntl +# subsets: +# - addresses: +# - hostname: s5cntl +# ip: 10.35.35.30 diff --git a/deploy/bftd/secretstore.yaml b/deploy/bftd/secretstore.yaml new file mode 100644 index 0000000..f42d8c6 --- /dev/null +++ b/deploy/bftd/secretstore.yaml @@ -0,0 +1,26 @@ +# Expected Structure: +# spell-git-access: {"id_rsa":"", "id_rsa.pub":""} +# ldap-access: {} +# +# +# +apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: spell-vault-backend +spec: + provider: + vault: + server: "https://vault.vault:8200" + path: "soe/secret/spell" + version: "v2" + caProvider: + type: "Secret" + name: "vault-ca" + key: "ca.crt" + auth: + kubernetes: + mountPath: "kubernetes" + role: "serviceaccount" + serviceAccountRef: + name: "default" diff --git a/deploy/bfts/externalsecret.yaml b/deploy/bfts/externalsecret.yaml new file mode 100644 index 0000000..dfdbc01 --- /dev/null +++ b/deploy/bfts/externalsecret.yaml @@ -0,0 +1,66 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: spell-git-access +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + data: + - secretKey: id_rsa + remoteRef: + key: /spell-git-access + property: id_rsa + - secretKey: id_rsa.pub + remoteRef: + key: /spell-git-access + property: id_rsa.pub +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ldap-secret +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + data: + - secretKey: ldap-secret + remoteRef: + key: /ldap-secret + property: ldap-secret +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ses24-encryption-keys +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + dataFrom: + - extract: + key: /ses24-encryption + decodingStrategy: Auto + +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ses24-encryption-cert +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + data: + - secretKey: ses24_rsa.pem + remoteRef: + key: /ses24-encryption + property: ses24_rsa.pem diff --git a/deploy/bfts/kustomization.yaml b/deploy/bfts/kustomization.yaml new file mode 100644 index 0000000..d60f692 --- /dev/null +++ b/deploy/bfts/kustomization.yaml @@ -0,0 +1,30 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- ../spell-registry/ +- secretstore.yaml +- externalsecret.yaml +- missions/zs24a/ +- missions/zs24b/ +- missions/zs24c/ +- missions/zs24d/ +- missions/zs24e/ +- missions/zs24f/ + +patchesJson6902: + - target: + kind: Ingress + name: spell-registry + version: v1 + patch: |- + - op: replace + path: "/spec/rules/0/host" + value: "spell-registry.ing-lu-s.spo.internal.ses.com" + - op: replace + path: "/spec/tls/0/hosts/0" + value: "spell-registry.ing-lu-s.spo.internal.ses.com" + +configurations: +- kustomizeconfig.yaml diff --git a/deploy/bfts/kustomizeconfig.yaml b/deploy/bfts/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/bfts/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/bfts/missions/zs24a/kube-ses24-server.yaml b/deploy/bfts/missions/zs24a/kube-ses24-server.yaml new file mode 100644 index 0000000..438142d --- /dev/null +++ b/deploy/bfts/missions/zs24a/kube-ses24-server.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-zs24a-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-zs24a-spell-release + valuesKey: kube-ses24-values.yaml diff --git a/deploy/bfts/missions/zs24a/kube-ses24-values.yaml b/deploy/bfts/missions/zs24a/kube-ses24-values.yaml new file mode 100644 index 0000000..959bd95 --- /dev/null +++ b/deploy/bfts/missions/zs24a/kube-ses24-values.yaml @@ -0,0 +1,75 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.8.1-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + env: + NODE_ID_zs24asim: "3369" + NODE_NAME_3369: zs24asim + properties: + key: value + settings: + archiva: "zs24asim" + FLEETLIMITS: limitsdb-api.limitsdb.svc:8080 + FLEETLIMITS_SEAT: SIMB + SATDB: satdb-api.satdb.svc:8080 + cws: "zs24asim" + obus: obus-test-server.obus.svc:54407 + maxprocs: 15 + mission: ZS24A + context_mission: SES24 + family: SBNEO + seat: SIMB + applicable_missions: + SBNEO: "SES24,ZS24A,ZS24B,ZS24C" + input_folder: "{{ .Values.spell_server.mission }}/InputFiles" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles" + additionalInitSteps: "[ -d '/etc/pki/tls/private/wrapped_keys/' ] && (cd /etc/pki/tls/private/wrapped_keys/ && unzip -u ses24-keys.zip && (find . | grep ses24 | sed 'p;s/ses24/zs24a/' | xargs -n2 mv)) || echo 'Could not find encryption key archive!'" + additionalVolumeMounts: + - mountPath: /etc/pki/tls/private/wrapped_keys/ + name: ses24-certs-folder + - mountPath: /etc/pki/tls/private/wrapped_keys/ses24-keys.zip + name: ses24-encryption-keys + subPath: ses24-keys.zip + - mountPath: /etc/pki/tls/zs24a_keys/zs24a_rsa.pem + name: ses24-encryption-cert + subPath: ses24_rsa.pem + additionalVolumes: + - name: ses24-certs-folder + emptyDir: + sizeLimit: 100Mi + medium: Memory + - name: ses24-encryption-cert + secret: + secretName: ses24-encryption-cert + optional: true + defaultMode: 0400 + - name: ses24-encryption-keys + secret: + secretName: ses24-encryption-keys + optional: true + defaultMode: 0400 +procedures: + size: 5Gi + repo_folder: SES24 + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + propsrcs1.idmz.soc.ses,10.114.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDwEclkdaNWKtL0iuulsFrzG5VGH2y2ct7uZu755g6oKKYhyTrs9ytCdS7sK5NTwDNBjiEuywXtcZcX1RyGenxc/3YQTj0aHWC3tXfVSUcbkzKle0Tdxaq+arlI35TcLLsDnaI1OWN/TMUhGasGGSwOOp2IzJP8JAuD1/iNfzhVa1SOD39jt55oDEnncHqKS25CqBNWY1XC23+gQexB+6ai+bYZutDP+IKmnx/5quPeTnIJnXRRvGHh01oTNRTlVH5Nz4evVmSQst57vNcXzY76WwDvav2u8Gu6Yz2cyngCelXnU8Ko76sRR+VxwYeZvBDbgh0//S8aWl72KFVCH9o5 + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT + +auth: + ssh: + version: v2.6.7.7-dev # {"$imagepolicy": "spell:spell-server:tag"} + ldap: + url: ldaps://beacdc1.xeneng.ses + +service: + loadBalancerIP: 10.35.47.136 \ No newline at end of file diff --git a/deploy/bfts/missions/zs24a/kustomization.yaml b/deploy/bfts/missions/zs24a/kustomization.yaml new file mode 100644 index 0000000..a50f7a0 --- /dev/null +++ b/deploy/bfts/missions/zs24a/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- kube-ses24-server.yaml +- ses24cntl.yaml + +configMapGenerator: +- name: kube-zs24a-spell-release + options: + disableNameSuffixHash: false + files: + - kube-ses24-values.yaml + + +configurations: +- kustomizeconfig.yaml \ No newline at end of file diff --git a/deploy/bfts/missions/zs24a/kustomizeconfig.yaml b/deploy/bfts/missions/zs24a/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/bfts/missions/zs24a/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/bfts/missions/zs24a/ses24cntl.yaml b/deploy/bfts/missions/zs24a/ses24cntl.yaml new file mode 100644 index 0000000..ea425fb --- /dev/null +++ b/deploy/bfts/missions/zs24a/ses24cntl.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: zs24asim + labels: + app.kubernetes.io/name: spell-cws-scorpio-zs24asim + app.kubernetes.io/instance: spell +spec: + type: ExternalName + externalName: cws-lb.scorpio-zs24a.svc.cluster.local + diff --git a/deploy/bfts/missions/zs24b/kube-ses24-server.yaml b/deploy/bfts/missions/zs24b/kube-ses24-server.yaml new file mode 100644 index 0000000..c2a382c --- /dev/null +++ b/deploy/bfts/missions/zs24b/kube-ses24-server.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-zs24b-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-zs24b-spell-release + valuesKey: kube-ses24-values.yaml diff --git a/deploy/bfts/missions/zs24b/kube-ses24-values.yaml b/deploy/bfts/missions/zs24b/kube-ses24-values.yaml new file mode 100644 index 0000000..57b05da --- /dev/null +++ b/deploy/bfts/missions/zs24b/kube-ses24-values.yaml @@ -0,0 +1,75 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.8.1-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + env: + NODE_ID_zs24bsim: "3369" + NODE_NAME_3369: zs24bsim + properties: + key: value + settings: + archiva: "zs24bsim" + FLEETLIMITS: limitsdb-api.limitsdb.svc:8080 + FLEETLIMITS_SEAT: SIMB + SATDB: satdb-api.satdb.svc:8080 + cws: "zs24bsim" + obus: obus-test-server.obus.svc:54407 + maxprocs: 15 + mission: ZS24B + context_mission: SES24 + family: SBNEO + seat: SIMB + applicable_missions: + SBNEO: "SES24,ZS24A,ZS24B,ZS24C,ZS24D,ZS24E,ZS24F" + input_folder: "{{ .Values.spell_server.mission }}/InputFiles" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles" + additionalInitSteps: "[ -d '/etc/pki/tls/private/wrapped_keys/' ] && (cd /etc/pki/tls/private/wrapped_keys/ && unzip -u ses24-keys.zip && (find . | grep ses24 | sed 'p;s/ses24/zs24b/' | xargs -n2 mv)) || echo 'Could not find encryption key archive!'" + additionalVolumeMounts: + - mountPath: /etc/pki/tls/private/wrapped_keys/ + name: ses24-certs-folder + - mountPath: /etc/pki/tls/private/wrapped_keys/ses24-keys.zip + name: ses24-encryption-keys + subPath: ses24-keys.zip + - mountPath: /etc/pki/tls/zs24b_keys/zs24b_rsa.pem + name: ses24-encryption-cert + subPath: ses24_rsa.pem + additionalVolumes: + - name: ses24-certs-folder + emptyDir: + sizeLimit: 100Mi + medium: Memory + - name: ses24-encryption-cert + secret: + secretName: ses24-encryption-cert + optional: true + defaultMode: 0400 + - name: ses24-encryption-keys + secret: + secretName: ses24-encryption-keys + optional: true + defaultMode: 0400 +procedures: + size: 5Gi + repo_folder: SES24 + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + propsrcs1.idmz.soc.ses,10.114.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDwEclkdaNWKtL0iuulsFrzG5VGH2y2ct7uZu755g6oKKYhyTrs9ytCdS7sK5NTwDNBjiEuywXtcZcX1RyGenxc/3YQTj0aHWC3tXfVSUcbkzKle0Tdxaq+arlI35TcLLsDnaI1OWN/TMUhGasGGSwOOp2IzJP8JAuD1/iNfzhVa1SOD39jt55oDEnncHqKS25CqBNWY1XC23+gQexB+6ai+bYZutDP+IKmnx/5quPeTnIJnXRRvGHh01oTNRTlVH5Nz4evVmSQst57vNcXzY76WwDvav2u8Gu6Yz2cyngCelXnU8Ko76sRR+VxwYeZvBDbgh0//S8aWl72KFVCH9o5 + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT + +auth: + ssh: + version: v2.6.7.7-dev # {"$imagepolicy": "spell:spell-server:tag"} + ldap: + url: ldaps://beacdc1.xeneng.ses + +service: + loadBalancerIP: 10.35.47.182 diff --git a/deploy/bfts/missions/zs24b/kustomization.yaml b/deploy/bfts/missions/zs24b/kustomization.yaml new file mode 100644 index 0000000..74f3511 --- /dev/null +++ b/deploy/bfts/missions/zs24b/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- kube-ses24-server.yaml +- ses24cntl.yaml + +configMapGenerator: +- name: kube-zs24b-spell-release + options: + disableNameSuffixHash: false + files: + - kube-ses24-values.yaml + + +configurations: +- kustomizeconfig.yaml \ No newline at end of file diff --git a/deploy/bfts/missions/zs24b/kustomizeconfig.yaml b/deploy/bfts/missions/zs24b/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/bfts/missions/zs24b/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/bfts/missions/zs24b/ses24cntl.yaml b/deploy/bfts/missions/zs24b/ses24cntl.yaml new file mode 100644 index 0000000..2ea3539 --- /dev/null +++ b/deploy/bfts/missions/zs24b/ses24cntl.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: zs24bsim + labels: + app.kubernetes.io/name: spell-cws-scorpio-zs24bsim + app.kubernetes.io/instance: spell +spec: + type: ExternalName + externalName: cws-lb.scorpio-zs24b.svc.cluster.local + diff --git a/deploy/bfts/missions/zs24c/kube-ses24-server.yaml b/deploy/bfts/missions/zs24c/kube-ses24-server.yaml new file mode 100644 index 0000000..7637c7d --- /dev/null +++ b/deploy/bfts/missions/zs24c/kube-ses24-server.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-zs24c-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-zs24c-spell-release + valuesKey: kube-ses24-values.yaml diff --git a/deploy/bfts/missions/zs24c/kube-ses24-values.yaml b/deploy/bfts/missions/zs24c/kube-ses24-values.yaml new file mode 100644 index 0000000..1ab0f76 --- /dev/null +++ b/deploy/bfts/missions/zs24c/kube-ses24-values.yaml @@ -0,0 +1,75 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.8.1-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + env: + NODE_ID_zs24csim: "3369" + NODE_NAME_3369: zs24csim + properties: + key: value + settings: + archiva: "zs24csim" + FLEETLIMITS: limitsdb-api.limitsdb.svc:8080 + FLEETLIMITS_SEAT: SIMB + SATDB: satdb-api.satdb.svc:8080 + cws: "zs24csim" + obus: obus-test-server.obus.svc:54407 + maxprocs: 15 + mission: ZS24C + context_mission: SES24 + family: SBNEO + seat: SIMB + applicable_missions: + SBNEO: "SES24,ZS24A,ZS24B,ZS24C,ZS24D,ZS24E,ZS24F" + input_folder: "{{ .Values.spell_server.mission }}/InputFiles" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles" + additionalInitSteps: "[ -d '/etc/pki/tls/private/wrapped_keys/' ] && (cd /etc/pki/tls/private/wrapped_keys/ && unzip -u ses24-keys.zip && (find . | grep ses24 | sed 'p;s/ses24/zs24c/' | xargs -n2 mv)) || echo 'Could not find encryption key archive!'" + additionalVolumeMounts: + - mountPath: /etc/pki/tls/private/wrapped_keys/ + name: ses24-certs-folder + - mountPath: /etc/pki/tls/private/wrapped_keys/ses24-keys.zip + name: ses24-encryption-keys + subPath: ses24-keys.zip + - mountPath: /etc/pki/tls/zs24c_keys/zs24c_rsa.pem + name: ses24-encryption-cert + subPath: ses24_rsa.pem + additionalVolumes: + - name: ses24-certs-folder + emptyDir: + sizeLimit: 100Mi + medium: Memory + - name: ses24-encryption-cert + secret: + secretName: ses24-encryption-cert + optional: true + defaultMode: 0400 + - name: ses24-encryption-keys + secret: + secretName: ses24-encryption-keys + optional: true + defaultMode: 0400 +procedures: + size: 5Gi + repo_folder: SES24 + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + propsrcs1.idmz.soc.ses,10.114.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDwEclkdaNWKtL0iuulsFrzG5VGH2y2ct7uZu755g6oKKYhyTrs9ytCdS7sK5NTwDNBjiEuywXtcZcX1RyGenxc/3YQTj0aHWC3tXfVSUcbkzKle0Tdxaq+arlI35TcLLsDnaI1OWN/TMUhGasGGSwOOp2IzJP8JAuD1/iNfzhVa1SOD39jt55oDEnncHqKS25CqBNWY1XC23+gQexB+6ai+bYZutDP+IKmnx/5quPeTnIJnXRRvGHh01oTNRTlVH5Nz4evVmSQst57vNcXzY76WwDvav2u8Gu6Yz2cyngCelXnU8Ko76sRR+VxwYeZvBDbgh0//S8aWl72KFVCH9o5 + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT + +auth: + ssh: + version: v2.6.7.7-dev # {"$imagepolicy": "spell:spell-server:tag"} + ldap: + url: ldaps://beacdc1.xeneng.ses + +service: + loadBalancerIP: 10.35.47.226 diff --git a/deploy/bfts/missions/zs24c/kustomization.yaml b/deploy/bfts/missions/zs24c/kustomization.yaml new file mode 100644 index 0000000..1eb4aa5 --- /dev/null +++ b/deploy/bfts/missions/zs24c/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- kube-ses24-server.yaml +- ses24cntl.yaml + +configMapGenerator: +- name: kube-zs24c-spell-release + options: + disableNameSuffixHash: false + files: + - kube-ses24-values.yaml + + +configurations: +- kustomizeconfig.yaml \ No newline at end of file diff --git a/deploy/bfts/missions/zs24c/kustomizeconfig.yaml b/deploy/bfts/missions/zs24c/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/bfts/missions/zs24c/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/bfts/missions/zs24c/ses24cntl.yaml b/deploy/bfts/missions/zs24c/ses24cntl.yaml new file mode 100644 index 0000000..9b86107 --- /dev/null +++ b/deploy/bfts/missions/zs24c/ses24cntl.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: zs24csim + labels: + app.kubernetes.io/name: spell-cws-scorpio-zs24csim + app.kubernetes.io/instance: spell +spec: + type: ExternalName + externalName: cws-lb.scorpio-zs24c.svc.cluster.local + diff --git a/deploy/bfts/missions/zs24d/kube-ses24-server.yaml b/deploy/bfts/missions/zs24d/kube-ses24-server.yaml new file mode 100644 index 0000000..8b650fc --- /dev/null +++ b/deploy/bfts/missions/zs24d/kube-ses24-server.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-zs24d-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-zs24d-spell-release + valuesKey: kube-ses24-values.yaml diff --git a/deploy/bfts/missions/zs24d/kube-ses24-values.yaml b/deploy/bfts/missions/zs24d/kube-ses24-values.yaml new file mode 100644 index 0000000..66d2eed --- /dev/null +++ b/deploy/bfts/missions/zs24d/kube-ses24-values.yaml @@ -0,0 +1,75 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.8.1-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + env: + NODE_ID_zs24dsim: "3369" + NODE_NAME_3369: zs24dsim + properties: + key: value + settings: + archiva: "zs24dsim" + FLEETLIMITS: limitsdb-api.limitsdb.svc:8080 + FLEETLIMITS_SEAT: SIMB + SATDB: satdb-api.satdb.svc:8080 + cws: "zs24dsim" + obus: obus-test-server.obus.svc:54407 + maxprocs: 15 + mission: ZS24D + context_mission: SES24 + family: SBNEO + seat: SIMB + applicable_missions: + SBNEO: "SES24,ZS24A,ZS24D,ZS24C,ZS24D,ZS24E,ZS24F" + input_folder: "{{ .Values.spell_server.mission }}/InputFiles" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles" + additionalInitSteps: "[ -d '/etc/pki/tls/private/wrapped_keys/' ] && (cd /etc/pki/tls/private/wrapped_keys/ && unzip -u ses24-keys.zip && (find . | grep ses24 | sed 'p;s/ses24/zs24d/' | xargs -n2 mv)) || echo 'Could not find encryption key archive!'" + additionalVolumeMounts: + - mountPath: /etc/pki/tls/private/wrapped_keys/ + name: ses24-certs-folder + - mountPath: /etc/pki/tls/private/wrapped_keys/ses24-keys.zip + name: ses24-encryption-keys + subPath: ses24-keys.zip + - mountPath: /etc/pki/tls/zs24d_keys/zs24d_rsa.pem + name: ses24-encryption-cert + subPath: ses24_rsa.pem + additionalVolumes: + - name: ses24-certs-folder + emptyDir: + sizeLimit: 100Mi + medium: Memory + - name: ses24-encryption-cert + secret: + secretName: ses24-encryption-cert + optional: true + defaultMode: 0400 + - name: ses24-encryption-keys + secret: + secretName: ses24-encryption-keys + optional: true + defaultMode: 0400 +procedures: + size: 5Gi + repo_folder: SES24 + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + propsrcs1.idmz.soc.ses,10.114.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDwEclkdaNWKtL0iuulsFrzG5VGH2y2ct7uZu755g6oKKYhyTrs9ytCdS7sK5NTwDNBjiEuywXtcZcX1RyGenxc/3YQTj0aHWC3tXfVSUcbkzKle0Tdxaq+arlI35TcLLsDnaI1OWN/TMUhGasGGSwOOp2IzJP8JAuD1/iNfzhVa1SOD39jt55oDEnncHqKS25CqBNWY1XC23+gQexB+6ai+bYZutDP+IKmnx/5quPeTnIJnXRRvGHh01oTNRTlVH5Nz4evVmSQst57vNcXzY76WwDvav2u8Gu6Yz2cyngCelXnU8Ko76sRR+VxwYeZvBDbgh0//S8aWl72KFVCH9o5 + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT + +auth: + ssh: + version: v2.6.7.7-dev # {"$imagepolicy": "spell:spell-server:tag"} + ldap: + url: ldaps://beacdc1.xeneng.ses + +service: + loadBalancerIP: 10.35.47.230 diff --git a/deploy/bfts/missions/zs24d/kustomization.yaml b/deploy/bfts/missions/zs24d/kustomization.yaml new file mode 100644 index 0000000..0af5d03 --- /dev/null +++ b/deploy/bfts/missions/zs24d/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- kube-ses24-server.yaml +- ses24cntl.yaml + +configMapGenerator: +- name: kube-zs24d-spell-release + options: + disableNameSuffixHash: false + files: + - kube-ses24-values.yaml + + +configurations: +- kustomizeconfig.yaml \ No newline at end of file diff --git a/deploy/bfts/missions/zs24d/kustomizeconfig.yaml b/deploy/bfts/missions/zs24d/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/bfts/missions/zs24d/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/bfts/missions/zs24d/ses24cntl.yaml b/deploy/bfts/missions/zs24d/ses24cntl.yaml new file mode 100644 index 0000000..d2d5731 --- /dev/null +++ b/deploy/bfts/missions/zs24d/ses24cntl.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: zs24dsim + labels: + app.kubernetes.io/name: spell-cws-scorpio-zs24dsim + app.kubernetes.io/instance: spell +spec: + type: ExternalName + externalName: cws-lb.scorpio-zs24d.svc.cluster.local + diff --git a/deploy/bfts/missions/zs24e/kube-ses24-server.yaml b/deploy/bfts/missions/zs24e/kube-ses24-server.yaml new file mode 100644 index 0000000..9b3984c --- /dev/null +++ b/deploy/bfts/missions/zs24e/kube-ses24-server.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-zs24e-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-zs24e-spell-release + valuesKey: kube-ses24-values.yaml diff --git a/deploy/bfts/missions/zs24e/kube-ses24-values.yaml b/deploy/bfts/missions/zs24e/kube-ses24-values.yaml new file mode 100644 index 0000000..07b0b6f --- /dev/null +++ b/deploy/bfts/missions/zs24e/kube-ses24-values.yaml @@ -0,0 +1,75 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.8.1-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + env: + NODE_ID_zs24esim: "3369" + NODE_NAME_3369: zs24esim + properties: + key: value + settings: + archiva: "zs24esim" + FLEETLIMITS: limitsdb-api.limitsdb.svc:8080 + FLEETLIMITS_SEAT: SIMB + SATDB: satdb-api.satdb.svc:8080 + cws: "zs24esim" + obus: obus-test-server.obus.svc:54407 + maxprocs: 15 + mission: ZS24E + context_mission: SES24 + family: SBNEO + seat: SIMB + applicable_missions: + SBNEO: "SES24,ZS24A,ZS24E,ZS24C,ZS24D,ZS24E,ZS24F" + input_folder: "{{ .Values.spell_server.mission }}/InputFiles" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles" + additionalInitSteps: "[ -d '/etc/pki/tls/private/wrapped_keys/' ] && (cd /etc/pki/tls/private/wrapped_keys/ && unzip -u ses24-keys.zip && (find . | grep ses24 | sed 'p;s/ses24/zs24e/' | xargs -n2 mv)) || echo 'Could not find encryption key archive!'" + additionalVolumeMounts: + - mountPath: /etc/pki/tls/private/wrapped_keys/ + name: ses24-certs-folder + - mountPath: /etc/pki/tls/private/wrapped_keys/ses24-keys.zip + name: ses24-encryption-keys + subPath: ses24-keys.zip + - mountPath: /etc/pki/tls/zs24e_keys/zs24e_rsa.pem + name: ses24-encryption-cert + subPath: ses24_rsa.pem + additionalVolumes: + - name: ses24-certs-folder + emptyDir: + sizeLimit: 100Mi + medium: Memory + - name: ses24-encryption-cert + secret: + secretName: ses24-encryption-cert + optional: true + defaultMode: 0400 + - name: ses24-encryption-keys + secret: + secretName: ses24-encryption-keys + optional: true + defaultMode: 0400 +procedures: + size: 5Gi + repo_folder: SES24 + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + propsrcs1.idmz.soc.ses,10.114.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDwEclkdaNWKtL0iuulsFrzG5VGH2y2ct7uZu755g6oKKYhyTrs9ytCdS7sK5NTwDNBjiEuywXtcZcX1RyGenxc/3YQTj0aHWC3tXfVSUcbkzKle0Tdxaq+arlI35TcLLsDnaI1OWN/TMUhGasGGSwOOp2IzJP8JAuD1/iNfzhVa1SOD39jt55oDEnncHqKS25CqBNWY1XC23+gQexB+6ai+bYZutDP+IKmnx/5quPeTnIJnXRRvGHh01oTNRTlVH5Nz4evVmSQst57vNcXzY76WwDvav2u8Gu6Yz2cyngCelXnU8Ko76sRR+VxwYeZvBDbgh0//S8aWl72KFVCH9o5 + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT + +auth: + ssh: + version: v2.6.7.7-dev # {"$imagepolicy": "spell:spell-server:tag"} + ldap: + url: ldaps://beacdc1.xeneng.ses + +service: + loadBalancerIP: 10.35.47.155 diff --git a/deploy/bfts/missions/zs24e/kustomization.yaml b/deploy/bfts/missions/zs24e/kustomization.yaml new file mode 100644 index 0000000..a3a5f4a --- /dev/null +++ b/deploy/bfts/missions/zs24e/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- kube-ses24-server.yaml +- ses24cntl.yaml + +configMapGenerator: +- name: kube-zs24e-spell-release + options: + disableNameSuffixHash: false + files: + - kube-ses24-values.yaml + + +configurations: +- kustomizeconfig.yaml \ No newline at end of file diff --git a/deploy/bfts/missions/zs24e/kustomizeconfig.yaml b/deploy/bfts/missions/zs24e/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/bfts/missions/zs24e/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/bfts/missions/zs24e/ses24cntl.yaml b/deploy/bfts/missions/zs24e/ses24cntl.yaml new file mode 100644 index 0000000..2d408fa --- /dev/null +++ b/deploy/bfts/missions/zs24e/ses24cntl.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: zs24esim + labels: + app.kubernetes.io/name: spell-cws-scorpio-zs24esim + app.kubernetes.io/instance: spell +spec: + type: ExternalName + externalName: cws-lb.scorpio-zs24e.svc.cluster.local + diff --git a/deploy/bfts/missions/zs24f/kube-ses24-server.yaml b/deploy/bfts/missions/zs24f/kube-ses24-server.yaml new file mode 100644 index 0000000..6770b57 --- /dev/null +++ b/deploy/bfts/missions/zs24f/kube-ses24-server.yaml @@ -0,0 +1,18 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-zs24f-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-zs24f-spell-release + valuesKey: kube-ses24-values.yaml diff --git a/deploy/bfts/missions/zs24f/kube-ses24-values.yaml b/deploy/bfts/missions/zs24f/kube-ses24-values.yaml new file mode 100644 index 0000000..b153875 --- /dev/null +++ b/deploy/bfts/missions/zs24f/kube-ses24-values.yaml @@ -0,0 +1,75 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.8.1-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + env: + NODE_ID_zs24fsim: "3369" + NODE_NAME_3369: zs24fsim + properties: + key: value + settings: + archiva: "zs24fsim" + FLEETLIMITS: limitsdb-api.limitsdb.svc:8080 + FLEETLIMITS_SEAT: SIMB + SATDB: satdb-api.satdb.svc:8080 + cws: "zs24fsim" + obus: obus-test-server.obus.svc:54407 + maxprocs: 15 + mission: ZS24F + context_mission: SES24 + family: SBNEO + seat: SIMB + applicable_missions: + SBNEO: "SES24,ZS24A,ZS24F,ZS24C,ZS24D,ZS24E,ZS24F" + input_folder: "{{ .Values.spell_server.mission }}/InputFiles" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles" + additionalInitSteps: "[ -d '/etc/pki/tls/private/wrapped_keys/' ] && (cd /etc/pki/tls/private/wrapped_keys/ && unzip -u ses24-keys.zip && (find . | grep ses24 | sed 'p;s/ses24/zs24f/' | xargs -n2 mv) ) || echo 'Could not find encryption key archive!'" + additionalVolumeMounts: + - mountPath: /etc/pki/tls/private/wrapped_keys/ + name: ses24-certs-folder + - mountPath: /etc/pki/tls/private/wrapped_keys/ses24-keys.zip + name: ses24-encryption-keys + subPath: ses24-keys.zip + - mountPath: /etc/pki/tls/zs24f_keys/zs24f_rsa.pem + name: ses24-encryption-cert + subPath: ses24_rsa.pem + additionalVolumes: + - name: ses24-certs-folder + emptyDir: + sizeLimit: 100Mi + medium: Memory + - name: ses24-encryption-cert + secret: + secretName: ses24-encryption-cert + optional: true + defaultMode: 0400 + - name: ses24-encryption-keys + secret: + secretName: ses24-encryption-keys + optional: true + defaultMode: 0400 +procedures: + size: 5Gi + repo_folder: SES24 + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + propsrcs1.idmz.soc.ses,10.114.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDwEclkdaNWKtL0iuulsFrzG5VGH2y2ct7uZu755g6oKKYhyTrs9ytCdS7sK5NTwDNBjiEuywXtcZcX1RyGenxc/3YQTj0aHWC3tXfVSUcbkzKle0Tdxaq+arlI35TcLLsDnaI1OWN/TMUhGasGGSwOOp2IzJP8JAuD1/iNfzhVa1SOD39jt55oDEnncHqKS25CqBNWY1XC23+gQexB+6ai+bYZutDP+IKmnx/5quPeTnIJnXRRvGHh01oTNRTlVH5Nz4evVmSQst57vNcXzY76WwDvav2u8Gu6Yz2cyngCelXnU8Ko76sRR+VxwYeZvBDbgh0//S8aWl72KFVCH9o5 + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT + +auth: + ssh: + version: v2.6.7.7-dev # {"$imagepolicy": "spell:spell-server:tag"} + ldap: + url: ldaps://beacdc1.xeneng.ses + +service: + loadBalancerIP: 10.35.47.172 diff --git a/deploy/bfts/missions/zs24f/kustomization.yaml b/deploy/bfts/missions/zs24f/kustomization.yaml new file mode 100644 index 0000000..58f87ec --- /dev/null +++ b/deploy/bfts/missions/zs24f/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- kube-ses24-server.yaml +- ses24cntl.yaml + +configMapGenerator: +- name: kube-zs24f-spell-release + options: + disableNameSuffixHash: false + files: + - kube-ses24-values.yaml + + +configurations: +- kustomizeconfig.yaml \ No newline at end of file diff --git a/deploy/bfts/missions/zs24f/kustomizeconfig.yaml b/deploy/bfts/missions/zs24f/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/bfts/missions/zs24f/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/bfts/missions/zs24f/ses24cntl.yaml b/deploy/bfts/missions/zs24f/ses24cntl.yaml new file mode 100644 index 0000000..354e864 --- /dev/null +++ b/deploy/bfts/missions/zs24f/ses24cntl.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: zs24fsim + labels: + app.kubernetes.io/name: spell-cws-scorpio-zs24fsim + app.kubernetes.io/instance: spell +spec: + type: ExternalName + externalName: cws-lb.scorpio-zs24f.svc.cluster.local + diff --git a/deploy/bfts/secretstore.yaml b/deploy/bfts/secretstore.yaml new file mode 100644 index 0000000..f42d8c6 --- /dev/null +++ b/deploy/bfts/secretstore.yaml @@ -0,0 +1,26 @@ +# Expected Structure: +# spell-git-access: {"id_rsa":"", "id_rsa.pub":""} +# ldap-access: {} +# +# +# +apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: spell-vault-backend +spec: + provider: + vault: + server: "https://vault.vault:8200" + path: "soe/secret/spell" + version: "v2" + caProvider: + type: "Secret" + name: "vault-ca" + key: "ca.crt" + auth: + kubernetes: + mountPath: "kubernetes" + role: "serviceaccount" + serviceAccountRef: + name: "default" diff --git a/deploy/pfte/externalsecret.yaml b/deploy/pfte/externalsecret.yaml new file mode 100644 index 0000000..7a21cf4 --- /dev/null +++ b/deploy/pfte/externalsecret.yaml @@ -0,0 +1,34 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: spell-git-access +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + data: + - secretKey: id_rsa + remoteRef: + key: /spell-git-access + property: id_rsa + - secretKey: id_rsa.pub + remoteRef: + key: /spell-git-access + property: id_rsa.pub +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ldap-secret +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + data: + - secretKey: ldap-secret + remoteRef: + key: /ldap-secret + property: ldap-secret \ No newline at end of file diff --git a/deploy/pfte/flux-policies/imagepolicy.yaml b/deploy/pfte/flux-policies/imagepolicy.yaml new file mode 100644 index 0000000..4223022 --- /dev/null +++ b/deploy/pfte/flux-policies/imagepolicy.yaml @@ -0,0 +1,27 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImagePolicy +metadata: + name: spell-registry +spec: + imageRepositoryRef: + name: spell-registry + filterTags: + pattern: '^v(?P[^.]+(\.[^.]+)?(\.[^.]+)?)\.?(?P[^-]*)' + extract: '$semver$minor' + policy: + semver: + range: x.x.x +--- +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImagePolicy +metadata: + name: spell-server +spec: + imageRepositoryRef: + name: spell-server + filterTags: + pattern: '^v(?P[^.]+(\.[^.]+)?(\.[^.]+)?)\.?(?P[^-]*)' + extract: '$semver$minor' + policy: + semver: + range: x.x.x \ No newline at end of file diff --git a/deploy/pfte/flux-policies/imagerepository.yaml b/deploy/pfte/flux-policies/imagerepository.yaml new file mode 100644 index 0000000..b18478b --- /dev/null +++ b/deploy/pfte/flux-policies/imagerepository.yaml @@ -0,0 +1,23 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageRepository +metadata: + name: spell-registry +spec: + certSecretRef: + name: nexus-ca + image: bzengnexus1.idmz.eng.ses/spell/spell-registry + interval: 1m0s + secretRef: + name: bzengnexus-secret +--- +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageRepository +metadata: + name: spell-server +spec: + certSecretRef: + name: nexus-ca + image: bzengnexus1.idmz.eng.ses/spell/spell-server + interval: 1m0s + secretRef: + name: bzengnexus-secret \ No newline at end of file diff --git a/deploy/pfte/flux-policies/imageupdateautomation.yaml b/deploy/pfte/flux-policies/imageupdateautomation.yaml new file mode 100644 index 0000000..37858a8 --- /dev/null +++ b/deploy/pfte/flux-policies/imageupdateautomation.yaml @@ -0,0 +1,43 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageUpdateAutomation +metadata: + name: spell-repo + namespace: spell +spec: + interval: 1m0s + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + git: + checkout: + ref: + branch: jgall/container-updates + commit: + author: + email: fluxcdbot@users.noreply.internal.ses.com + name: fluxcdbot + messageTemplate: | + Automated image update + + Automation name: {{ .AutomationObject }} + + Files: + {{ range $filename, $_ := .Updated.Files -}} + - {{ $filename }} + {{ end -}} + + Objects: + {{ range $resource, $_ := .Updated.Objects -}} + - {{ $resource.Kind }} {{ $resource.Name }} + {{ end -}} + + Images: + {{ range .Updated.Images -}} + - {{.}} + {{ end -}} + push: + branch: flux-image-version-update + update: + path: ./deploy/pfts + strategy: Setters \ No newline at end of file diff --git a/deploy/pfte/flux-policies/nexus-ca.yaml b/deploy/pfte/flux-policies/nexus-ca.yaml new file mode 100644 index 0000000..977340d --- /dev/null +++ b/deploy/pfte/flux-policies/nexus-ca.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +data: + caFile: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVtVENDQTRHZ0F3SUJBZ0lUTlFBQUFVeWxRZW1sNGwrcVdRQUFBQUFCVERBTkJna3Foa2lHOXcwQkFRc0YKQURBak1TRXdId1lEVlFRREV4aFRSVk5UWldOMWNtbDBlVWx6YzNWcGJtZERRVXhWTURFd0hoY05NakF3T0RBMgpNakUwTkRReldoY05NalF3T0RBMk1qRTFORFF6V2pDQmh6RUxNQWtHQTFVRUJoTUNURlV4RVRBUEJnTlZCQWNUCkNFSmxkSHBrYjNKbU1Rd3dDZ1lEVlFRS0V3TlRSVk14RERBS0JnTlZCQXNUQTFOUFJURWhNQjhHQTFVRUF4TVkKWW5wbGJtZHVaWGgxY3pFdWFXUnRlaTVsYm1jdWMyVnpNU1l3SkFZSktvWklodmNOQVFrQkZoZEZaSGRoY21RdQpRbUZ6ZEdWc2JHbEFjMlZ6TG1OdmJUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCCkFMbndHKzdJSTBtVCtidVp5cXU4TEd4V1NWQ3hMc1JJREpsYkZwUlNUOFdPdjh5SlpzVDBQMkNwbXZlUk9XNnMKNGNuOGs4RnBZdVRBZndLRVVTM3l5S1FyTjZobjlDZllJWVcrQ1FpcVJPQldiQWVQNVFQaDJmc3o3NURjRW5DTQoxeXRtOHZTcE05ejVHSm45b1VObTVyMEh0dmFUdVNoNzgxdEt6TEVtaHhRbE9kSWdqYzluZ2lLb1RqUC9zV2tuCkM0aU0rVEVFemtKYTNZaUZacVRsT3M5Q0JNVnhmK0IxTUMzODRvcU51YmtzSDRIVUZNbnp5cm9UOGJoVThiN1cKQk4vRUk0aEV6NDZ2ODlyZHFrdHVJZGwveklKejUrMm1ZYXRpWnc1M2VYRm1pVEV5VWNqem96YWFIdEpDWG1teQpUeDJuWnNIYllDeEs3Q3R4UWtMUTJXRUNBd0VBQWFPQ0FWOHdnZ0ZiTUIwR0ExVWREZ1FXQkJTYVc0WTUzbU1TCk1hZTJodm1qYzJhbmJIRGZNVEFmQmdOVkhTTUVHREFXZ0JTa3g2TVp3SGxoejlNaHRWWUVTRnByeC83YzBEQngKQmdOVkhSOEVhakJvTUdhZ1pLQmloaTlvZEhSd09pOHZZM0pzTG5ObGN5NWpiMjB2VTBWVFUyVmpkWEpwZEhsSgpjM04xYVc1blEwRk1WVEF4TG1OeWJJWXZhSFIwY0RvdkwyTnliQzV3YTJrdWMyVnpMMU5GVTFObFkzVnlhWFI1ClNYTnpkV2x1WjBOQlRGVXdNUzVqY213d1N3WUlLd1lCQlFVSEFRRUVQekE5TURzR0NDc0dBUVVGQnpBQ2hpOW8KZEhSd09pOHZZM0pzTG5CcmFTNXpaWE12VTBWVFUyVmpkWEpwZEhsSmMzTjFhVzVuUTBGTVZUQXhMbU55ZERBTQpCZ05WSFJNQkFmOEVBakFBTUNNR0ExVWRFUVFjTUJxQ0dHSjZaVzVuYm1WNGRYTXhMbWxrYlhvdVpXNW5Mbk5sCmN6QU9CZ05WSFE4QkFmOEVCQU1DQmFBd0ZnWURWUjBsQVFIL0JBd3dDZ1lJS3dZQkJRVUhBd0V3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFCVlA4Z1hiVmxVTzVLNExQL1lrWXY5Ykp4MnNSODdHbjBRMi85dWZDOXpHMjJOdApYZUlCcDdqc2RDTWpNYzd4QzRWUHhTNktrSjRiUk9WZWI3V3BZK2tFbTRsSklreDNSQlhsemp4aitaT2JvVmhECjNoblNrWTRhNFFzdlJURTlucjQrYlRpSGN4ck90VGNIM244NEtFZXhjRDBwaktkN29yVWVGWHl6MjBJdTN4QjYKT0w0aHB4aEUrVkNTd1I0NXhVSFRLckt3K3pJa3VUV1NYMzBVQ0hZblExRWNTQkFKdHdLNWNkbXZtdUlXazJSdgpWSlVHSjlRQnFOV0padElUZlh3Zk4yT0ljT1VweHRPK3lXckQ5RHZGak1SYVZiUEIwMkIzNW5OcXA2QWN1N21hCnVsNjRicjlSc1grS2w2SGQ1eWltTTBieWJNTXZ4QUtpczNDaGFSMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= +kind: Secret +metadata: + name: nexus-ca diff --git a/deploy/pfte/kustomization.yaml b/deploy/pfte/kustomization.yaml new file mode 100644 index 0000000..495766b --- /dev/null +++ b/deploy/pfte/kustomization.yaml @@ -0,0 +1,26 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- ../spell-registry/ +- secretstore.yaml +- externalsecret.yaml +- missions/ses24/ +- flux-policies/imagepolicy.yaml +- flux-policies/imagerepository.yaml +- flux-policies/imageupdateautomation.yaml +- flux-policies/nexus-ca.yaml + +patchesJson6902: + - target: + kind: Ingress + name: spell-registry + version: v1 + patch: |- + - op: replace + path: "/spec/rules/0/host" + value: "spell-registry.ing-us-p.spo.internal.ses.com" + - op: replace + path: "/spec/tls/0/hosts/0" + value: "spell-registry.ing-us-p.spo.internal.ses.com" diff --git a/deploy/pfte/missions/ses24/kube-ses24-server.yaml b/deploy/pfte/missions/ses24/kube-ses24-server.yaml new file mode 100644 index 0000000..14f88a9 --- /dev/null +++ b/deploy/pfte/missions/ses24/kube-ses24-server.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-ses24-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + reconcileStrategy: Revision + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-ses24-spell-release + valuesKey: kube-ses24-values.yaml +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ses24-encryption-keys +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + dataFrom: + - extract: + key: /ses24-encryption + decodingStrategy: Auto + +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ses24-encryption-cert +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + data: + - secretKey: ses24_rsa.pem + remoteRef: + key: /ses24-encryption + property: ses24_rsa.pem diff --git a/deploy/pfte/missions/ses24/kube-ses24-values.yaml b/deploy/pfte/missions/ses24/kube-ses24-values.yaml new file mode 100644 index 0000000..2313135 --- /dev/null +++ b/deploy/pfte/missions/ses24/kube-ses24-values.yaml @@ -0,0 +1,74 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.6.5-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + env: + NODE_ID_ses24prod: "3369" + NODE_NAME_3369: ses24prod + properties: + key: value + settings: + archiva: "ses24prod" + FLEETLIMITS: limitsdb-api.limitsdb.svc:8080 + FLEETLIMITS_SEAT: VALP + SATDB: satdb-api.satdb.svc:8080 + cws: "ses24prod" + obus: obus-server.obus.svc:54407 + maxprocs: 15 + mission: SES24 + family: SBNEO + seat: "" + applicable_missions: + SBNEO: SES24 + input_folder: "{{ .Values.spell_server.mission }}/InputFiles" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles" + additionalInitSteps: "[ -d '/etc/pki/tls/private/wrapped_keys/' ] && (cd /etc/pki/tls/private/wrapped_keys/ && unzip -u ses24-keys.zip) || echo 'Could not find encryption key archive!'" + additionalVolumeMounts: + - mountPath: /etc/pki/tls/private/wrapped_keys/ + name: ses24-certs-folder + - mountPath: /etc/pki/tls/private/wrapped_keys/ses24-keys.zip + name: ses24-encryption-keys + subPath: ses24-keys.zip + - mountPath: /etc/pki/tls/ses24_keys/ses24_rsa.pem + name: ses24-encryption-cert + subPath: ses24_rsa.pem + additionalVolumes: + - name: ses24-certs-folder + emptyDir: + sizeLimit: 100Mi + medium: Memory + - name: ses24-encryption-cert + secret: + secretName: ses24-encryption-cert + optional: true + defaultMode: 0400 + - name: ses24-encryption-keys + secret: + secretName: ses24-encryption-keys + optional: true + defaultMode: 0400 +procedures: + size: 5Gi + repo_folder: SES24 + branch: OPS + server: prengrcs1.idmz.eng.ses + known_hosts: |- + propsrcs1.idmz.soc.ses,10.114.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDwEclkdaNWKtL0iuulsFrzG5VGH2y2ct7uZu755g6oKKYhyTrs9ytCdS7sK5NTwDNBjiEuywXtcZcX1RyGenxc/3YQTj0aHWC3tXfVSUcbkzKle0Tdxaq+arlI35TcLLsDnaI1OWN/TMUhGasGGSwOOp2IzJP8JAuD1/iNfzhVa1SOD39jt55oDEnncHqKS25CqBNWY1XC23+gQexB+6ai+bYZutDP+IKmnx/5quPeTnIJnXRRvGHh01oTNRTlVH5Nz4evVmSQst57vNcXzY76WwDvav2u8Gu6Yz2cyngCelXnU8Ko76sRR+VxwYeZvBDbgh0//S8aWl72KFVCH9o5 + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT + +auth: + ssh: + version: v2.6.6.5-dev # {"$imagepolicy": "spell:spell-server:tag"} + ldap: + url: ldaps://peacdc1.xeneng.ses + +service: + loadBalancerIP: 10.113.45.168 \ No newline at end of file diff --git a/deploy/pfte/missions/ses24/kustomization.yaml b/deploy/pfte/missions/ses24/kustomization.yaml new file mode 100644 index 0000000..f7b5592 --- /dev/null +++ b/deploy/pfte/missions/ses24/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- kube-ses24-server.yaml +- ses24cntl.yaml + +configMapGenerator: +- name: kube-ses24-spell-release + options: + disableNameSuffixHash: true + files: + - kube-ses24-values.yaml + + +# configurations: +# - kustomizeconfig.yaml \ No newline at end of file diff --git a/deploy/pfte/missions/ses24/kustomizeconfig.yaml b/deploy/pfte/missions/ses24/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/pfte/missions/ses24/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/pfte/missions/ses24/ses24cntl.yaml b/deploy/pfte/missions/ses24/ses24cntl.yaml new file mode 100644 index 0000000..b0aa0da --- /dev/null +++ b/deploy/pfte/missions/ses24/ses24cntl.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: ses24prod + labels: + app.kubernetes.io/name: spell-cws-scorpio-ses24cntl + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "v2.6.6.5-dev" # {"$imagepolicy": "spell:spell-server:tag"} +spec: + type: ExternalName + externalName: cws-lb.scorpio-ses24.svc.cluster.local + diff --git a/deploy/pfte/secretstore.yaml b/deploy/pfte/secretstore.yaml new file mode 100644 index 0000000..f42d8c6 --- /dev/null +++ b/deploy/pfte/secretstore.yaml @@ -0,0 +1,26 @@ +# Expected Structure: +# spell-git-access: {"id_rsa":"", "id_rsa.pub":""} +# ldap-access: {} +# +# +# +apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: spell-vault-backend +spec: + provider: + vault: + server: "https://vault.vault:8200" + path: "soe/secret/spell" + version: "v2" + caProvider: + type: "Secret" + name: "vault-ca" + key: "ca.crt" + auth: + kubernetes: + mountPath: "kubernetes" + role: "serviceaccount" + serviceAccountRef: + name: "default" diff --git a/deploy/pfts/externalsecret.yaml b/deploy/pfts/externalsecret.yaml new file mode 100644 index 0000000..7a21cf4 --- /dev/null +++ b/deploy/pfts/externalsecret.yaml @@ -0,0 +1,34 @@ +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: spell-git-access +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + data: + - secretKey: id_rsa + remoteRef: + key: /spell-git-access + property: id_rsa + - secretKey: id_rsa.pub + remoteRef: + key: /spell-git-access + property: id_rsa.pub +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ldap-secret +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + data: + - secretKey: ldap-secret + remoteRef: + key: /ldap-secret + property: ldap-secret \ No newline at end of file diff --git a/deploy/pfts/flux-policies/imagepolicy.yaml b/deploy/pfts/flux-policies/imagepolicy.yaml new file mode 100644 index 0000000..4223022 --- /dev/null +++ b/deploy/pfts/flux-policies/imagepolicy.yaml @@ -0,0 +1,27 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImagePolicy +metadata: + name: spell-registry +spec: + imageRepositoryRef: + name: spell-registry + filterTags: + pattern: '^v(?P[^.]+(\.[^.]+)?(\.[^.]+)?)\.?(?P[^-]*)' + extract: '$semver$minor' + policy: + semver: + range: x.x.x +--- +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImagePolicy +metadata: + name: spell-server +spec: + imageRepositoryRef: + name: spell-server + filterTags: + pattern: '^v(?P[^.]+(\.[^.]+)?(\.[^.]+)?)\.?(?P[^-]*)' + extract: '$semver$minor' + policy: + semver: + range: x.x.x \ No newline at end of file diff --git a/deploy/pfts/flux-policies/imagerepository.yaml b/deploy/pfts/flux-policies/imagerepository.yaml new file mode 100644 index 0000000..b18478b --- /dev/null +++ b/deploy/pfts/flux-policies/imagerepository.yaml @@ -0,0 +1,23 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageRepository +metadata: + name: spell-registry +spec: + certSecretRef: + name: nexus-ca + image: bzengnexus1.idmz.eng.ses/spell/spell-registry + interval: 1m0s + secretRef: + name: bzengnexus-secret +--- +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageRepository +metadata: + name: spell-server +spec: + certSecretRef: + name: nexus-ca + image: bzengnexus1.idmz.eng.ses/spell/spell-server + interval: 1m0s + secretRef: + name: bzengnexus-secret \ No newline at end of file diff --git a/deploy/pfts/flux-policies/imageupdateautomation.yaml b/deploy/pfts/flux-policies/imageupdateautomation.yaml new file mode 100644 index 0000000..37858a8 --- /dev/null +++ b/deploy/pfts/flux-policies/imageupdateautomation.yaml @@ -0,0 +1,43 @@ +apiVersion: image.toolkit.fluxcd.io/v1beta1 +kind: ImageUpdateAutomation +metadata: + name: spell-repo + namespace: spell +spec: + interval: 1m0s + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + git: + checkout: + ref: + branch: jgall/container-updates + commit: + author: + email: fluxcdbot@users.noreply.internal.ses.com + name: fluxcdbot + messageTemplate: | + Automated image update + + Automation name: {{ .AutomationObject }} + + Files: + {{ range $filename, $_ := .Updated.Files -}} + - {{ $filename }} + {{ end -}} + + Objects: + {{ range $resource, $_ := .Updated.Objects -}} + - {{ $resource.Kind }} {{ $resource.Name }} + {{ end -}} + + Images: + {{ range .Updated.Images -}} + - {{.}} + {{ end -}} + push: + branch: flux-image-version-update + update: + path: ./deploy/pfts + strategy: Setters \ No newline at end of file diff --git a/deploy/pfts/flux-policies/nexus-ca.yaml b/deploy/pfts/flux-policies/nexus-ca.yaml new file mode 100644 index 0000000..977340d --- /dev/null +++ b/deploy/pfts/flux-policies/nexus-ca.yaml @@ -0,0 +1,6 @@ +apiVersion: v1 +data: + caFile: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVtVENDQTRHZ0F3SUJBZ0lUTlFBQUFVeWxRZW1sNGwrcVdRQUFBQUFCVERBTkJna3Foa2lHOXcwQkFRc0YKQURBak1TRXdId1lEVlFRREV4aFRSVk5UWldOMWNtbDBlVWx6YzNWcGJtZERRVXhWTURFd0hoY05NakF3T0RBMgpNakUwTkRReldoY05NalF3T0RBMk1qRTFORFF6V2pDQmh6RUxNQWtHQTFVRUJoTUNURlV4RVRBUEJnTlZCQWNUCkNFSmxkSHBrYjNKbU1Rd3dDZ1lEVlFRS0V3TlRSVk14RERBS0JnTlZCQXNUQTFOUFJURWhNQjhHQTFVRUF4TVkKWW5wbGJtZHVaWGgxY3pFdWFXUnRlaTVsYm1jdWMyVnpNU1l3SkFZSktvWklodmNOQVFrQkZoZEZaSGRoY21RdQpRbUZ6ZEdWc2JHbEFjMlZ6TG1OdmJUQ0NBU0l3RFFZSktvWklodmNOQVFFQkJRQURnZ0VQQURDQ0FRb0NnZ0VCCkFMbndHKzdJSTBtVCtidVp5cXU4TEd4V1NWQ3hMc1JJREpsYkZwUlNUOFdPdjh5SlpzVDBQMkNwbXZlUk9XNnMKNGNuOGs4RnBZdVRBZndLRVVTM3l5S1FyTjZobjlDZllJWVcrQ1FpcVJPQldiQWVQNVFQaDJmc3o3NURjRW5DTQoxeXRtOHZTcE05ejVHSm45b1VObTVyMEh0dmFUdVNoNzgxdEt6TEVtaHhRbE9kSWdqYzluZ2lLb1RqUC9zV2tuCkM0aU0rVEVFemtKYTNZaUZacVRsT3M5Q0JNVnhmK0IxTUMzODRvcU51YmtzSDRIVUZNbnp5cm9UOGJoVThiN1cKQk4vRUk0aEV6NDZ2ODlyZHFrdHVJZGwveklKejUrMm1ZYXRpWnc1M2VYRm1pVEV5VWNqem96YWFIdEpDWG1teQpUeDJuWnNIYllDeEs3Q3R4UWtMUTJXRUNBd0VBQWFPQ0FWOHdnZ0ZiTUIwR0ExVWREZ1FXQkJTYVc0WTUzbU1TCk1hZTJodm1qYzJhbmJIRGZNVEFmQmdOVkhTTUVHREFXZ0JTa3g2TVp3SGxoejlNaHRWWUVTRnByeC83YzBEQngKQmdOVkhSOEVhakJvTUdhZ1pLQmloaTlvZEhSd09pOHZZM0pzTG5ObGN5NWpiMjB2VTBWVFUyVmpkWEpwZEhsSgpjM04xYVc1blEwRk1WVEF4TG1OeWJJWXZhSFIwY0RvdkwyTnliQzV3YTJrdWMyVnpMMU5GVTFObFkzVnlhWFI1ClNYTnpkV2x1WjBOQlRGVXdNUzVqY213d1N3WUlLd1lCQlFVSEFRRUVQekE5TURzR0NDc0dBUVVGQnpBQ2hpOW8KZEhSd09pOHZZM0pzTG5CcmFTNXpaWE12VTBWVFUyVmpkWEpwZEhsSmMzTjFhVzVuUTBGTVZUQXhMbU55ZERBTQpCZ05WSFJNQkFmOEVBakFBTUNNR0ExVWRFUVFjTUJxQ0dHSjZaVzVuYm1WNGRYTXhMbWxrYlhvdVpXNW5Mbk5sCmN6QU9CZ05WSFE4QkFmOEVCQU1DQmFBd0ZnWURWUjBsQVFIL0JBd3dDZ1lJS3dZQkJRVUhBd0V3RFFZSktvWkkKaHZjTkFRRUxCUUFEZ2dFQkFCVlA4Z1hiVmxVTzVLNExQL1lrWXY5Ykp4MnNSODdHbjBRMi85dWZDOXpHMjJOdApYZUlCcDdqc2RDTWpNYzd4QzRWUHhTNktrSjRiUk9WZWI3V3BZK2tFbTRsSklreDNSQlhsemp4aitaT2JvVmhECjNoblNrWTRhNFFzdlJURTlucjQrYlRpSGN4ck90VGNIM244NEtFZXhjRDBwaktkN29yVWVGWHl6MjBJdTN4QjYKT0w0aHB4aEUrVkNTd1I0NXhVSFRLckt3K3pJa3VUV1NYMzBVQ0hZblExRWNTQkFKdHdLNWNkbXZtdUlXazJSdgpWSlVHSjlRQnFOV0padElUZlh3Zk4yT0ljT1VweHRPK3lXckQ5RHZGak1SYVZiUEIwMkIzNW5OcXA2QWN1N21hCnVsNjRicjlSc1grS2w2SGQ1eWltTTBieWJNTXZ4QUtpczNDaGFSMD0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= +kind: Secret +metadata: + name: nexus-ca diff --git a/deploy/pfts/kustomization.yaml b/deploy/pfts/kustomization.yaml new file mode 100644 index 0000000..cc709e4 --- /dev/null +++ b/deploy/pfts/kustomization.yaml @@ -0,0 +1,26 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- ../spell-registry/ +- secretstore.yaml +- externalsecret.yaml +- missions/ses24/ +- flux-policies/imagepolicy.yaml +- flux-policies/imagerepository.yaml +- flux-policies/imageupdateautomation.yaml +- flux-policies/nexus-ca.yaml + +patchesJson6902: + - target: + kind: Ingress + name: spell-registry + version: v1 + patch: |- + - op: replace + path: "/spec/rules/0/host" + value: "spell-registry.ing-us-s.spo.internal.ses.com" + - op: replace + path: "/spec/tls/0/hosts/0" + value: "spell-registry.ing-us-s.spo.internal.ses.com" diff --git a/deploy/pfts/missions/ses24/kube-ses24-server.yaml b/deploy/pfts/missions/ses24/kube-ses24-server.yaml new file mode 100644 index 0000000..72c8409 --- /dev/null +++ b/deploy/pfts/missions/ses24/kube-ses24-server.yaml @@ -0,0 +1,51 @@ +--- +apiVersion: helm.toolkit.fluxcd.io/v2beta1 +kind: HelmRelease +metadata: + name: kube-ses24-spell-server +spec: + chart: + spec: + chart: ./deploy/spell-server + reconcileStrategy: Revision + sourceRef: + kind: GitRepository + name: spell-repo + namespace: flux-system + interval: 10m0s + valuesFrom: + - kind: ConfigMap + name: kube-ses24-spell-server + valuesKey: kube-ses24-values.yaml +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ses24-encryption-keys +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + dataFrom: + - extract: + key: /ses24-encryption + decodingStrategy: Auto + +--- +apiVersion: external-secrets.io/v1beta1 +kind: ExternalSecret +metadata: + name: ses24-encryption-cert +spec: + refreshInterval: "15s" # synchronisation + secretStoreRef: + name: spell-vault-backend + kind: SecretStore + + data: + - secretKey: ses24_rsa.pem + remoteRef: + key: /ses24-encryption + property: ses24_rsa.pem diff --git a/deploy/pfts/missions/ses24/kube-ses24-values.yaml b/deploy/pfts/missions/ses24/kube-ses24-values.yaml new file mode 100644 index 0000000..f13b594 --- /dev/null +++ b/deploy/pfts/missions/ses24/kube-ses24-values.yaml @@ -0,0 +1,65 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa +image: + registry: bzengnexus1.idmz.eng.ses +spell_server: + version: v2.6.6.5-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + env: + NODE_ID_ddcntl: "3369" + NODE_NAME_3369: ddcntl + properties: + key: value + settings: + archiva: "ddcntl" + FLEETLIMITS: limitsdb-api.limitsdb.svc:8080 + SATDB: satdb-api.satdb.svc:8080 + cws: "ddcntl" + obus: obus-server.obus.svc:54407 + maxprocs: 15 + ssh_port: 22 + mission: SES24 + family: SBNEO + seat: SIMP + applicable_missions: + SBNEO: SES24 + input_folder: "{{ .Values.spell_server.mission }}/InputFiles" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles" + additionalInitSteps: "cd /etc/pki/tls/private/wrapped_keys/ && unzip -u ses24-keys.zip " + additionalVolumeMounts: + - mountPath: /etc/pki/tls/private/wrapped_keys/ + name: ses24-certs-folder + - mountPath: /etc/pki/tls/private/wrapped_keys/ses24-keys.zip + name: ses24-encryption-keys + subPath: ses24-keys.zip + - mountPath: /etc/pki/tls/ses24_keys/ses24_rsa.pem + name: ses24-encryption-cert + subPath: ses24_rsa.pem + additionalVolumes: + - name: ses24-certs-folder + emptyDir: + sizeLimit: 100Mi + medium: Memory + - name: ses24-encryption-cert + secret: + secretName: ses24-encryption-cert + optional: false + defaultMode: 0400 + - name: ses24-encryption-keys + secret: + secretName: ses24-encryption-keys + optional: false + defaultMode: 0400 +procedures: + size: 5Gi + repo_folder: SES24 + branch: SIM + server: prengrcs1.idmz.eng.ses + known_hosts: |- + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT + diff --git a/deploy/pfts/missions/ses24/kustomization.yaml b/deploy/pfts/missions/ses24/kustomization.yaml new file mode 100644 index 0000000..bcd399d --- /dev/null +++ b/deploy/pfts/missions/ses24/kustomization.yaml @@ -0,0 +1,16 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: spell +resources: +- kube-ses24-server.yaml +- ses24cntl.yaml + +configMapGenerator: +- name: kube-ses24-spell-server + files: + - kube-ses24-values.yaml + + +configurations: +- kustomizeconfig.yaml \ No newline at end of file diff --git a/deploy/pfts/missions/ses24/kustomizeconfig.yaml b/deploy/pfts/missions/ses24/kustomizeconfig.yaml new file mode 100644 index 0000000..f6b2027 --- /dev/null +++ b/deploy/pfts/missions/ses24/kustomizeconfig.yaml @@ -0,0 +1,6 @@ +nameReference: +- kind: ConfigMap + version: v1 + fieldSpecs: + - path: spec/valuesFrom/name + kind: HelmRelease \ No newline at end of file diff --git a/deploy/pfts/missions/ses24/ses24cntl.yaml b/deploy/pfts/missions/ses24/ses24cntl.yaml new file mode 100644 index 0000000..371883e --- /dev/null +++ b/deploy/pfts/missions/ses24/ses24cntl.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: ddcntl + labels: + app.kubernetes.io/name: spell-cws-scorpio-ses24cntl + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "v2.6.6.5-dev" # {"$imagepolicy": "spell:spell-server:tag"} +spec: + type: ExternalName + externalName: cws-lb.scorpio-ses24.svc.cluster.local + diff --git a/deploy/pfts/secretstore.yaml b/deploy/pfts/secretstore.yaml new file mode 100644 index 0000000..f42d8c6 --- /dev/null +++ b/deploy/pfts/secretstore.yaml @@ -0,0 +1,26 @@ +# Expected Structure: +# spell-git-access: {"id_rsa":"", "id_rsa.pub":""} +# ldap-access: {} +# +# +# +apiVersion: external-secrets.io/v1beta1 +kind: SecretStore +metadata: + name: spell-vault-backend +spec: + provider: + vault: + server: "https://vault.vault:8200" + path: "soe/secret/spell" + version: "v2" + caProvider: + type: "Secret" + name: "vault-ca" + key: "ca.crt" + auth: + kubernetes: + mountPath: "kubernetes" + role: "serviceaccount" + serviceAccountRef: + name: "default" diff --git a/deploy/spell-registry/deployment.yaml b/deploy/spell-registry/deployment.yaml new file mode 100644 index 0000000..1cbffe0 --- /dev/null +++ b/deploy/spell-registry/deployment.yaml @@ -0,0 +1,60 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: spell-registry + labels: + app.kubernetes.io/name: spell-registry + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "v2.6.5.1-dev" # {"$imagepolicy": "spell:spell-registry:tag"} +spec: + replicas: 1 + selector: + matchLabels: + app.kubernetes.io/name: spell-registry + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "v2.6.5.1-dev" # {"$imagepolicy": "spell:spell-registry:tag"} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 0 + type: RollingUpdate + template: + metadata: + labels: + app.kubernetes.io/name: spell-registry + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "v2.6.5.1-dev" # {"$imagepolicy": "spell:spell-registry:tag"} + spec: + automountServiceAccountToken: true + serviceAccountName: spell-registry-sa + securityContext: + runAsNonRoot: true + runAsUser: 1001 + containers: + - image: bzengnexus1.idmz.eng.ses/spell/spell-registry:v2.6.5.1-dev # {"$imagepolicy": "spell:spell-registry"} + name: spell-registry + imagePullPolicy: Always + securityContext: + allowPrivilegeEscalation: false + resources: + requests: + cpu: "20m" + memory: "55M" + livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 90 + timeoutSeconds: 10 + readinessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 30 + timeoutSeconds: 10 + env: + - name: ENVVARNAME + value: ENVVARVALUE + ports: + - containerPort: 8080 + name: http diff --git a/deploy/spell-registry/ingress.yaml b/deploy/spell-registry/ingress.yaml new file mode 100644 index 0000000..233b19b --- /dev/null +++ b/deploy/spell-registry/ingress.yaml @@ -0,0 +1,26 @@ +# https://kubernetes.io/docs/concepts/services-networking/ingress/#the-ingress-resource + +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: spell-registry + labels: + app.kubernetes.io/name: spell-registry + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "1.0.0" +spec: + rules: + - host: spell-registry.ing-lu-s.spo.internal.ses.com + http: + paths: + - path: / + pathType: Prefix + backend: + service: + name: spell-registry + port: + number: 8080 + tls: + - secretName: ingress-tls + hosts: + - spell-registry.ing-lu-s.spo.internal.ses.com \ No newline at end of file diff --git a/deploy/spell-registry/kustomization.yaml b/deploy/spell-registry/kustomization.yaml new file mode 100644 index 0000000..7a2f5ff --- /dev/null +++ b/deploy/spell-registry/kustomization.yaml @@ -0,0 +1,10 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +namespace: spell +resources: +- sa.yaml +- role.yaml +- rolebinding.yaml +- deployment.yaml +- service.yaml +- ingress.yaml diff --git a/deploy/spell-registry/role.yaml b/deploy/spell-registry/role.yaml new file mode 100644 index 0000000..7ed2865 --- /dev/null +++ b/deploy/spell-registry/role.yaml @@ -0,0 +1,11 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: spell-registry +rules: +- apiGroups: [""] # "" indicates the core API group + resources: ["services"] + verbs: ["get", "watch", "list"] +- apiGroups: ["apps"] # "" indicates the core API group + resources: ["deployments"] + verbs: ["get", "watch", "list"] \ No newline at end of file diff --git a/deploy/spell-registry/rolebinding.yaml b/deploy/spell-registry/rolebinding.yaml new file mode 100644 index 0000000..c0b5a67 --- /dev/null +++ b/deploy/spell-registry/rolebinding.yaml @@ -0,0 +1,12 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: spell-registry +subjects: +- name: spell-registry-sa + kind: ServiceAccount + apiGroup: "" +roleRef: + kind: Role + name: spell-registry + apiGroup: rbac.authorization.k8s.io \ No newline at end of file diff --git a/deploy/spell-registry/sa.yaml b/deploy/spell-registry/sa.yaml new file mode 100644 index 0000000..39366ef --- /dev/null +++ b/deploy/spell-registry/sa.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: ServiceAccount + +metadata: + name: spell-registry-sa + labels: + app.kubernetes.io/name: spell-registry-sa + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "1.0.0" + +imagePullSecrets: +- name: bzengnexus-secret +- name: prengnexus-secret \ No newline at end of file diff --git a/deploy/spell-registry/service.yaml b/deploy/spell-registry/service.yaml new file mode 100644 index 0000000..2e33861 --- /dev/null +++ b/deploy/spell-registry/service.yaml @@ -0,0 +1,17 @@ +kind: Service +apiVersion: v1 +metadata: + name: spell-registry + labels: + app.kubernetes.io/name: spell-registry + app.kubernetes.io/instance: spell + app.kubernetes.io/version: "1.0.0" +spec: + selector: + app.kubernetes.io/name: spell-registry + app.kubernetes.io/instance: spell + type: ClusterIP + ports: + - name: http + port: 8080 + targetPort: 8080 \ No newline at end of file diff --git a/deploy/spell-server/.helmignore b/deploy/spell-server/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/deploy/spell-server/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/deploy/spell-server/Chart.yaml b/deploy/spell-server/Chart.yaml new file mode 100644 index 0000000..d7befe4 --- /dev/null +++ b/deploy/spell-server/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: spell-server +description: Spell Server Helm Chart + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 1.0.59 +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "2.6.10" diff --git a/deploy/spell-server/configs/context.xml b/deploy/spell-server/configs/context.xml new file mode 100644 index 0000000..e488817 --- /dev/null +++ b/deploy/spell-server/configs/context.xml @@ -0,0 +1,112 @@ +{{- define "spell-server.input-folder" -}} +{{- if .Values.spell_server.input_folder -}} +{{ tpl .Values.spell_server.input_folder . }} +{{- else -}} +{{ .Values.spell_server.mission }}/InputFiles +{{- end -}} +{{- end -}} +{{- define "spell-server.output-folder" -}} +{{- if .Values.spell_server.output_folder -}} +{{ tpl .Values.spell_server.output_folder . }} +{{- else -}} +{{ .Values.spell_server.mission }}/OutputFiles +{{- end -}} +{{- end }} + + + + + + {{ .Values.spell_server.mission }} Spacecraft + Context for {{ .Values.spell_server.mission }} + + + + + + {{ .Values.spell_server.driver.name}} + + + + + + {{ .Values.spell_server.spacecraft | default .Values.spell_server.mission }} + {{ .Values.spell_server.mission }} + {{ .Values.spell_server.family }} + {{ .Values.spell_server.driver.name}} + + + + + + $SPELL_DATA/{{ .Values.spell_server.mission }}/Procedures + $SPELL_DATA/{{ .Values.spell_server.mission }}/UserLib + + $SPELL_DATA/{{- include "spell-server.output-folder" . }} + $SPELL_DATA/{{- include "spell-server.input-folder" . }} + + + + + +{{- range $key, $value := .Values.spell_server.driver.settings }} + {{ tpl $value $ }} +{{- end}} + + {{ .Values.spell_server.seat }} + + + + + + + + 0 + 60 + True + False + False + 5 + DISABLED + DISABLED + ENABLED + True + + + + + + + {{- include "spell-server.input-folder" . }}/SpacecraftDB + {{- include "spell-server.input-folder" . }}/GroundDB + {{- include "spell-server.input-folder" . }}/ManeuvreMessage + {{- include "spell-server.input-folder" . }}/User + {{- include "spell-server.output-folder" . }}/AsRun + {{- include "spell-server.output-folder" . }}/Warmstart + {{- include "spell-server.input-folder" . }}/User + {{- include "spell-server.input-folder" . }}/EphemerisFile + + SCDB + GDB + + + + + + + + + 0.0 + 140 + + + 600 + 0.0 + + + False + 0.0 + + + + diff --git a/deploy/spell-server/configs/driver.xml b/deploy/spell-server/configs/driver.xml new file mode 100644 index 0000000..4e77b25 --- /dev/null +++ b/deploy/spell-server/configs/driver.xml @@ -0,0 +1,12 @@ + + {{ .Values.spell_server.driver.name | upper}} Driver + TM,TC,EV,RSC,TIME,RNG,TASK,MEM + {{ .Values.spell_server.maxprocs | default 15}} + {{ tpl (.Values.spell_server.driver.lib | default "$SPELL_DRIVER/lib") $ }} + {{ tpl (.Values.spell_server.driver.path | default "$SPELL_DRIVER/drivers") $ }} + +{{- range $key, $value := .Values.spell_server.driver.properties }} + {{ tpl $value $ }} +{{- end}} + + diff --git a/deploy/spell-server/configs/language.xml b/deploy/spell-server/configs/language.xml new file mode 100644 index 0000000..e661f57 --- /dev/null +++ b/deploy/spell-server/configs/language.xml @@ -0,0 +1,236 @@ + + + ABORT|SKIP|RECHECK|CANCEL + True + ENG + 120 + 3 + 0 + False + None + True + ABORT|SKIP|RECHECK|CANCEL + NOACTION + False + False + + + + ABORT|SKIP|RESEND|CANCEL + True + 0 + 120 + 0 + None + False + True + ABORT|SKIP|RESEND|CANCEL + NOACTION + False + False + + + + ABORT|SKIP|REPEAT|CANCEL + True + INFORMATION + SCOPE_PROC + TIME_MODE_LIVE + True + NOACTION + NOACTION + "" + + + + ABORT|SKIP|REPEAT|CANCEL + True + True + + + + ABORT|SKIP|REPEAT|CANCEL + True + True + + + + ABORT|SKIP|REPEAT|CANCEL + True + + + + ABORT|SKIP|REPEAT|CANCEL + True + + + + ABORT|SKIP|REPEAT + False + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|RECHECK|SKIP|CANCEL + ABORT|RECHECK|SKIP|CANCEL + True + None + False + + + + ABORT|REPEAT|RESEND|RECHECK|SKIP|CANCEL + ABORT|REPEAT|RESEND|RECHECK|SKIP|CANCEL + True + 1 + + + + ABORT|SKIP|CANCEL|REPEAT + None + + + + REPEAT|ABORT + True + OK_CANCEL + 60 + + + + None + ABORT|SKIP|CANCEL + True + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + 6 + ABORT|SKIP + + + + 6 + "ps" + + + + 3 + + + + True + True + True + ABORT|SKIP|REPEAT|CANCEL + True + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT|CANCEL + + + + ABORT|SKIP|REPEAT + + + + ABORT|SKIP|REPEAT + + + + diff --git a/deploy/spell-server/configs/log.properties b/deploy/spell-server/configs/log.properties new file mode 100644 index 0000000..0a2e97d --- /dev/null +++ b/deploy/spell-server/configs/log.properties @@ -0,0 +1,4 @@ +log4cplus.rootLogger=STDOUT +log4cplus.appender.STDOUT=log4cplus::ConsoleAppender +log4cplus.appender.STDOUT.layout=log4cplus::PatternLayout +log4cplus.appender.STDOUT.layout.ConversionPattern=[ %d{ %Y/%m/%d %H:%M:%S.%q} ] [%-5p] [ %h ] [ %30.30l ]: %m%n diff --git a/deploy/spell-server/configs/scheduler_wasp.xml b/deploy/spell-server/configs/scheduler_wasp.xml new file mode 100644 index 0000000..35b3fab --- /dev/null +++ b/deploy/spell-server/configs/scheduler_wasp.xml @@ -0,0 +1,13 @@ + + WASP activity scheduler + + +{{- range $idx,$value := .Values.spell_addons.pace.pharos }} + {{ $value }} +{{- end }} + + + 15 + + /home/spell/.wasp + diff --git a/deploy/spell-server/configs/server.xml b/deploy/spell-server/configs/server.xml new file mode 100644 index 0000000..968ad6c --- /dev/null +++ b/deploy/spell-server/configs/server.xml @@ -0,0 +1,84 @@ + + + + + + + ENABLED + PROC + + 9 + + 20000 + + 20000 + + 30000 + + 10000 + + 60000 + + + + + + + language.xml + + + + + + + 19823 + 19723 + + + + + + + $SPELL_HOME/bin/SPELL-Executor + false + {{ .Values.spell_server.context_mission | default .Values.spell_server.mission }} + + + + + + + 0 + 3600 + True + False + False + 5 + DISABLED + DISABLED + + + + + + + driver.xml + + + + + + + context.xml + + + + + + +{{- range $key, $value := .Values.spell_server.applicable_missions }} + {{ tpl $value $ }} +{{- end}} + + + diff --git a/deploy/spell-server/templates/NOTES.txt b/deploy/spell-server/templates/NOTES.txt new file mode 100644 index 0000000..d392cdf --- /dev/null +++ b/deploy/spell-server/templates/NOTES.txt @@ -0,0 +1,5 @@ +1. Get the application URL by running these commands: + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "spell-server.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "spell-server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} diff --git a/deploy/spell-server/templates/_helpers.tpl b/deploy/spell-server/templates/_helpers.tpl new file mode 100644 index 0000000..fd2fab7 --- /dev/null +++ b/deploy/spell-server/templates/_helpers.tpl @@ -0,0 +1,60 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "spell-server.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "spell-server.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "spell-server.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "spell-server.labels" -}} +helm.sh/chart: {{ include "spell-server.chart" . }} +{{ include "spell-server.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "spell-server.selectorLabels" -}} +app.kubernetes.io/name: {{ include "spell-server.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +spell-mission: {{ .Values.spell_server.mission | quote }} +spell-seat: {{ .Values.spell_server.seat | quote }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "spell-server.serviceAccountName" -}} +{{- default "spell-sa" .Values.serviceAccountName }} +{{- end }} diff --git a/deploy/spell-server/templates/cm.yaml b/deploy/spell-server/templates/cm.yaml new file mode 100644 index 0000000..1448ccf --- /dev/null +++ b/deploy/spell-server/templates/cm.yaml @@ -0,0 +1,28 @@ + +kind: ConfigMap +apiVersion: v1 +metadata: + name: {{ include "spell-server.fullname" . }} + labels: + {{- include "spell-server.labels" . | nindent 4 }} +data: + context.xml : | + {{ tpl (.Files.Get "configs/context.xml") . | indent 4 | trim}} + + driver.xml : | + {{ tpl (.Files.Get "configs/driver.xml") . | indent 4 | trim}} + + language.xml : | + {{ tpl (.Files.Get "configs/language.xml") . | indent 4 | trim}} + + server.xml : | + {{ tpl (.Files.Get "configs/server.xml") . | indent 4 | trim}} + + log.properties : | + {{ tpl (.Files.Get "configs/log.properties") . | indent 4 | trim}} + + scheduler_wasp.xml: | + {{ tpl (.Files.Get "configs/scheduler_wasp.xml") . | indent 4 | trim}} + + known_hosts: | + {{ .Values.procedures.known_hosts | default "" | indent 4 | trim }} \ No newline at end of file diff --git a/deploy/spell-server/templates/deployment.yaml b/deploy/spell-server/templates/deployment.yaml new file mode 100644 index 0000000..1162433 --- /dev/null +++ b/deploy/spell-server/templates/deployment.yaml @@ -0,0 +1,410 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "spell-server.fullname" . }} + labels: + {{- include "spell-server.labels" . | nindent 4 }} +spec: + replicas: 1 + selector: + matchLabels: + {{- include "spell-server.selectorLabels" . | nindent 6 }} + strategy: + rollingUpdate: + maxSurge: 1 + maxUnavailable: 1 + type: RollingUpdate + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "spell-server.selectorLabels" . | nindent 8 }} + spec: + serviceAccountName: {{ .Values.serviceAccountName | default "sa-spell" }} + securityContext: + fsGroup: 1000 + initContainers: + - name: spell-driver-init + resources: + requests: + cpu: "20m" + memory: "55M" + limits: + cpu: "1" + memory: "1Gi" + image: {{ .Values.image.registry }}/spell/spell-driver-ubuntu-{{ .Values.spell_server.driver.name }}:{{ .Values.spell_server.version }} + command: ["cp", "-r", "/home/spell/SPELL/", "/home/spell/SPELL_DRIVER"] + imagePullPolicy: {{ .Values.image.pullPolicy | default "Always"}} + volumeMounts: + - mountPath: /home/spell/SPELL_DRIVER + name: spell-driver + containers: + - image: {{ .Values.image.registry }}/spell/spell-server-ubuntu:{{ .Values.spell_server.version }} + name: spell-server + imagePullPolicy: {{ .Values.image.pullPolicy | default "Always"}} + resources: + requests: + cpu: "20m" + memory: "55M" + limits: + cpu: "3" + memory: "4Gi" + args: + - {{ .Values.spell_server.mission }} + securityContext: + capabilities: + drop: + - all + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1000 + lifecycle: + preStop: + exec: + command: + - /bin/sh + - -c + - "pgrep SPELL | xargs -I{} /bin/sh -c 'echo \\\\$(ps -p {} -o comm=): >> $SPELL_LOG/SPELL_Restart_$(date +\"%Y%m%d%H%M\").log && gdb -p {} -ex \"thread apply all bt\" >> $SPELL_LOG/SPELL_Restart_$(date +\"%Y%m%d%H%M\").log 2>&1 '" + + postStart: + exec: + command: + - "/bin/sh" + - "-c" + - "mkdir -p /home/spell/procs/DEPLOYMENT/ && cp /home/spell/SPELL/release.nfo /home/spell/procs/DEPLOYMENT ; {{ .Values.spell_server.additionalInitSteps }}" + env: + - name: SPELL_MISSION + value: {{ .Values.spell_server.mission }} + - name: SPELL_DRIVER + value: /home/spell/SPELL_DRIVER/SPELL + - name: SPELL_DATA + value: /home/spell/procs/SPELL_PROCS +{{- range $key, $value := .Values.spell_server.driver.env }} + - name: {{$key}} + value: {{$value | quote}} +{{- end }} + startupProbe: + exec: + command: + - /home/spell/SPELL/bin/SPELL-Ping + failureThreshold: 20 + timeoutSeconds: 5 + initialDelaySeconds: 30 + periodSeconds: 30 + livenessProbe: + exec: + command: + - /home/spell/SPELL/bin/SPELL-Ping + failureThreshold: 3 + timeoutSeconds: 15 + periodSeconds: 15 + volumeMounts: + {{- with .Values.spell_server.additionalVolumeMounts }} + {{- toYaml . | nindent 8 }} + {{- end }} + - mountPath: /home/spell/SPELL_DRIVER + name: spell-driver + - mountPath: /home/spell/SPELL_CONFIG/contexts/context.xml + name: {{ include "spell-server.fullname" . }} + subPath: context.xml + - mountPath: /home/spell/SPELL_CONFIG/server/server_{{ .Values.spell_server.mission }}.xml + name: {{ include "spell-server.fullname" . }} + subPath: server.xml + - mountPath: /home/spell/SPELL_CONFIG/spell/language.xml + name: {{ include "spell-server.fullname" . }} + subPath: language.xml + - mountPath: /home/spell/SPELL_CONFIG/spell/log.properties + name: {{ include "spell-server.fullname" . }} + subPath: log.properties + - mountPath: /home/spell/SPELL_CONFIG/spell/driver.xml + name: {{ include "spell-server.fullname" . }} + subPath: driver.xml + - mountPath: /home/spell/services + name: {{ include "spell-server.fullname" . }} + subPath: services + + - mountPath: /home/spell/procs + name: spell-procs + + - name: fileds + image: {{ .Values.image.registry }}/{{ .Values.fileds.image }}:{{ .Values.fileds.version }} + imagePullPolicy: {{ .Values.image.pullPolicy | default "Always"}} + args: + - "{{ .Values.procedures.server }}" + - "{{ .Values.spell_server.mission }}" + - "{{ .Values.procedures.repo_folder }}" + - "{{ .Values.procedures.branch }}" + resources: + requests: + cpu: "20m" + memory: "55M" + limits: + cpu: "1" + memory: "2Gi" + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1000 + capabilities: + drop: ["all"] + startupProbe: + exec: + command: + - sh + - -c + - test -d /home/spell/SPELL_PROCS/{{ .Values.spell_server.mission }}/Procedures || (echo "Checkout still in Progress" && exit 1) + failureThreshold: 20 + initialDelaySeconds: 30 + periodSeconds: 30 + volumeMounts: + - name: git-ssh-key + mountPath: "/home/dt/.ssh/id_rsa" + subPath: "id_rsa" + readOnly: true + - name: git-ssh-key + mountPath: "/home/dt/.ssh/id_rsa.pub" + subPath: "id_rsa.pub" + readOnly: true + - name: git-ssh-key + mountPath: "/home/dt/.ssh/config" + subPath: "config" + readOnly: true + + - mountPath: /home/spell/ + name: spell-procs + - name: dispatch-folder + mountPath: /home/dispatch/ + - name: dispatch-log-folder + mountPath: /home/dt/fileds/log/dispatch + - image: "{{ .Values.image.registry }}/{{ .Values.auth.ssh.image }}:{{ .Values.auth.ssh.version }}" + name: spell-ssh + imagePullPolicy: {{ .Values.image.pullPolicy | default "Always"}} + resources: + requests: + cpu: "20m" + memory: "55M" + limits: + cpu: "1" + memory: "2Gi" + securityContext: + capabilities: + add: ["SYS_CHROOT", "CAP_AUDIT_WRITE"] + readinessProbe: + tcpSocket: + port: 8422 + initialDelaySeconds: 5 + periodSeconds: 10 + + env: + - name: SPELL_MISSION + value: {{ .Values.spell_server.mission }} + - name: LDAP_URL + value: {{ .Values.auth.ssh.ldap.url }} + - name: LDAP_BIND_DN + value: {{ .Values.auth.ssh.ldap.binddn }} + - name: SSH_PORT + value: "8422" + - name: SPELL_HOME + value: /home/spell/ + ports: + - containerPort: 8422 + name: ssh + volumeMounts: + {{- with .Values.procedures.additionalVolumeMounts }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: {{ .Values.auth.ssh.ldap.bindpwSecret }} + mountPath: /etc/ldap_password + subPath: ldap-secret + - mountPath: /home/spell + name: spell-procs +{{- if .Values.spell_addons.logrotate.enabled }} + - image: {{ .Values.image.registry }}/{{ .Values.spell_addons.logrotate.image }}:{{ .Values.spell_addons.logrotate.version }} + name: logrotate + imagePullPolicy: {{ .Values.image.pullPolicy | default "Always"}} + resources: + requests: + cpu: "20m" + memory: "55M" + limits: + cpu: "2" + memory: "1000M" + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1000 + capabilities: + drop: ["all"] + env: + - name: SPELL_MISSION + value: {{ .Values.spell_server.mission }} + - name: SPELL_DRIVER + value: /home/spell/SPELL_DRIVER/SPELL + - name: SPELL_DATA + value: /home/spell/procs/SPELL_PROCS +{{- range $key, $value := .Values.spell_addons.logrotate.env }} + - name: {{$key}} + value: {{$value | quote}} +{{- end }} + volumeMounts: + {{- with .Values.spell_addons.logrotate.additionalVolumeMounts }} + {{- toYaml . | nindent 10 }} + {{- end }} + - mountPath: /home/spell/SPELL_DRIVER + name: spell-driver + - mountPath: /home/spell/SPELL_CONFIG/contexts/context_{{ .Values.spell_server.mission }}.xml + name: {{ include "spell-server.fullname" . }} + subPath: context.xml + - mountPath: /home/spell/SPELL_CONFIG/contexts/context.xml + name: {{ include "spell-server.fullname" . }} + subPath: context.xml + - mountPath: /home/spell/SPELL_CONFIG/server/server_{{ .Values.spell_server.mission }}.xml + name: {{ include "spell-server.fullname" . }} + subPath: server.xml + - mountPath: /home/spell/SPELL_CONFIG/spell/language.xml + name: {{ include "spell-server.fullname" . }} + subPath: language.xml + - mountPath: /home/spell/SPELL_CONFIG/spell/log.properties + name: {{ include "spell-server.fullname" . }} + subPath: log.properties + - mountPath: /home/spell/SPELL_CONFIG/spell/scheduler_wasp.xml + name: {{ include "spell-server.fullname" . }} + subPath: scheduler_wasp.xml + - mountPath: /home/spell/SPELL_CONFIG/spell/driver.xml + name: {{ include "spell-server.fullname" . }} + subPath: driver.xml + - mountPath: /home/spell/services + name: {{ include "spell-server.fullname" . }} + subPath: services + - mountPath: /home/spell/procs + name: spell-procs +{{- end }} +{{- if .Values.spell_addons.pace.enabled }} + - image: {{ .Values.image.registry }}/{{ .Values.spell_addons.pace.image }}:{{ .Values.spell_addons.pace.version }} + name: pace + imagePullPolicy: {{ .Values.image.pullPolicy | default "Always"}} + resources: + requests: + cpu: "20m" + memory: "55M" + limits: + cpu: "1" + memory: "500M" + securityContext: + allowPrivilegeEscalation: false + runAsNonRoot: true + runAsUser: 1001 + runAsGroup: 1000 + capabilities: + drop: ["all"] + args: +{{- range $idx,$value := .Values.spell_addons.pace.args }} + - {{ $value }} +{{- end }} + env: + - name: SPELL_MISSION + value: {{ .Values.spell_server.mission }} + - name: SPELL_DRIVER + value: /home/spell/SPELL_DRIVER/SPELL + - name: SPELL_DATA + value: /home/spell/procs/SPELL_PROCS +{{- if .Values.spell_addons.pace.useDriver }} +{{- range $key, $value := .Values.spell_server.driver.env }} + - name: {{$key}} + value: {{$value | quote}} +{{- end }} +{{- end }} +{{- range $key, $value := .Values.spell_addons.pace.env }} + - name: {{$key}} + value: {{$value | quote}} +{{- end }} + volumeMounts: + {{- with .Values.spell_addons.pace.additionalVolumeMounts }} + {{- toYaml . | nindent 10 }} + {{- end }} +{{- if .Values.spell_addons.pace.useDriver }} + - mountPath: /home/spell/SPELL_DRIVER + name: spell-driver +{{- end }} + - mountPath: /home/spell/SPELL_CONFIG/contexts/context_{{ .Values.spell_server.mission }}.xml + name: {{ include "spell-server.fullname" . }} + subPath: context.xml + - mountPath: /home/spell/SPELL_CONFIG/contexts/context.xml + name: {{ include "spell-server.fullname" . }} + subPath: context.xml + - mountPath: /home/spell/SPELL_CONFIG/server/server_{{ .Values.spell_server.mission }}.xml + name: {{ include "spell-server.fullname" . }} + subPath: server.xml + - mountPath: /home/spell/SPELL_CONFIG/spell/language.xml + name: {{ include "spell-server.fullname" . }} + subPath: language.xml + - mountPath: /home/spell/SPELL_CONFIG/spell/log.properties + name: {{ include "spell-server.fullname" . }} + subPath: log.properties + - mountPath: /home/spell/SPELL_CONFIG/spell/scheduler_wasp.xml + name: {{ include "spell-server.fullname" . }} + subPath: scheduler_wasp.xml + - mountPath: /home/spell/SPELL_CONFIG/spell/driver.xml + name: {{ include "spell-server.fullname" . }} + subPath: driver.xml + - mountPath: /home/spell/services + name: {{ include "spell-server.fullname" . }} + subPath: services +{{- if .Values.spell_addons.pace.mountProcs }} + - mountPath: /home/spell/procs + name: spell-procs +{{- end }} + +{{- end }} + volumes: + {{- with .Values.spell_addons.pace.additionalVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.spell_server.additionalVolumes }} + {{- toYaml . | nindent 8 }} + {{- end }} + - name: dispatch-folder + emptyDir: + sizeLimit: 100Mi + - name: dispatch-log-folder + emptyDir: + sizeLimit: 1Gi + - name: sssd-cache-folder + emptyDir: + sizeLimit: 1Gi + - name: git-ssh-key + secret: + secretName: spell-git-access + optional: false + defaultMode: 0400 + - name: spell-procs + persistentVolumeClaim: + claimName: {{ include "spell-server.fullname" . }}-pvc + - name: {{ .Values.auth.ssh.ldap.bindpwSecret }} + secret: + secretName: {{ .Values.auth.ssh.ldap.bindpwSecret }} + optional: false + - name: {{ include "spell-server.fullname" . }} + configMap: + name: {{ include "spell-server.fullname" . }} + - name: spell-driver + emptyDir: {} + restartPolicy: Always + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} \ No newline at end of file diff --git a/deploy/spell-server/templates/pvc.yaml b/deploy/spell-server/templates/pvc.yaml new file mode 100644 index 0000000..6503672 --- /dev/null +++ b/deploy/spell-server/templates/pvc.yaml @@ -0,0 +1,13 @@ +kind: PersistentVolumeClaim +apiVersion: v1 +metadata: + name: {{ include "spell-server.fullname" . }}-pvc + labels: + {{- include "spell-server.labels" . | nindent 4 }} +spec: + accessModes: + - ReadWriteOnce + storageClassName: {{ .Values.procedures.storageClass }} + resources: + requests: + storage: {{ .Values.procedures.size}} \ No newline at end of file diff --git a/deploy/spell-server/templates/service.yaml b/deploy/spell-server/templates/service.yaml new file mode 100644 index 0000000..c27d001 --- /dev/null +++ b/deploy/spell-server/templates/service.yaml @@ -0,0 +1,24 @@ +kind: Service +apiVersion: v1 +metadata: + name: {{ include "spell-server.fullname" . }} + labels: + {{- include "spell-server.labels" . | nindent 4 }} + annotations: + {{- toYaml .Values.service.annotations | nindent 4 }} +spec: + selector: + {{- include "spell-server.selectorLabels" . | nindent 4 }} + + {{- if and (eq .Values.service.type "LoadBalancer") (not (empty .Values.service.loadBalancerIP)) }} + loadBalancerIP: {{ .Values.service.loadBalancerIP }} + {{- end }} + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + nodePort: {{ .Values.service.nodePort }} + protocol: TCP + name: {{ .Values.service.name }} + targetPort: 8422 + + \ No newline at end of file diff --git a/deploy/spell-server/templates/tests/test-connection.yaml b/deploy/spell-server/templates/tests/test-connection.yaml new file mode 100644 index 0000000..51ab4f6 --- /dev/null +++ b/deploy/spell-server/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "spell-server.fullname" . }}-test-connection" + labels: + {{- include "spell-server.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "spell-server.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/deploy/spell-server/values.yaml b/deploy/spell-server/values.yaml new file mode 100644 index 0000000..0bbdbef --- /dev/null +++ b/deploy/spell-server/values.yaml @@ -0,0 +1,99 @@ +# Default values for spell-server. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +serviceAccountName: spell-sa + +image: + registry: bzengnexus1.idmz.eng.ses + +# tolerations: +# - key: agentpool +# operator: Equal +# value: escorpio +# effect: NoSchedule + +service: + annotations: {} + type: LoadBalancer + port: 22 + name: ssh + +spell_addons: + logrotate: + image: spell/logrotate-ubuntu + version: 2.6.10 + enabled: true + env: + LOGROTATE_INTERVAL: "daily" + DELAYED_START: 30 + additionalVolumeMounts: [] + additionalVolumes: [] + pace: + image: spell/pace + enabled: false + version: 2.0.7 + useDriver: true + mountProcs: true + env: {} + additionalVolumeMounts: [] + # - mountPath: /home/spell/.wasp + # name: wasp-secret + # subPath: ".wasp" + # readonly: true + additionalVolumes: [] + # - name: wasp-secret + # secret: + # secretName: wasp-secret + # optional: true + # defaultMode: 0400 + args: [] + # - "ZMP01" + pharos: [] + +auth: + mode: ssh #clear/ssh + ssh: + version: v2.6.8-dev + image: spell/ssh-server-ubuntu + ldap: + url: ldaps://beacdc1.xeneng.ses + binddn: CN=ldap,OU=G_SYS,OU=ses-users,DC=xeneng,DC=ses + bindpwSecret: ldap-secret + +fileds: + version: 1.3.7-dev + image: spell/fileds-ubuntu + +spell_server: + version: v2.6.8-dev # {"$imagepolicy": "spell:spell-server:tag"} + driver: + name: scorpio + lib: "$SPELL_DRIVER/drivers/{{ .Values.spell_server.driver.name }}/interface" + env: {} + properties: {} + settings: {} + #cws: "{{ .Values.spell_server.seat | lower }}cntl.scorpio.eng.ses" + #archiva: "{{ .Values.spell_server.seat | lower }}dbs01.scorpio.eng.ses" + #obus: bfts-ob.eng.ses:54407 + maxprocs: 15 + mission: ZMP01 + spacecraft: null + family: BSS702SP + seat: S3 + applicable_missions: {} + input_folder: "{{ .Values.spell_server.mission }}/InputFiles/{{ .Values.spell_server.mission }}" + output_folder: "{{ .Values.spell_server.mission }}/OutputFiles/{{ .Values.spell_server.mission }}" + additionalVolumeMounts: [] + additionalVolumes: [] + additionalInitSteps: "" +procedures: + additionalVolumeMounts: [] + storageClass: null + size: 5Gi + repo_folder: MPOWER + branch: SIM + server: bzengrcs1.idmz.eng.ses + known_hosts: |- + bzengrcs1.idmz.eng.ses,10.35.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA3Hjvq/uZqY8nxGeB8sQiq5sLaq4T8h3BTnHkElLjq1C6lJ+5hV9OgiYdf9NCMwZuES5ENQAFqB2GnHjDc0X4sz6K/tk3pqGXQ/wdwVA+ZylBunpv5CJqKyQO+mu+pbke9yhZNuGZeGi7x8cmhQgZ5xOYQdW2ijMecQ/hxO2+598hiIB1nsCWcVnX4zV07cpgViRkZd/cA8J/TekYG3058fDekPTtVwOVVnG25W1jSVzY/wuV0pSwraRCesiLkE4XSF59RDMn0HEqLgy6iy0Gj5nWY4cJ3Fct/VyDjqZhVM/Lr6d6avo3Io7c7rUM055fKfu2Xh8iMluYMK7PSrwMDQ== + prengrcs1.idmz.eng.ses,10.113.65.65 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCkcQiI+f8S43Yy8lyZ+mbCbJdMi4sH19yXXJ6BKemsm2wiVcX/q+cYrsjYLCayd8/AJdS4tBzGZegXgQ3YSnhzuoNlKFg4UqRnnsU4ynwa+VU38dbxrSTZKUhumFYvZ749d55GIwHZnardMb0ZROYO+mga8beYRbe7bWLlqCUpRgsNkz8wuzS2fd/u+J9T+I7Z7iYNpD91IIeqlch6EXPix4piD/beoD4W+1tXSd6UKDEARANqAsF+mdv6OfOVsfzV06A65+XuoR8gZn0W6ieojm6CdlIU/8i+q7EyVf0q9TMjPCVEvZOdNI1x9a06fezQdQpETDtVuVJDbr/TpDRT \ No newline at end of file diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000..e69de29 diff --git a/docker/Makefile b/docker/Makefile new file mode 100644 index 0000000..41f481e --- /dev/null +++ b/docker/Makefile @@ -0,0 +1,14 @@ +# default: all + +REPO := bzengnexus1.idmz.eng.ses/ +# TAG := 1.0.0 + +TOPTARGETS := all push + +SUBDIRS := $(wildcard */.) + +$(TOPTARGETS): $(SUBDIRS) +$(SUBDIRS): + $(MAKE) -C $@ $(MAKECMDGOALS) REPO=$(REPO) + +.PHONY: $(TOPTARGETS) $(SUBDIRS) diff --git a/docker/drivers/Makefile b/docker/drivers/Makefile new file mode 100644 index 0000000..bc563b8 --- /dev/null +++ b/docker/drivers/Makefile @@ -0,0 +1,13 @@ +# default: all + +REPO := bzengnexus1.idmz.eng.ses/ + +TOPTARGETS := all push + +SUBDIRS := $(wildcard */.) + +$(TOPTARGETS): $(SUBDIRS) +$(SUBDIRS): + $(MAKE) -C $@ $(MAKECMDGOALS) REPO=$(REPO) + +.PHONY: $(TOPTARGETS) $(SUBDIRS) diff --git a/docker/drivers/example/Dockerfile b/docker/drivers/example/Dockerfile new file mode 100644 index 0000000..79391d2 --- /dev/null +++ b/docker/drivers/example/Dockerfile @@ -0,0 +1,26 @@ +ARG BUILD_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 +ARG RUNTIME_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 + +FROM ${BUILD_IMAGE} as builder +USER spell +WORKDIR /home/spell/SPELL_BUILD + +COPY --chown=spell . . +RUN mkdir -p /home/spell/SPELL && chown spell:spell /home/spell/SPELL +ENV SPELL_COTS=/home/spell/SPELL_COTS + +RUN ./build_driver.sh /home/spell/SPELL example "" "-DWITH_DEBUG -DWITH_KUBERNETES" + +FROM ${RUNTIME_IMAGE} + +COPY --chown=spell --from=builder /home/spell/SPELL /home/spell/SPELL + +ENV SPELL_HOME=/home/spell/SPELL +ENV SPELL_CONFIG=/home/spell/SPELL_CONFIG +ENV SPELL_DATA=/home/spell/procs/SPELL_PROCS +ENV SPELL_LOG=/home/spell/procs/SPELL_LOG +ENV PATH=$SPELL_HOME/bin:$SPELL_COTS/bin:$PATH +ENV SPELL_DRIVER=/home/spell/SPELL_DRIVER/SPELL +ENV LD_LIBRARY_PATH=$SPELL_HOME/lib:$SPELL_DRIVER/lib:$SPELL_COTS/lib + +USER spell diff --git a/docker/drivers/example/Makefile b/docker/drivers/example/Makefile new file mode 100644 index 0000000..47e3d50 --- /dev/null +++ b/docker/drivers/example/Makefile @@ -0,0 +1,20 @@ +default: build + +REPO := bzengnexus1.idmz.eng.ses/ +NAME := spell/spell-driver-ubuntu-example +COMMIT := $(shell git rev-parse --short HEAD) +TAG := $(shell git describe --abbrev=0 --tags ${COMMIT} 2>/dev/null || true) +TAG_COMMIT := $(shell git rev-list -n 1 --abbrev-commit ${TAG}) + +VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(VERSION)-dev +endif +TAG := $(VERSION) + +build: + docker build --build-arg http_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg https_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg no_proxy=10.113.66.135,10.35.66.136,localhost,eng.ses,xeneng.ses,az.ses -t $(REPO)$(NAME):v$(TAG) ../../../ -f ./Dockerfile + +push: + docker push $(REPO)$(NAME):v$(TAG) + diff --git a/docker/drivers/fuzzer/Dockerfile b/docker/drivers/fuzzer/Dockerfile new file mode 100644 index 0000000..ff3de18 --- /dev/null +++ b/docker/drivers/fuzzer/Dockerfile @@ -0,0 +1,25 @@ +ARG BUILD_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 +ARG RUNTIME_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 + +FROM ${BUILD_IMAGE} as builder +USER spell +WORKDIR /home/spell/SPELL_BUILD + +COPY --chown=spell . . +RUN mkdir -p /home/spell/SPELL && chown spell:spell /home/spell/SPELL +ENV SPELL_COTS=/home/spell/SPELL_COTS + +RUN ./build_driver.sh /home/spell/SPELL fuzzer "" "-DWITH_DEBUG -DWITH_KUBERNETES" + +FROM ${RUNTIME_IMAGE} +COPY --chown=spell --from=builder /home/spell/SPELL /home/spell/SPELL + +ENV SPELL_HOME=/home/spell/SPELL +ENV SPELL_CONFIG=/home/spell/SPELL_CONFIG +ENV SPELL_DATA=/home/spell/procs/SPELL_PROCS +ENV SPELL_LOG=/home/spell/procs/SPELL_LOG +ENV PATH=$SPELL_HOME/bin:$SPELL_COTS/bin:$PATH +ENV SPELL_DRIVER=/home/spell/SPELL_DRIVER/SPELL +ENV LD_LIBRARY_PATH=$SPELL_HOME/lib:$SPELL_DRIVER/lib:$SPELL_COTS/lib + +USER spell diff --git a/docker/drivers/fuzzer/Makefile b/docker/drivers/fuzzer/Makefile new file mode 100644 index 0000000..d44ccf0 --- /dev/null +++ b/docker/drivers/fuzzer/Makefile @@ -0,0 +1,20 @@ +default: build + +REPO := bzengnexus1.idmz.eng.ses/ +NAME := spell/spell-driver-ubuntu-fuzzer +COMMIT := $(shell git rev-parse --short HEAD) +TAG := $(shell git describe --abbrev=0 --tags ${COMMIT} 2>/dev/null || true) +TAG_COMMIT := $(shell git rev-list -n 1 --abbrev-commit ${TAG}) + +VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(VERSION)-dev +endif +TAG := $(VERSION) + +build: + docker build --build-arg http_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg https_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg no_proxy=10.113.66.135,10.35.66.136,localhost,eng.ses,xeneng.ses,az.ses -t $(REPO)$(NAME):v$(TAG) ../../../ -f ./Dockerfile + +push: + docker push $(REPO)$(NAME):v$(TAG) + diff --git a/docker/drivers/hifly2/Dockerfile b/docker/drivers/hifly2/Dockerfile new file mode 100644 index 0000000..9e053f9 --- /dev/null +++ b/docker/drivers/hifly2/Dockerfile @@ -0,0 +1,39 @@ +ARG BUILD_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 +ARG RUNTIME_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 + +FROM ${BUILD_IMAGE} as builder +USER root +RUN apt-get update && apt-get install -y omniidl libomniorb4-dev && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* + +USER spell + +RUN mkdir -p /home/spell/SPELL_BUILD +WORKDIR /home/spell/SPELL_BUILD + +COPY --chown=spell . . +RUN mkdir -p /home/spell/SPELL +ENV SPELL_COTS=/home/spell/SPELL_COTS + +#RUN ./build_spell.sh /home/spell/SPELL "none" "-DWITH_DEBUG -DWITH_KUBERNETES" + +RUN cd drivers/hifly2 && ./generate_stubs.sh 6 && cd ../.. && ./build_driver.sh /home/spell/SPELL hifly "SPELL_SYN SPELL_UTIL SPELL_WRP" "-DWITH_DEBUG -DWITH_KUBERNETES" + +FROM ${RUNTIME_IMAGE} + +USER spell +COPY --from=builder --chown=spell /home/spell/SPELL /home/spell/SPELL + +ENV SPELL_HOME=/home/spell/SPELL +ENV SPELL_CONFIG=/home/spell/SPELL_CONFIG +ENV SPELL_DATA=/home/spell/procs/SPELL_PROCS +ENV SPELL_LOG=/home/spell/procs/SPELL_LOG +ENV PATH=$SPELL_HOME/bin:$SPELL_COTS/bin:$PATH +ENV SPELL_DRIVER=/home/spell/SPELL_DRIVER/SPELL +ENV LD_LIBRARY_PATH=$SPELL_HOME/lib:$SPELL_DRIVER/lib:$SPELL_COTS/lib + +RUN chmod u+rwx -R /home/spell && chmod g+rx -R /home/spell + +USER root +COPY drivers/scorpio/services /services +RUN cat /services >> /etc/services && rm /services +USER spell diff --git a/docker/drivers/hifly2/Makefile b/docker/drivers/hifly2/Makefile new file mode 100644 index 0000000..8825538 --- /dev/null +++ b/docker/drivers/hifly2/Makefile @@ -0,0 +1,21 @@ +default: build + +REPO := bzengnexus1.idmz.eng.ses/ +NAME := spell/spell-driver-ubuntu-hifly2 +COMMIT := $(shell git rev-parse --short HEAD) +TAG := $(shell git describe --abbrev=0 --tags ${COMMIT} 2>/dev/null || true) +TAG_COMMIT := $(shell git rev-list -n 1 --abbrev-commit ${TAG}) + +VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(VERSION)-dev +endif +TAG := $(VERSION) + + +build: + docker build --build-arg http_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg https_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg no_proxy=10.113.66.135,10.35.66.136,localhost,eng.ses,xeneng.ses,az.ses -t $(REPO)$(NAME):v$(TAG) ../../../ -f ./Dockerfile + +push: + docker push $(REPO)$(NAME):v$(TAG) + diff --git a/docker/drivers/scorpio/Dockerfile b/docker/drivers/scorpio/Dockerfile new file mode 100644 index 0000000..f01a1aa --- /dev/null +++ b/docker/drivers/scorpio/Dockerfile @@ -0,0 +1,30 @@ +ARG BUILD_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 +ARG RUNTIME_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 + +FROM ${BUILD_IMAGE} as builder +USER spell +WORKDIR /home/spell/SPELL_BUILD + +COPY --chown=spell . . +RUN mkdir -p /home/spell/SPELL && chown spell:spell /home/spell/SPELL +ENV SPELL_COTS=/home/spell/SPELL_COTS + +RUN ./build_driver.sh /home/spell/SPELL scorpio "" "-DWITH_DEBUG -DWITH_KUBERNETES" + +FROM ${RUNTIME_IMAGE} + +COPY --chown=spell --from=builder /home/spell/SPELL /home/spell/SPELL +COPY --chown=spell --from=builder /home/spell/SPELL_BUILD/build/drivers/scorpio/ITC_wrap* /home/spell/SPELL/drivers/scorpio/interface/ + +ENV SPELL_HOME=/home/spell/SPELL +ENV SPELL_CONFIG=/home/spell/SPELL_CONFIG +ENV SPELL_DATA=/home/spell/procs/SPELL_PROCS +ENV SPELL_LOG=/home/spell/procs/SPELL_LOG +ENV PATH=$SPELL_HOME/bin:$SPELL_COTS/bin:$PATH +ENV SPELL_DRIVER=/home/spell/SPELL_DRIVER/SPELL +ENV LD_LIBRARY_PATH=$SPELL_HOME/lib:$SPELL_DRIVER/lib:$SPELL_COTS/lib + +USER root +COPY drivers/scorpio/services /services +RUN cat /services >> /etc/services && rm /services +USER spell diff --git a/docker/drivers/scorpio/Makefile b/docker/drivers/scorpio/Makefile new file mode 100644 index 0000000..21800e9 --- /dev/null +++ b/docker/drivers/scorpio/Makefile @@ -0,0 +1,20 @@ +default: build + +REPO := bzengnexus1.idmz.eng.ses/ +NAME := spell/spell-driver-ubuntu-scorpio +COMMIT := $(shell git rev-parse --short HEAD) +TAG := $(shell git describe --abbrev=0 --tags ${COMMIT} 2>/dev/null || true) +TAG_COMMIT := $(shell git rev-list -n 1 --abbrev-commit ${TAG}) + +VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(VERSION)-dev +endif +TAG := $(VERSION) + +build: + docker build --build-arg http_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg https_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg no_proxy=10.113.66.135,10.35.66.136,localhost,eng.ses,xeneng.ses,az.ses -t $(REPO)$(NAME):v$(TAG) ../../../ -f ./Dockerfile + +push: + docker push $(REPO)$(NAME):v$(TAG) + diff --git a/docker/logrotate/Dockerfile b/docker/logrotate/Dockerfile new file mode 100644 index 0000000..37cd20e --- /dev/null +++ b/docker/logrotate/Dockerfile @@ -0,0 +1,37 @@ +ARG UBUNTU_VERSION=22.04 +ARG NEXUS_REPO=docker-weu.spo.internal.ses.com/ + +FROM ${NEXUS_REPO}ubuntu:${UBUNTU_VERSION} as base + +# permissions +ARG CONTAINER_UID=1001 +ARG CONTAINER_GID=1000 + +COPY ./go-cron.tar.gz /tmp/go-cron.tar.gz + +# install dev tools +RUN export CONTAINER_USER=logrotate && \ + export CONTAINER_GROUP=logrotate && \ + addgroup --group --gid $CONTAINER_GID logrotate && \ + adduser --uid $CONTAINER_UID --ingroup logrotate --home /usr/bin/logrotate.d --shell /bin/bash --system logrotate && \ + apt-get update && apt-get install -y \ + tini \ + tar \ + gzip \ + tzdata \ + logrotate && \ + mkdir -p /usr/bin/logrotate.d && \ + tar xvf /tmp/go-cron.tar.gz -C /usr/bin && \ + rm -rf /var/cache/apt/archives /var/lib/apt/lists/* + +COPY docker-entrypoint.sh /usr/bin/logrotate.d/docker-entrypoint.sh +COPY logrotate.conf /usr/bin/logrotate.d/logrotate.conf + +# environment variable for this container +ENV LOGROTATE_INTERVAL= \ + LOG_PATHS= + + +ENTRYPOINT ["/bin/tini","--","/usr/bin/logrotate.d/docker-entrypoint.sh"] +VOLUME ["/logrotate-status"] +CMD ["cron"] \ No newline at end of file diff --git a/docker/logrotate/Makefile b/docker/logrotate/Makefile new file mode 100644 index 0000000..6d17139 --- /dev/null +++ b/docker/logrotate/Makefile @@ -0,0 +1,21 @@ +default: build + +REPO := bzengnexus1.idmz.eng.ses/ +NAME := spell/logrotate-ubuntu +COMMIT := $(shell git rev-parse --short HEAD) +TAG := $(shell git describe --abbrev=0 --tags ${COMMIT} 2>/dev/null || true) +TAG_COMMIT := $(shell git rev-list -n 1 --abbrev-commit ${TAG}) + +VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(VERSION)-dev +endif +TAG := $(VERSION) + + +build: + docker build --build-arg NEXUS_REPO= --build-arg VERSION_TAG=2.6.10 --build-arg http_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg https_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg no_proxy=10.113.66.135,10.35.66.136,localhost,eng.ses,xeneng.ses,az.ses -t $(REPO)$(NAME):v$(TAG) ./ -f ./Dockerfile + +push: + docker push $(REPO)$(NAME):v$(TAG) + diff --git a/docker/logrotate/docker-entrypoint.sh b/docker/logrotate/docker-entrypoint.sh new file mode 100644 index 0000000..5054085 --- /dev/null +++ b/docker/logrotate/docker-entrypoint.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# + + +if [ -n "${DELAYED_START}" ]; then + sleep ${DELAYED_START} +fi + +logrotate_croninterval="@daily" + +if [ -n "${LOGROTATE_INTERVAL}" ]; then + case "$LOGROTATE_INTERVAL" in + hourly) + logrotate_croninterval='@hourly' + ;; + daily) + logrotate_croninterval='@daily' + ;; + weekly) + logrotate_croninterval='@weekly' + ;; + monthly) + logrotate_croninterval='@monthly' + ;; + yearly) + logrotate_croninterval='@yearly' + ;; + *) + logrotate_croninterval="$LOGROTATE_INTERVAL" + ;; + esac +fi + +logrotate_cron_timetable="/usr/sbin/logrotate --state=/tmp/logrotate.status /usr/bin/logrotate.d/logrotate.conf" + +if [ "$1" = 'cron' ]; then + exec /usr/bin/go-cron "${logrotate_croninterval}" /bin/bash -c "${logrotate_cron_timetable}" +else + exec "$@" +fi + diff --git a/docker/logrotate/go-cron.tar.gz b/docker/logrotate/go-cron.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..813355154ca4f6b95bd7cf62bb1a96c67ec948e9 GIT binary patch literal 2099200 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%0D+pp4ZcQ!H;R^x%AKNsL`**Th7Aq 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
#A4>nrPJiG3T{eAtt_G+R)_W;6x@5c|< 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
;kZ;p`5-_}B*Mgc}M*6v&T-G2Mf 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%0D@|rKyp}}JMy~rG4p{)c<{DTR+7avA@b(HxE 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

6i)FR-OsaFB7Nxc?$OX}^wyHXzn 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

J$OoB>}LRH>1V(XAbB# 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�r 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 A@&Et; literal 0 HcmV?d00001 diff --git a/docker/logrotate/logrotate.conf b/docker/logrotate/logrotate.conf new file mode 100644 index 0000000..c7b1772 --- /dev/null +++ b/docker/logrotate/logrotate.conf @@ -0,0 +1,12 @@ +/home/spell/procs/SPELL_LOG/*.log { + daily + su logrotate logrotate + rotate 4 + size 10M + compress + missingokq + copytruncate + notifempty +} + + diff --git a/docker/spell-registry/.gitignore b/docker/spell-registry/.gitignore new file mode 100644 index 0000000..9e79245 --- /dev/null +++ b/docker/spell-registry/.gitignore @@ -0,0 +1,32 @@ +# macOS +.DS_Store + +# sbt specific +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +project/local-plugins.sbt +.history +.ensime +.ensime_cache/ +.sbt-scripted/ +local.sbt + +# Bloop +.bsp + +# VS Code +.vscode/ + +# Metals +.bloop/ +.metals/ +metals.sbt + +# IDEA +.idea +.idea_modules +/.worksheet/ diff --git a/docker/spell-registry/.scalafmt.conf b/docker/spell-registry/.scalafmt.conf new file mode 100644 index 0000000..8cd9640 --- /dev/null +++ b/docker/spell-registry/.scalafmt.conf @@ -0,0 +1,2 @@ +version = "3.5.3" +runner.dialect = scala3 \ No newline at end of file diff --git a/docker/spell-registry/Makefile b/docker/spell-registry/Makefile new file mode 100644 index 0000000..d2d433e --- /dev/null +++ b/docker/spell-registry/Makefile @@ -0,0 +1,23 @@ +default: build + +REPO := bzengnexus1.idmz.eng.ses/ +NAME := spell/spell-registry +COMMIT := $(shell git rev-parse --short HEAD) +TAG := $(shell git describe --abbrev=0 --tags ${COMMIT} 2>/dev/null || true) +TAG_COMMIT := $(shell git rev-list -n 1 --abbrev-commit ${TAG}) + +VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(VERSION)-dev +endif +TAG := $(VERSION) + + +build: +# docker run -it --rm -e http_proxy=http://bzengpx1.idmz.eng.ses:8080 -e https_proxy=http://bzengpx1.idmz.eng.ses:8080 -e no_proxy=10.113.66.135,10.35.66.136,localhost,eng.ses,xeneng.ses,az.ses -v $(PWD):/app -w /app sbtscala/scala-sbt:eclipse-temurin-19_36_1.8.0_3.2.1 sbt docker:publishLocal + VERSION=$(TAG) sbt docker:publishLocal + +push: + docker tag spell-registry:$(TAG) $(REPO)$(NAME):v$(TAG) + docker push $(REPO)$(NAME):v$(TAG) + diff --git a/docker/spell-registry/README.md b/docker/spell-registry/README.md new file mode 100644 index 0000000..6999c4f --- /dev/null +++ b/docker/spell-registry/README.md @@ -0,0 +1,85 @@ +# openapi-client + +Swagger Spell Registry - OpenAPI 3.0 +- API version: 1.0.0 + +Spell Registry Interface + + +*Automatically generated by the [OpenAPI Generator](https://openapi-generator.tech)* + +## Requirements + +Building the API client library requires: +1. Java 1.7+ +2. Maven/Gradle/SBT + +## Installation + +To install the API client library to your local Maven repository, simply execute: + +```shell +mvn clean install +``` + +To deploy it to a remote Maven repository instead, configure the settings of the repository and execute: + +```shell +mvn clean deploy +``` + +Refer to the [OSSRH Guide](http://central.sonatype.org/pages/ossrh-guide.html) for more information. + +### Maven users + +Add this dependency to your project's POM: + +```xml + + org.openapitools + openapi-client + 1.0.0 + compile + +``` + +### Gradle users + +Add this dependency to your project's build file: + +```groovy +compile "org.openapitools:openapi-client:1.0.0" +``` + +### SBT users + +```scala +libraryDependencies += "org.openapitools" % "openapi-client" % "1.0.0" +``` + +## Getting Started + +## Documentation for API Endpoints + +All URIs are relative to *http://spell-registry.bftd-kube.eng.ses/api/v1* + +Class | Method | HTTP request | Description +------------ | ------------- | ------------- | ------------- +*RegistryApi* | **listSpellServers** | **GET** /list | Finds SPELL servers and return their status and connection infos +*ServerApi* | **listSpellServers** | **GET** /list | Finds SPELL servers and return their status and connection infos + + +## Documentation for Models + + - [SpellServer](SpellServer.md) + + +## Documentation for Authorization + +All endpoints do not require authorization. +Authentication schemes defined for the API: + +## Author + +juergen.gall@ses.com + diff --git a/docker/spell-registry/build.sbt b/docker/spell-registry/build.sbt new file mode 100644 index 0000000..4e652fe --- /dev/null +++ b/docker/spell-registry/build.sbt @@ -0,0 +1,33 @@ +val scala3Version = "3.2.1" + +Global / onChangedBuildSource := ReloadOnSourceChanges + +val artifactVersion = sys.env.getOrElse("VERSION", "1.0.0") + +lazy val root = project + .in(file(".")) + .settings( + name := "spell-registry", + inThisBuild(List( + version := artifactVersion, + organization := "com.ses", + )), + + scalaVersion := scala3Version, + dockerExposedPorts ++= Seq(8080), + dockerBaseImage := "docker-weu.spo.internal.ses.com/eclipse-temurin:19.0.1_10-jre-alpine", + + libraryDependencies += "org.scalameta" %% "munit" % "0.7.29" % Test, + libraryDependencies ++= Seq( + "com.goyeau" %% "kubernetes-client" % "0.9.0", + "com.typesafe" % "config" % "1.4.2", + "org.http4s" %% "http4s-ember-server" % "0.23.16" + ), + dockerUsername := Some("spell"), + dockerAutoremoveMultiStageIntermediateImages := false, + dockerAliases := Seq( + dockerAlias.value.withTag(Option("latest")).withRegistryHost(Option("docker-weu.spo.internal.ses.com")), + dockerAlias.value.withRegistryHost(Option("docker-weu.spo.internal.ses.com")) + ), + ) + .enablePlugins(JavaAppPackaging).enablePlugins(AshScriptPlugin).enablePlugins(DockerPlugin) diff --git a/docker/spell-registry/project/build.properties b/docker/spell-registry/project/build.properties new file mode 100644 index 0000000..8b9a0b0 --- /dev/null +++ b/docker/spell-registry/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.8.0 diff --git a/docker/spell-registry/project/plugins.sbt b/docker/spell-registry/project/plugins.sbt new file mode 100644 index 0000000..edc9886 --- /dev/null +++ b/docker/spell-registry/project/plugins.sbt @@ -0,0 +1,2 @@ + +addSbtPlugin("com.github.sbt" % "sbt-native-packager" % "1.9.4") diff --git a/docker/spell-registry/src/main/resources/application.conf b/docker/spell-registry/src/main/resources/application.conf new file mode 100644 index 0000000..12e57b8 --- /dev/null +++ b/docker/spell-registry/src/main/resources/application.conf @@ -0,0 +1,2 @@ +apiserver="https://kubernetes.default" +server_port=8080 \ No newline at end of file diff --git a/docker/spell-registry/src/main/scala/KubeAPI.scala b/docker/spell-registry/src/main/scala/KubeAPI.scala new file mode 100644 index 0000000..85c29fd --- /dev/null +++ b/docker/spell-registry/src/main/scala/KubeAPI.scala @@ -0,0 +1,78 @@ +package com.ses + +import cats.effect.IO +import com.goyeau.kubernetes.client.* +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.slf4j.Slf4jLogger +import java.io.File +import org.http4s.AuthScheme +import org.http4s.Credentials.Token +import org.http4s.headers.Authorization +import org.http4s.implicits.* +import scala.concurrent.ExecutionContext +import scala.io.Source +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import org.http4s.Uri +import org.http4s.ParseFailure +import io.k8s.api.core.v1.Service +import io.k8s.api.core.v1.ServiceList +import cats.effect.unsafe.implicits.global +import io.k8s.api.core.v1.Pod +import io.k8s.api.apps.v1.Deployment + + +object KubeAPI { + implicit val config: Config = ConfigFactory.load("application") + + val apiServer = Uri.fromString(config.getString("apiserver")) match { + case Left(ParseFailure(sanitized, details)) => { + uri"https://kubernetes.default" + } + case Right(uri) => uri + } + lazy val namespace = Source + .fromFile("/var/run/secrets/kubernetes.io/serviceaccount/namespace") + .mkString + + lazy val kubernetesClient = + KubernetesClient[IO]( + KubeConfig.of[IO]( + server = apiServer, + authorization = Option( + Authorization( + Token( + AuthScheme.Bearer, + Source + .fromFile("/var/run/secrets/kubernetes.io/serviceaccount/token") + .mkString + ) + ) + ), + caCertFile = Option( + new File("/var/run/secrets/kubernetes.io/serviceaccount/ca.crt") + ) + ) + ) + def requestHealth() = + KubeAPI.kubernetesClient != null + + def requestServices(): ServiceList = + KubeAPI.kubernetesClient.use { client => + { + for { + serviceList <- client.services.namespace(namespace).list(Map("app.kubernetes.io/name" -> "spell-server")) + } yield (serviceList) + } + }.unsafeRunSync() + + def getDeployment(name : String) : Deployment = + KubeAPI.kubernetesClient.use { client => + { + for { + serviceList <- client.deployments.namespace(namespace).get(name) + } yield (serviceList) + } + }.unsafeRunSync() + +} diff --git a/docker/spell-registry/src/main/scala/Main.scala b/docker/spell-registry/src/main/scala/Main.scala new file mode 100644 index 0000000..533da32 --- /dev/null +++ b/docker/spell-registry/src/main/scala/Main.scala @@ -0,0 +1,34 @@ +package com.ses + +import cats.effect.IO +import com.goyeau.kubernetes.client.* +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.slf4j.Slf4jLogger +import java.io.File +import org.http4s._ +import org.http4s.AuthScheme +import org.http4s.Credentials.Token +import org.http4s.headers.Authorization +import org.http4s.implicits.* +import scala.concurrent.ExecutionContext +import scala.io.Source +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import org.http4s.Uri +import org.http4s.ParseFailure +import org.http4s.HttpRoutes +import cats.effect.ExitCode +import com.ses.SpellServerService +import cats.effect.IOApp + +implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO] +implicit val config: Config = ConfigFactory.load("application") + + +object Main extends IOApp { + + override def run(args: List[String]): IO[ExitCode] = { + SpellServerService.serverStream[IO].useForever.as(ExitCode.Success) + } + +} diff --git a/docker/spell-registry/src/main/scala/SpellServerService.scala b/docker/spell-registry/src/main/scala/SpellServerService.scala new file mode 100644 index 0000000..9111802 --- /dev/null +++ b/docker/spell-registry/src/main/scala/SpellServerService.scala @@ -0,0 +1,121 @@ +package com.ses + +import com.comcast.ip4s._ +import org.http4s.dsl.Http4sDsl +import org.http4s.ember.server.EmberServerBuilder +import cats.effect._ +import cats.syntax.all._ +import fs2.Stream +import io.circe.Json +import org.http4s._ +import org.http4s.circe._ +import org.http4s.dsl.Http4sDsl +import org.http4s.headers._ +import org.http4s.multipart.Multipart +import org.http4s.server._ +import org.http4s.server.middleware.authentication.BasicAuth +import org.http4s.server.middleware.authentication.BasicAuth.BasicAuthenticator +import org.http4s.syntax.all._ +import com.typesafe.config.Config +import scala.util.Try + +import io.circe._, io.circe.generic.auto._, io.circe.parser._, io.circe.syntax._ +import org.typelevel.log4cats.Logger +import org.typelevel.log4cats.slf4j.Slf4jLogger + +sealed trait SpellMessage +case class SpellError(error: String) extends SpellMessage + +case class SpellService( + name: String, + mission: String, + seat: String, + ip: String, + contextPort: Int, + sshPort: Int, + running: Boolean +) + +object SpellServerService { + implicit val logger: Logger[IO] = Slf4jLogger.getLogger[IO] + + def serverStream[F[_]: Async](implicit + config: Config + ) = { + val port = Port.fromString(config.getString("server_port")) match { + case None => port"8080" + case Some(p) => p + } + + EmberServerBuilder.default + .withPort(port) + .withHost(host"0.0.0.0") + .withHttpApp(new SpellServerServiceRoutes[F].routes.orNotFound) + .withLogger(Slf4jLogger.getLogger[F]) + .build + } + +} + +class SpellServerServiceRoutes[F[_]: Sync] extends Http4sDsl[F] { + + val routes: HttpRoutes[F] = + HttpRoutes.of[F] { + case GET -> Root / "healthz" => { + Try { + val services = KubeAPI.requestHealth().asJson + + Ok(services) + }.handleError { f => + val result = SpellError(s"Failed with ${f}").asJson + + InternalServerError(result) + }.get + } + + case req @ GET -> Root / "list" => + Try { + val services = KubeAPI + .requestServices() + .items + .map[SpellService](kubeService => + val name = kubeService.metadata.get.name.get + val labels = kubeService.metadata.get.labels.getOrElse(Map()) + val ip = kubeService.status.fold(None)(status => + status.loadBalancer.fold(None)(lb => + lb.ingress.fold(None)(lbi => + if (lbi.isEmpty) None + else lbi.get(0).fold(None)(f => f.ip) + ) + ) + ) + val port = kubeService.spec.get.ports + .getOrElse(Seq()) + .find(f => f.name.getOrElse("") == "ssh") + .fold(None)(p => Some(p.port)) + val deployment = KubeAPI.getDeployment(name) + SpellService( + name = name, + mission = labels + .getOrElse("mission", labels.getOrElse("spell-mission", "")), + seat = + labels.getOrElse("seat", labels.getOrElse("spell-seat", "")), + ip = ip.getOrElse(""), + contextPort = 19823, + sshPort = port.getOrElse(-1), + running = ip.isDefined && port.isDefined && (labels.isDefinedAt( + "mission" + ) || labels.isDefinedAt("spell-mission")) && (labels + .isDefinedAt("seat") || labels.isDefinedAt("spell-seat")) && deployment.status.fold(false)(st => st.availableReplicas.fold(false)(_ > 0)) + ) + ) + + Ok(services.asJson) + }.handleError { f => + val result = SpellError(s"Failed with ${f}").asJson + + Ok(result) + }.get + + } +} diff --git a/docker/spell-registry/src/test/scala/MySuite.scala b/docker/spell-registry/src/test/scala/MySuite.scala new file mode 100644 index 0000000..621784d --- /dev/null +++ b/docker/spell-registry/src/test/scala/MySuite.scala @@ -0,0 +1,9 @@ +// For more information on writing tests, see +// https://scalameta.org/munit/docs/getting-started.html +class MySuite extends munit.FunSuite { + test("example test that succeeds") { + val obtained = 42 + val expected = 42 + assertEquals(obtained, expected) + } +} diff --git a/docker/spell-server/Dockerfile b/docker/spell-server/Dockerfile new file mode 100644 index 0000000..7f8b05d --- /dev/null +++ b/docker/spell-server/Dockerfile @@ -0,0 +1,43 @@ +ARG BUILD_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 +ARG RUNTIME_IMAGE=prengnexus1.idmz.eng.ses/spell/spell-cots-ubuntu-build:v1.0.0 +ARG VERSION_TAG=0.0.1 + +FROM ${BUILD_IMAGE} as builder +ARG VERSION_TAG + +USER root +RUN apt-get update && apt-get install -y libbackward-cpp-dev libdw-dev \ + && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* + +USER spell +RUN mkdir -p /home/spell/SPELL_BUILD +WORKDIR /home/spell/SPELL_BUILD + +COPY --chown=spell . /home/spell/SPELL_BUILD +RUN mkdir -p /home/spell/SPELL && chown spell /home/spell/SPELL +ENV SPELL_COTS=/home/spell/SPELL_COTS +ENV LD_LIBRARY_PATH=$SPELL_HOME/lib:$SPELL_DRIVER/lib:$SPELL_COTS/lib + +RUN cd /home/spell/SPELL_BUILD && ./build_spell_ubuntu.sh /home/spell/SPELL "none" "-DBACKWARD_HAS_DW=1 -DWITH_BACKTRACE -DWITH_DEBUG -DWITH_KUBERNETES" + +FROM ${RUNTIME_IMAGE} +USER spell +COPY --chown=spell --from=builder /home/spell/SPELL /home/spell/SPELL + +ENV SPELL_HOME=/home/spell/SPELL +ENV SPELL_CONFIG=/home/spell/SPELL_CONFIG +ENV SPELL_DATA=/home/spell/procs/SPELL_PROCS +ENV SPELL_LOG=/home/spell/procs/SPELL_LOG +ENV PATH=$SPELL_HOME/bin:$SPELL_COTS/bin:$PATH +ENV SPELL_DRIVER=/home/spell/SPELL_DRIVER/SPELL +ENV LD_LIBRARY_PATH=$SPELL_HOME/lib:$SPELL_DRIVER/lib:$SPELL_COTS/lib + +WORKDIR /home/spell +RUN ln -sf /home/spell/procs/SPACECRAFT SPACECRAFT && ln -sf /home/spell/procs/SPELL_PROCS SPELL_PROCS + +USER root +COPY --from=builder /home/spell/SPELL_BUILD/drivers/scorpio/services /services +RUN cat /services >> /etc/services && rm /services +USER spell + +ENTRYPOINT ["SPELL-Server-Docker"] diff --git a/docker/spell-server/Makefile b/docker/spell-server/Makefile new file mode 100644 index 0000000..aa5649b --- /dev/null +++ b/docker/spell-server/Makefile @@ -0,0 +1,21 @@ +default: build + +REPO := bzengnexus1.idmz.eng.ses/ +NAME := spell/spell-server-ubuntu +COMMIT := $(shell git rev-parse --short HEAD) +TAG := $(shell git describe --abbrev=0 --tags ${COMMIT} 2>/dev/null || true) +TAG_COMMIT := $(shell git rev-list -n 1 --abbrev-commit ${TAG}) + +VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(VERSION)-dev +endif +TAG := $(VERSION) + + +build: + docker build --build-arg VERSION_TAG=2.6.10 --build-arg http_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg https_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg no_proxy=10.113.66.135,10.35.66.136,localhost,eng.ses,xeneng.ses,az.ses -t $(REPO)$(NAME):v$(TAG) ../../ -f ./Dockerfile + +push: + docker push $(REPO)$(NAME):v$(TAG) + diff --git a/docker/ssh-server/Dockerfile b/docker/ssh-server/Dockerfile new file mode 100644 index 0000000..8ef7a57 --- /dev/null +++ b/docker/ssh-server/Dockerfile @@ -0,0 +1,22 @@ +ARG UBUNTU_VERSION=22.04 + +FROM docker-weu.spo.internal.ses.com/ubuntu:${UBUNTU_VERSION} as base + +RUN apt-get update && apt-get install -y openssh-server net-tools sssd nscd libnss-ldap libpam-ldap ldap-auth-config libpam-sss libnss-sss sssd-tools \ + && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y rxvt-unicode \ + && rm -rf /var/cache/apt/archives /var/lib/apt/lists/* + +RUN groupadd spell && useradd -rm -d /home/spell -s /sbin/nologin -g spell -G spell -u 1001 spell + +RUN mkdir -p /var/run/sshd +ADD init.sh /init.sh +RUN chmod u+x /init.sh + + +EXPOSE 8422 +RUN chmod u+rwx -R /home/ && mkdir -p /usr/libexec/openssh/ && ln -s /usr/lib/sftp-server /usr/libexec/openssh/sftp-server && ln -s /usr/lib/sftp-server /bin/sftp-server && chmod g+rx -R /home/ && touch /etc/services && pam-auth-update --enable mkhomedir + +ENV TERM=xterm + +CMD /init.sh \ No newline at end of file diff --git a/docker/ssh-server/Dockerfile.centos b/docker/ssh-server/Dockerfile.centos new file mode 100644 index 0000000..0fc489d --- /dev/null +++ b/docker/ssh-server/Dockerfile.centos @@ -0,0 +1,25 @@ + +FROM centos:7 as base + +RUN yum-config-manager --save --disable base && rm /etc/yum.repos.d/* +COPY etc/yum.repos.d/* /etc/yum.repos.d/ + +RUN yum makecache fast && yum update -y +RUN yum-config-manager --save --disable base --disable extras --disable updates +RUN yum clean all && yum install -y \ + openssl net-tools \ + nss-pam-ldapd nscd openldap-clients openssh-server openssh-clients rsyslog authconfig \ + && yum clean all + +RUN groupadd spell && useradd -rm -d /home/spell -s /sbin/nologin -g spell -G spell -u 1001 spell + +RUN mkdir -p /var/run/sshd +ADD init.sh /init.sh +RUN chmod u+x /init.sh + + +EXPOSE 8422 +RUN chmod u+rwx -R /home/ && chmod g+rx -R /home/ + +CMD /init.sh + diff --git a/docker/ssh-server/Makefile b/docker/ssh-server/Makefile new file mode 100644 index 0000000..93fa37d --- /dev/null +++ b/docker/ssh-server/Makefile @@ -0,0 +1,20 @@ +default: build + +REPO := bzengnexus1.idmz.eng.ses/ +NAME := spell/ssh-server-ubuntu +COMMIT := $(shell git rev-parse --short HEAD) +TAG := $(shell git describe --abbrev=0 --tags ${COMMIT} 2>/dev/null || true) +TAG_COMMIT := $(shell git rev-list -n 1 --abbrev-commit ${TAG}) + +VERSION := $(TAG:v%=%) +ifneq ($(COMMIT), $(TAG_COMMIT)) + VERSION := $(VERSION)-dev +endif +TAG := $(VERSION) + +build: + docker build --build-arg http_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg https_proxy=http://bzengpx1.idmz.eng.ses:8080 --build-arg no_proxy=10.113.66.135,10.35.66.136,localhost,eng.ses,xeneng.ses,az.ses -t $(REPO)$(NAME):v$(TAG) . + +push: + docker push $(REPO)$(NAME):v$(TAG) + diff --git a/docker/ssh-server/etc/yum.repos.d/bzenginst1.repo b/docker/ssh-server/etc/yum.repos.d/bzenginst1.repo new file mode 100644 index 0000000..08ec4e9 --- /dev/null +++ b/docker/ssh-server/etc/yum.repos.d/bzenginst1.repo @@ -0,0 +1,7 @@ +[bzenginst1] +name=YUM repo on bzenginst1.idmz.eng.ses +baseurl=http://10.35.66.136/centos/$releasever/$basearch +enabled=1 +gpgcheck=0 +proxy=_none_ + diff --git a/docker/ssh-server/init.sh b/docker/ssh-server/init.sh new file mode 100644 index 0000000..bfa26ba --- /dev/null +++ b/docker/ssh-server/init.sh @@ -0,0 +1,237 @@ +#!/bin/bash +# + +#LDAP_PASSWORD= + +[ -z "${LDAP_PASSWORD}" ] && LDAP_PASSWORD=$(cat /etc/ldap_password) +[ -z "${LDAP_URL}" ] && LDAP_URL=$(cat /etc/ldap_url) +[ -z "${LDAP_URL}" ] && LDAP_URL="ldaps://beacdc1.xeneng.ses" +[ -z "${SSH_PORT}" ] && SSH_PORT=8422 +[ -z "${LDAP_BIND_DN}" ] && LDAP_BIND_DN="CN=ldap,OU=G_SYS,OU=ses-users,DC=xeneng,DC=ses" +[ -z "${LDAP_SEARCH_BASE}" ] && LDAP_SEARCH_BASE="OU=ses-users,DC=xeneng,DC=ses" + +[ -f /etc/ssh/ssh_host_rsa_key ] || ssh-keygen -f /etc/ssh/ssh_host_rsa_key -q -N '' + +mkdir -p /var/lib/sss/db/ +cat > /etc/sssd/sssd.conf < /sshd_config < /home/spell/groups/\" \$7 \" &\" + system(cmd) + print substr(\$4,p+1) \"=\" \$7 + }' > /home/spell/users" +# id -G -n \$7 \ No newline at end of file diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 03a1e42..ab61088 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -1,5 +1,41 @@ -SUBDIRS= +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ -if WITH_DRIVER_STD -SUBDIRS+=standalone +SUBDIRS = example + +if WITH_DRIVER_HIFLY +SUBDIRS += hifly2 +endif + +if WITH_DRIVER_SCORPIO +SUBDIRS += scorpio endif + +SERVICE=${top_srcdir}/drivers/service +SERVICE_DST=${exec_prefix}/drivers/service + +all-local: + find ${SERVICE} -type f -name "*.py" -print0 | xargs -0 -n1 sh -c LD_LIBRARY_PATH=${SPELL_COTS}/lib ${PYTHON2} ${top_srcdir}/scripts/src/SPELL-Compile + find ${SERVICE} -type f -name "*.py" -print0 | xargs -0 -n1 sh -c LD_LIBRARY_PATH=${SPELL_COTS}/lib ${PYTHON3} ${top_srcdir}/scripts/src/SPELL-Compile + +install-exec-hook: + mkdir -p ${SERVICE_DST} + cp -ur ${SERVICE}/*.py ${SERVICE_DST} diff --git a/drivers/example/Makefile.am b/drivers/example/Makefile.am index db5e249..fe55830 100644 --- a/drivers/example/Makefile.am +++ b/drivers/example/Makefile.am @@ -1,53 +1,36 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ DRIVER=example -DRIVER_SRC=src -DRIVER_CFG=config +DRIVER_SRC=${srcdir}/python +DRIVER_CFG=${srcdir}/config +DRIVER_DST=${exec_prefix}/drivers/${DRIVER} +CONFIG_DST=${exec_prefix}/config/spell all-local: - if [[ ! -d ${DRIVER_SRC} ]] ;\ - then \ - cp -r ${top_srcdir}/drivers/${DRIVER}/${DRIVER_SRC} . > /dev/null ;\ - fi - ${top_srcdir}/py-compiledir ${DRIVER_SRC} - -clean-local: - find ${DRIVER_SRC} -name "*.pyc" | xargs rm -f - find ${DRIVER_SRC} -name "*.pyo" | xargs rm -f - rm -f ${DRIVER_SRC}/build.date + find ${DRIVER_SRC} -type f -name "*.py" -print0 | xargs -0 -n1 sh -c LD_LIBRARY_PATH=${SPELL_COTS}/lib ${PYTHON2} ${top_srcdir}/scripts/src/SPELL-Compile + find ${DRIVER_SRC} -type f -name "*.py" -print0 | xargs -0 -n1 sh -c LD_LIBRARY_PATH=${SPELL_COTS}/lib ${PYTHON3} ${top_srcdir}/scripts/src/SPELL-Compile install-exec-hook: - if [[ ! -d ${exec_prefix}/drivers ]] ;\ - then \ - mkdir ${exec_prefix}/drivers ;\ - fi - rm -rf ${exec_prefix}/drivers/${DRIVER} - cp -r ${DRIVER_SRC} ${exec_prefix}/drivers/${DRIVER} - find ${exec_prefix}/drivers/${DRIVER} -name ".svn" | xargs rm -rf - find ${exec_prefix}/drivers/${DRIVER} -name ".py" | xargs rm -f - mkdir -p ${exec_prefix}/config/spell - cp -f ${DRIVER_CFG}/driver_${DRIVER}.xml ${exec_prefix}/config/spell/. + mkdir -p ${DRIVER_DST} + cp -ur ${DRIVER_SRC}/* ${DRIVER_DST} + mkdir -p ${CONFIG_DST} + cp -u ${DRIVER_CFG}/driver_${DRIVER}.xml ${exec_prefix}/config/spell diff --git a/drivers/example/config/driver_example.xml b/drivers/example/config/driver_example.xml index ddb841d..a579dc3 100644 --- a/drivers/example/config/driver_example.xml +++ b/drivers/example/config/driver_example.xml @@ -1,6 +1,6 @@ Example SPELL driver - TM,TC,EV,TIME,TASK,RSC,USR + TM,TC,EV,TIME,TASK,RSC,USER 5 $SPELL_HOME/drivers diff --git a/drivers/example/python/__init__.py b/drivers/example/python/__init__.py new file mode 100644 index 0000000..c37d2df --- /dev/null +++ b/drivers/example/python/__init__.py @@ -0,0 +1,25 @@ +################################################################################### +## MODULE : __init__ +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Module initialization +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### diff --git a/drivers/example/python/config.py b/drivers/example/python/config.py new file mode 100644 index 0000000..b8d933e --- /dev/null +++ b/drivers/example/python/config.py @@ -0,0 +1,100 @@ +################################################################################### +## MODULE : config +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#******************************************************************************* +# SPELL Imports +#******************************************************************************* +from spell.lib.exception import DriverException + +#******************************************************************************* +# Local Imports +#******************************************************************************* + +#******************************************************************************* +# System Imports +#******************************************************************************* +import os,sys + +############################################################################### +# Module import definition + +__all__ = ['CONFIG'] + +INTERFACE_DEFAULTS = {} + +############################################################################### +# Superclass +import spell.lib.adapter.config.ConfigInterface as superClass + +############################################################################### +# The configuration interface is typically in charge of managing resources and +# mechanisms that have nothing to do with specific services like TM, TC and EV. +# This interface normally prepares the ground so that the service interfaces +# like TM can initialize properly. The configuration interface is the first +# on being initialized. +############################################################################### +class ConfigInterface(superClass): + + #========================================================================== + def __init__(self): + """ + Perform the initialization of your data here, after the superClass + initialization. + """ + superClass.__init__(self) + # TODO Your initialization stuff here + + #========================================================================== + def setup(self, ctxConfig, drvConfig): + """ + Start the internal driver mechanisms (typically, start a centralized + internal service that manages all driver capabilities) + + ctxConfig : provides the current context information (S/C name and others) + drvConfig : provides the configuration parameters for this driver according + to the xml config file. + """ + superClass.setup(self, ctxConfig, drvConfig) + # TODO Initialize here the driver internal mechanisms + # Throw DriverException in case of fatal errors + + #========================================================================== + def cleanup(self, shutdown = False): + """ + Perform the cleanup of the internal driver resources. + + shutdown: when true, the SPELL executor is shutting down so every resource + should be released. When false, it means that the driver is being unloaded + but the executor keeps running. That is, it is possible that the driver + will be loaded again later on (e.g. on a RELOAD command). In this case + it may be interesting to keep some resources ready when 'shutdown' is false. + """ + superClass.cleanup(self, shutdown) + # TODO Release resources and cleanup stuff here + +############################################################################### +# Interface instance +CONFIG = ConfigInterface() diff --git a/drivers/example/python/constants.py b/drivers/example/python/constants.py new file mode 100644 index 0000000..f393547 --- /dev/null +++ b/drivers/example/python/constants.py @@ -0,0 +1,30 @@ +################################################################################### +## MODULE : constants +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Definition of driver specific constants +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#: Example constant +MY_CONSTANT = 'MY_CONSTANT' +#: Example constant +MY_FLAG = 1 diff --git a/drivers/example/python/ev.py b/drivers/example/python/ev.py new file mode 100644 index 0000000..e29f903 --- /dev/null +++ b/drivers/example/python/ev.py @@ -0,0 +1,104 @@ +################################################################################### +## MODULE : ev +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Event interface of the driver connection layer +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +################################################################################ + +#******************************************************************************* +# SPELL imports +#******************************************************************************* +from spell.lib.exception import DriverException +from spell.lang.modifiers import * +from spell.lang.constants import * + +#******************************************************************************* +# Local imports +#******************************************************************************* + +#******************************************************************************* +# System imports +#******************************************************************************* +import sys,os + +############################################################################### +# Module import definition + +__all__ = ['EV'] + +############################################################################### +# Superclass +import spell.lib.adapter.ev.EvInterface as superClass + +############################################################################### +# The events interface is in charge of managing GCS events. +############################################################################### +class EvInterface(superClass): + + #========================================================================== + def __init__(self): + """ + Perform the initialization of Event Handling data here, after the + superclass initialization + """ + superClass.__init__(self) + # TODO Your initialization stuff here + + #========================================================================== + def setup(self, ctxConfig, drvConfig): + """ + Start the internal driver EV mechanisms + + ctxConfig : provides the current context information (S/C name and others) + drvConfig : provides the configuration parameters for this driver according + to the xml config file. + """ + superClass.setup(self, ctxConfig, drvConfig) + # TODO Your startup stuff here + + #========================================================================== + def cleanup(self): + """ + Perform the cleanup of the internal driver EV resources. + """ + superClass.cleanup(self) + # TODO Release resources and cleanup stuff here + + #========================================================================== + def _raiseEvent(self, message, config): + """ + Invoked by the system in order to inject an event into the GCS. + + message: string containing the event message + config: dictionary containing the event parameters: + - Severity modifier (INFORMATION/WARNING/ERROR/FATAL) + - Scope (SCOPE_PROC,SCOPE_SYS,SCOPE_CFG) + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the event injection + +################################################################################ +# Interface instance +EV = EvInterface() diff --git a/drivers/example/python/gcstime.py b/drivers/example/python/gcstime.py new file mode 100644 index 0000000..a0d8f0c --- /dev/null +++ b/drivers/example/python/gcstime.py @@ -0,0 +1,98 @@ +################################################################################### +## MODULE : gcstime +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Time service interface of the driver connection layer +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#******************************************************************************* +# SPELL imports +#******************************************************************************* +from spell.lib.exception import DriverException + +#******************************************************************************* +# Local imports +#******************************************************************************* + +#******************************************************************************* +# System imports +#******************************************************************************* +import datetime + +############################################################################### +# Module import definition + +__all__ = ['TIME'] + +############################################################################### +# Superclass +import spell.lib.adapter.gcstime.TimeInterface as superClass + +############################################################################### +# The time interface is in charge of managing GCS time. +############################################################################### +class TimeInterface(superClass): + + #========================================================================== + def __init__(self): + """ + Perform the initialization of time handling data here, after the + superclass initialization + """ + superClass.__init__(self) + # TODO Your initialization stuff here + + #========================================================================== + def setup(self, ctxConfig, drvConfig): + """ + Start the internal driver TIME mechanisms + + ctxConfig : provides the current context information (S/C name and others) + drvConfig : provides the configuration parameters for this driver according + to the xml config file. + """ + superClass.setup(self, ctxConfig, drvConfig) + # TODO Your startup stuff here + + #========================================================================== + def cleanup(self): + """ + Perform the cleanup of the internal driver TIME resources. + """ + superClass.cleanup(self) + # TODO Release resources and cleanup stuff here + + #========================================================================== + def _getUTC(self, config = {} ): + """ + Obtain the current UTC time according to the GCS time. If the present + example is not change, the interface provices the current system local + time. + + config: SPELL configuration dictionary. + """ + # Default implementation: get system local time + return datetime.datetime.utcnow() + +################################################################################ +# Interface handle +TIME = TimeInterface() diff --git a/drivers/example/python/modifiers.py b/drivers/example/python/modifiers.py new file mode 100644 index 0000000..5fc5fc6 --- /dev/null +++ b/drivers/example/python/modifiers.py @@ -0,0 +1,29 @@ +################################################################################### +## MODULE : modifiers +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Driver-specific modifiers +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#: Example modifier +ExampleModifier = 'ExampleModifier' +# MANDATORY: name and value strings shall match diff --git a/drivers/example/python/resources.py b/drivers/example/python/resources.py new file mode 100644 index 0000000..d3303e3 --- /dev/null +++ b/drivers/example/python/resources.py @@ -0,0 +1,183 @@ +################################################################################### +## MODULE : resources +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Resource management interface of the driver connection layer +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#******************************************************************************* +# SPELL imports +#******************************************************************************* +from spell.lib.exception import DriverException +from spell.lang.constants import * +from spell.lang.modifiers import * + +#******************************************************************************* +# Local imports +#******************************************************************************* + +#******************************************************************************* +# System imports +#******************************************************************************* + +############################################################################### +# Module import definition + +__all__ = ['RSC'] + +############################################################################### +# Superclass +import spell.lib.adapter.resources.ResourceInterface as superClass + +############################################################################### +# The resource management driver interface. This class is in charge of +# managing the underlying GCS resources like configuration parameters. +############################################################################### +class ResourceInterface(superClass): + + #========================================================================== + def __init__(self): + """ + Perform the initialization of resource handling data here, after the + superclass initialization + """ + superClass.__init__(self) + # TODO Your initialization stuff here + + #========================================================================== + def setup(self, ctxConfig, drvConfig): + """ + Start the internal driver resource mgmt mechanisms + + ctxConfig : provides the current context information (S/C name and others) + drvConfig : provides the configuration parameters for this driver according + to the xml config file. + """ + superClass.setup(self, ctxConfig, drvConfig) + # TODO Your startup stuff here + + #========================================================================== + def cleanup(self): + """ + Perform the cleanup of the internal driver mechanisms. + """ + superClass.cleanup(self) + # TODO Release resources and cleanup stuff here + + #========================================================================== + def _setResource(self, name, value, isLocal, config = {} ): + """ + Assign the value of one resource or configuration parameter in the GCS + + name: resource identifier + value: resource value + isLocal: decide whether to set local or global instance + config: configuration modifiers + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the resource assignment + + #========================================================================== + def _getResource(self, name, isLocal, config = {} ): + """ + Acquire the value of one resource or configuration parameter in the GCS + + name: resource identifier + isLocal: decide whether to get local or global instance + config: configuration modifiers + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the resource acquisition. Return the resource value: + # return theValue + + #========================================================================== + def _getResources(self, isLocal, config = {} ): + """ + Acquire the name-value map of all resources or configuration parameters + in the GCS + + isLocal: decide whether to get local or global instances + config: configuration modifiers + """ + raise DriverException("Service not available in this driver") + + #========================================================================== + def _getResourceStatus(self, name, config = {} ): + """ + Acquire the current status of one resource or configuration parameter + in the GCS + + name: resource identifier + config: configuration modifiers + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the resource acquisition. + # return theStatus + + #========================================================================== + def _isResourceOK(self, name, config = {} ): + """ + Check if the current value of one resource or configuration parameter + in the GCS is correct or incorrect + + name: resource identifier + config: configuration modifiers + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the resource acquisition. + # return True/False + + #========================================================================== + def _setLink(self, name, enable, config = {}): + """ + Enable or disable a GCS link (TM, TC or others) + + name: link identifier + enable: True/False, indicates if the link needs to be enabled or disabled + config: configuration modifiers + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the resource acquisition. + # return True/False depending on success + + #========================================================================== + def _checkLink(self, name): + """ + Check if a GCS link (TM, TC or others) is enabled or disabled + + name: link identifier + config: configuration modifiers + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the resource acquisition. + # return True/False if the link is enabled/disabled + + +################################################################################ +# Interface handle +RSC = ResourceInterface() diff --git a/drivers/example/python/task.py b/drivers/example/python/task.py new file mode 100644 index 0000000..9c12219 --- /dev/null +++ b/drivers/example/python/task.py @@ -0,0 +1,173 @@ +################################################################################### +## MODULE : task +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Task management interface of the driver connection layer +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#******************************************************************************* +# SPELL imports +#******************************************************************************* +from spell.lib.exception import DriverException +from spell.lang.constants import * +from spell.lang.modifiers import * + +#******************************************************************************* +# Local imports +#******************************************************************************* + +#******************************************************************************* +# System imports +#******************************************************************************* + +############################################################################### +# Module import definition + +__all__ = ['TASK'] + +############################################################################### +# Superclass +import spell.lib.adapter.task.TaskInterface as superClass + +############################################################################### +# Task management library interface. This class is in charge of +# managing the underlying system applications. +############################################################################### +class TaskInterface(superClass): + + #========================================================================== + def __init__(self): + """ + Perform the initialization of data here, after the + superclass initialization + """ + superClass.__init__(self) + # TODO Your initialization stuff here + + #========================================================================== + def setup(self, ctxConfig, drvConfig): + """ + Start the internal driver TASK mechanisms + + ctxConfig : provides the current context information (S/C name and others) + drvConfig : provides the configuration parameters for this driver according + to the xml config file. + """ + superClass.setup(self, ctxConfig, drvConfig) + # TODO Your startup stuff here + + #========================================================================== + def cleanup(self): + """ + Perform the cleanup of the internal driver TASK resources. + """ + superClass.cleanup(self) + # TODO Release resources and cleanup stuff here + + #========================================================================== + def _startTask(self, name, config = {} ): + """ + Invoked by the system in order to start a GCS task. + + name: string containing the task name + config: configuration dictionary with modifiers. + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the task start. Return True/False. + + #========================================================================== + def _stopTask(self, name, config = {} ): + """ + Invoked by the system in order to stop a GCS task. + + name: string containing the task name + config: configuration dictionary with modifiers. + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the task stop. Return True/False. + + #========================================================================== + def _checkTask(self, name, config = {} ): + """ + Invoked by the system in order to check the status of a GCS task. + + name: string containing the task name + config: configuration dictionary with modifiers. + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the task start. Return the status code. + + #========================================================================== + def _openDisplay(self, displayName, config = {} ): + """ + Invoked by the system in order to open a telemetry display on the GCS + + displayName: name or identifier of the display + config: configuration dictionary with modifiers. + + Relevant modifiers are: + + Host: the machine name where the display should be open. + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the operation. Return True on success. + + #========================================================================== + def _printDisplay(self, displayName, config = {} ): + """ + Invoked by the system in order to print a telemetry display from the GCS + + displayName: name or identifier of the display + config: configuration dictionary with modifiers. + + Relevant modifiers are: + + Printer: the printer name + Format: PS (postcript) or VECTOR (ASCII text, for alphanumeric displays only) + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the operation. Return True on success. + + #========================================================================== + def _closeDisplay(self, displayName, config = {} ): + """ + Invoked by the system in order to close a telemetry display on the GCS + + displayName: name or identifier of the display + config: configuration dictionary with modifiers. + + Relevant modifiers are: + + Host: the machine name where the display should be closed. + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the operation. Return True on success. + +################################################################################ +# Interface handle +TASK = TaskInterface() diff --git a/drivers/example/python/tc.py b/drivers/example/python/tc.py new file mode 100644 index 0000000..d452d2b --- /dev/null +++ b/drivers/example/python/tc.py @@ -0,0 +1,138 @@ +################################################################################### +## MODULE : tc +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Telecommand interface of the driver connection layer +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#******************************************************************************* +# SPELL imports +#******************************************************************************* +from spell.lib.exception import DriverException +from spell.lang.constants import * +from spell.lang.modifiers import * + +#******************************************************************************* +# Local imports +#******************************************************************************* + +#******************************************************************************* +# System imports +#******************************************************************************* + +############################################################################### +# Module import definition + +__all__ = ['TC'] + +############################################################################### +# Superclass +import spell.lib.adapter.tc.TcInterface as superClass + +############################################################################### +# Telecommand injection interface. +############################################################################### +class TcInterface(superClass): + + #========================================================================== + def __init__(self): + """ + Perform the initialization of data here, after the + superclass initialization + """ + superClass.__init__(self) + # TODO Your initialization stuff here + + #========================================================================== + def setup(self, ctxConfig, drvConfig): + """ + Start the internal driver TC mechanisms + + ctxConfig : provides the current context information (S/C name and others) + drvConfig : provides the configuration parameters for this driver according + to the xml config file. + """ + superClass.setup(self, ctxConfig, drvConfig) + # TODO Your startup stuff here + + #========================================================================== + def cleanup(self): + """ + Perform the cleanup of the internal driver TC resources. + """ + superClass.cleanup(self) + # TODO Release resources and cleanup stuff here + + #========================================================================== + def _createTcItem(self, mnenonic, description = "" ): + """ + Instantiate a telecommand (usually taking the information from GCS) + + The item shall be derived from TcItemClass class. + + If not overriden, the adapter class provides a generic TC item structure. + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the item creation. + # return theItem + + #========================================================================== + def _sendCommand(self, tcItem, config = {} ): + """ + Inject a TC item into the GCS. + + tcItem : TcItemClass instance with the command information. + config : configuration dictionary + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the command injection. Return True/False + + #========================================================================== + def _sendList(self, tcItemList, config = {} ): + """ + Inject a group of TC items into the GCS. + + tcItemList : list of TcItemClass instance with the command information. + config : configuration dictionary + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the command list injection. Return True/False + + #========================================================================== + def _sendBlock(self, tcItemList, config = {} ): + """ + Inject a block of TC items into the GCS. Blocked items are injected + and transmitted as a single TC frame. + + tcItemList : list of TcItemClass instance with the command information. + config : configuration dictionary + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the block injection. Return True/False + +################################################################################ +# Interface handle +TC = TcInterface() diff --git a/drivers/example/python/tm.py b/drivers/example/python/tm.py new file mode 100644 index 0000000..5e6cd83 --- /dev/null +++ b/drivers/example/python/tm.py @@ -0,0 +1,269 @@ +################################################################################### +## MODULE : tm +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: Telemetry interface of the driver connection layer +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#******************************************************************************* +# SPELL imports +#******************************************************************************* +from spell.lib.exception import DriverException +from spell.lang.constants import * +from spell.lang.modifiers import * + +#******************************************************************************* +# Local imports +#******************************************************************************* + +#******************************************************************************* +# System imports +#******************************************************************************* +import sys,os + +############################################################################### +# Module import definition + +__all__ = ['TM'] + +############################################################################### +# Superclass +import spell.lib.adapter.tm.TmInterface as superClass + +############################################################################### +############################################################################### +class TmInterface( superClass ): + + #========================================================================== + def __init__(self): + """ + Perform the initialization of data here, after the + superclass initialization + """ + superClass.__init__(self) + # TODO Your initialization stuff here + + #========================================================================== + def setup(self, ctxConfig, drvConfig): + """ + Start the internal driver TM mechanisms + + ctxConfig : provides the current context information (S/C name and others) + drvConfig : provides the configuration parameters for this driver according + to the xml config file. + """ + superClass.setup(self, ctxConfig, drvConfig) + # TODO Your startup stuff here + + #========================================================================== + def cleanup(self): + """ + Perform the cleanup of the internal driver TM resources. + """ + superClass.cleanup(self) + # TODO Release resources and cleanup stuff here + + #=========================================================================== + def _createTmItem(self, mnemonic, description = ""): + """ + Instantiate a telemetry parameter (usually taking the information from GCS) + + The item shall be derived from TmItemClass class. + + If not overriden, the adapter class provides a generic TM item structure. + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the item creation. + # return theItem + + #========================================================================== + def _injectItem(self, tmItem, value, config = {} ): + """ + Inject a ground value for a TM parameter. + + tmItem : instance of TmItemClass with the parameter information + value : contains the required value + config : configuration dictionary with modifiers. + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the item injection. + # return True/False + + #========================================================================== + def _refreshItem(self, tmItem, config = {} ): + """ + Acquire the value for a TM parameter. + + tmItem : instance of TmItemClass with the parameter information + config : configuration dictionary with modifiers. + + Relevant modifiers are + + - Wait=True/False: when True, the system shall block the caller until + a new sample of the required parameter arrives to the GCS. If False, + the last recorded value of the parameter shall be returned immediately. + + - ValueFormat=RAW/ENG: if RAW is given, the raw or uncalibrated value + of the parameter shall be given. If ENG is used, the engineering or + calibrated value shall be returned. + + - Timeout=(TIME): maximum amount of time to wait for new samples, + before the parameter is declared as impossible to acquire. It only + makes sense when Wait=True. + + Returned value: a list with the value and True/False indicating the validity + of the parameter + """ + raise DriverException("Service not available in this driver") + # return [ theValue, theValidity ] + + #=========================================================================== + def _setLimit(self, param, limit, value, config ): + """ + Modify an Out Of Limits definition for a given telemetry parameter + + param: the telemetry parameter name + limit: the limit to be modified + value: the limit value + config: configuration dictionary with modifiers + + Relevant modifiers are: + + - Select=ACTIVE/ALL/: indicates which definitions should be affected + (all, only the active ones, or the one indicated by + the string) + + The limit to be modified may be one of the following: + + LoRed,LoYel,HiRed,HiYel: indicate the hard and soft limit values + + Nominal,Earning,Error,Ignore: the values are lists containing the values + assigned to each category, e.g. Nominal:['ValueA','ValueB'] + + Delta: set a step or spike limit + """ + raise DriverException("Service not available in this driver") + # return True/False + + #=========================================================================== + def _getLimit(self, param, limit, config ): + """ + Obtain an Out Of Limits definition for a given telemetry parameter + + param: the telemetry parameter name + limit: the limit to be modified + config: configuration dictionary with modifiers + + Relevant modifiers are: + + - Select=ACTIVE/ALL/: indicates which definitions should be considered + (all, only the active ones, or the one indicated by + the string) + + The limit to be obtained may be one of the following: + + LoRed,LoYel,HiRed,HiYel: indicate the hard and soft limit values + + Nominal,Earning,Error,Ignore: the values are lists containing the values + assigned to each category, e.g. Nominal:['ValueA','ValueB'] + + Delta: step or spike limit + """ + raise DriverException("Service not available in this driver") + # return the limit value or raise exception + + #=========================================================================== + def _getLimits(self, param, config ): + """ + Obtain an Out Of Limits definition for a given telemetry parameter + + param: the telemetry parameter name + config: configuration dictionary with modifiers + + Relevant modifiers are: + + - Select=ACTIVE/ALL/: indicates which definitions should be considered + (all, only the active ones, or the one indicated by + the string) + + The limit definition to be returned may be one of the following: + + {LoRed,LoYel,HiRed,HiYel}: indicate the hard and soft limit values + + {Nominal,Earning,Error,Ignore}: the values are lists containing the values + assigned to each category, e.g. Nominal:['ValueA','ValueB'] + + {Delta}: step or spike limit + """ + raise DriverException("Service not available in this driver") + # return the limit values or raise exception + + #=========================================================================== + def _setLimits(self, param, limits, config ): + """ + Modify an Out Of Limits definition for a given telemetry parameter + + param: the telemetry parameter name + limits: dictionary containing the limit parameters + config: configuration dictionary with modifiers + + Relevant modifiers are: + + - Select=ACTIVE/ALL/: indicates which definitions should be affected + (all, only the active ones, or the one indicated by + the string) + + Limit parameters dictionary may contain the following: + + LoRed,LoYel,HiRed,HiYel: indicate the hard and soft limit values + + Midpoint,Tolerance: indicate a middle limit point and a tolerance that is + used to calculate the upper and lower limits (M+T, M-T) + Hard and soft limits are considered equal. + + Nominal,Earning,Error,Ignore: the values are lists containing the values + assigned to each category, e.g. Nominal:['ValueA','ValueB'] + + Delta: set a step or spike limit + """ + raise DriverException("Service not available in this driver") + # return True/False + + #=========================================================================== + def _loadLimits(self, param, limits, config ): + """ + Load a set of limits definitions for a given parameter, from a given file. + + param: telemetry parameter name + limits: file with limit definitions + config: configuration parameters + + TBD + """ + raise DriverException("Service not available in this driver") + # return True/False + +################################################################################ +# Interface handle +TM = TmInterface() diff --git a/drivers/example/python/usr.py b/drivers/example/python/usr.py new file mode 100644 index 0000000..a5e3b23 --- /dev/null +++ b/drivers/example/python/usr.py @@ -0,0 +1,130 @@ +################################################################################### +## MODULE : usr +## DATE : Mar 18, 2011 +## PROJECT : SPELL +## DESCRIPTION: User management interface of the driver connection layer +## -------------------------------------------------------------------------------- +## +## Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +## +## This file is part of SPELL. +## +## This component is free software: you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published by +## the Free Software Foundation, either version 3 of the License, or +## (at your option) any later version. +## +## This software 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 General Public License for more details. +## +## You should have received a copy of the GNU General Public License +## along with SPELL. If not, see . +## +################################################################################### + +#******************************************************************************* +# SPELL imports +#******************************************************************************* +from spell.lib.exception import DriverException +from spell.lang.constants import * +from spell.lang.modifiers import * + +#******************************************************************************* +# Local imports +#******************************************************************************* + +#******************************************************************************* +# System imports +#******************************************************************************* + +############################################################################### +# Module import definition + +__all__ = ['USER'] + +############################################################################### +# Superclass +import spell.lib.adapter.usr.UserInterface as superClass + +############################################################################### +# User management interface. +############################################################################### +class UserInterface(superClass): + + #========================================================================== + def __init__(self): + """ + Perform the initialization of data here, after the + superclass initialization + """ + superClass.__init__(self) + # TODO Your initialization stuff here + + #========================================================================== + def setup(self, ctxConfig, drvConfig): + """ + Start the internal driver user mechanisms + + ctxConfig : provides the current context information (S/C name and others) + drvConfig : provides the configuration parameters for this driver according + to the xml config file. + """ + superClass.setup(self, ctxConfig, drvConfig) + # TODO Your startup stuff here + + #========================================================================== + def cleanup(self): + """ + Perform the cleanup of the internal driver user resources. + """ + superClass.cleanup(self) + # TODO Release resources and cleanup stuff here + + #========================================================================== + def _login(self, username, password, config = {} ): + """ + Login the given user in the GCS system. + + username: user name + password: the user password + config: configuration dictionary with modifiers. + + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the user login + # return True/False + + #========================================================================== + def _logout(self, username, config = {}): + """ + Logout the given user from the GCS system. + + username: user name + config: configuration dictionary with modifiers. + + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the user logout + # return True/False + + #========================================================================== + def _isLoggedIn(self, username, config = {}): + """ + Check if the given user is logged in the GCS system. + + username: user name + config: configuration dictionary with modifiers. + + """ + raise DriverException("Service not available in this driver") + # TODO if the service is supported remove the exception raise and + # implement the user login check + # return True/False + +############################################################################### +# Interface instance +USER = UserInterface() diff --git a/drivers/service/__init__.py b/drivers/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/drivers/service/cif_client.py b/drivers/service/cif_client.py new file mode 100644 index 0000000..3a25d59 --- /dev/null +++ b/drivers/service/cif_client.py @@ -0,0 +1,47 @@ + +from spell.lib.registry import REGISTRY +from spell.utils.log import LOG + +debugOn = False +def debug(msg): + if debugOn: print(msg) + +# CIF interface +class CIF_Client(): + + def __init__(self): + self._ready = False + + def _setup(self, channel=None, options=None): + self._ready = True + LOG.setRedirectMethod(self.remoteLog) + + # Change CIF verbosity + def setVerbosity(self, *arg): + debug('CIF setVerbosity: %s' % repr(arg)) + + # Reset CIF verbosity + def resetVerbosity(self, *arg): + debug('CIF resetVerbosity: %s' % repr(arg)) + + # Write a message + def write(self, *arg): + debug('CIF write: %s' % repr(arg)) + + # Send a notification + def notify(self, notifyType = None, name = None, value = None, + status = None, reason = None, time = None, stack = None): + debug('CIF Notify: %s %s=%s (%s) %s' \ + % (notifyType, name, value, status, reason)) + + # Send a prompt + def prompt(self, *arg): + debug('CIF prompt: %s' % repr(arg)) + + def remoteLog(self, message, severity, level): + # return False: To prevent the LoggerClass from handling the logging itself (prevent double output in log file and console log) + # return True: Append log entry to configured log file and/or console output as configured + return True + +# init +REGISTRY['CIF'] = CIF_Client() diff --git a/drivers/service/driverserver.py b/drivers/service/driverserver.py new file mode 100644 index 0000000..fb57dd1 --- /dev/null +++ b/drivers/service/driverserver.py @@ -0,0 +1,130 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLExecutor_pb2_grpc import SPELLExecutorStub +from spell.stubs.SPELLDriver_pb2 import Interfaces +import spell.stubs.SPELLDriver_pb2 as SPELLDriver_pb2 +import spell.stubs.SPELLDriver_pb2_grpc as SPELLDriver_pb2_grpc + +from info_service import DriverService +from tm_service import SPELLDriverTMInterfaceServicer +from tc_service import SPELLDriverTCInterfaceServicer +from ev_service import SPELLDriverEVInterfaceServicer +from rsc_service import SPELLDriverRSCInterfaceServicer +from time_service import SPELLDriverTIMEInterfaceServicer +from task_service import SPELLDriverTASKInterfaceServicer +from user_service import SPELLDriverUSERInterfaceServicer +from rng_service import SPELLDriverRNGInterfaceServicer +from mem_service import SPELLDriverMEMInterfaceServicer +try: + from pcs_service import SPELLDriverPCSInterfaceServicer +except: + pass + +from spell.utils.grpc_utils import GRPCThreadPool +from spell.utils.log import LOG + +from concurrent import futures +from contextlib import closing +import grpc +from grpc import RpcError +import socket +import time +import signal + +KNOWN_INTERFACES = { + 'TM' : lambda server: (Interfaces.TM , SPELLDriverTMInterfaceServicer(server)), # @UndefinedVariable + 'TC' : lambda server: (Interfaces.TC , SPELLDriverTCInterfaceServicer(server)), # @UndefinedVariable + 'EV' : lambda server: (Interfaces.EV , SPELLDriverEVInterfaceServicer(server)), # @UndefinedVariable + 'RSC' : lambda server: (Interfaces.RSC , SPELLDriverRSCInterfaceServicer(server)), # @UndefinedVariable + 'TIME' : lambda server: (Interfaces.TIME , SPELLDriverTIMEInterfaceServicer(server)), # @UndefinedVariable + 'TASK' : lambda server: (Interfaces.TASK , SPELLDriverTASKInterfaceServicer(server)), # @UndefinedVariable + 'USER' : lambda server: (Interfaces.USER , SPELLDriverUSERInterfaceServicer(server)), # @UndefinedVariable +# 'CONFIG': lambda server: (Interfaces.CONFIG , SPELLDriverCONFIGInterfaceServicer(server)), # @UndefinedVariable + 'RNG' : lambda server: (Interfaces.RNG , SPELLDriverRNGInterfaceServicer(server)), # @UndefinedVariable + 'MEM' : lambda server: (Interfaces.MEM , SPELLDriverMEMInterfaceServicer(server)), # @UndefinedVariable + 'PCS' : lambda server: (Interfaces.PCS , SPELLDriverPCSInterfaceServicer(server)) # @UndefinedVariable + } + +# Global for signal handling +server = None + +def signal_handler(signal, frame): + global server + log('signal received: %s' % str(signal)) + if server != None: + server.stop(0) + server = None + REGISTRY['EXEC']._close() + +def log(msg): + LOG('[driverserver]: ' + msg) + +def find_free_port(): + with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + s.bind(('', 0)) + return s.getsockname()[1] + +def serve(executorHost = None, executorPort = None): + global server + server = grpc.server(GRPCThreadPool('DRIVER', max_workers=50)) + port = find_free_port() + service = DriverService(port) + SPELLDriver_pb2_grpc.add_SPELLDriverInterfaceServicer_to_server(service, server) + endpoint = '[::]:' + str(port) + port = server.add_insecure_port(endpoint) + service._setPort(port) + + # install signal handler + signal.signal(signal.SIGINT, signal_handler) + signal.signal(signal.SIGTERM, signal_handler) + + log('starting gRPC server at ' + endpoint) + server.start() + + if executorHost and executorPort: + log('connecting to SPELL Executor') + + driverName = REGISTRY['CTX'].getDriver() + driverLogin = SPELLDriver_pb2.DriverInfo(driverName = driverName, + host =socket.getfqdn(), + port = port) + + driverInterfaces = REGISTRY['CONFIG'].getDriverConfig().getInterfaces() + log('registering the following interfaces for driver %s:' % driverName) + log(driverInterfaces) + for interface in driverInterfaces.split(','): + interface = interface.strip() + if interface in list(KNOWN_INTERFACES.keys()): + try: + log('creating driver interface %s' % interface) + ifcId, ifcService = KNOWN_INTERFACES[interface](server) + if ifcService: + driverLogin.interfaces.append(ifcId) + log('created driver interface %s for id %s ' \ + % (interface, ifcId)) + except: + log('could not create interface service for %s!' % interface) + import traceback + traceback.print_exc() + del traceback + + channel = grpc.insecure_channel('%s:%d' % (executorHost, executorPort)) + stub = SPELLExecutorStub(channel) + try: + response = stub.DriverLogin(driverLogin) + REGISTRY['CIF']._setup(channel, response) + REGISTRY['EXEC']._setup(channel, response) + log('login to SPELL Executor succeeded') + except RpcError as e: + log('login to SPELL Executor failed with error code %s' % e.code()) + log(e) + server.stop(0) + REGISTRY['EXEC']._close() + return + else: + REGISTRY['CIF']._setup() + REGISTRY['EXEC']._setup() + + log('started to serve requests, hit ctrl-c to stop') + while server != None: + time.sleep(1) diff --git a/drivers/service/ev_service.py b/drivers/service/ev_service.py new file mode 100644 index 0000000..bbcea53 --- /dev/null +++ b/drivers/service/ev_service.py @@ -0,0 +1,37 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_EVServicer,\ + add_SPELLDriverInterface_EVServicer_to_server +from spell.utils.grpc_utils import UnpackValue, LogFunctionInput + +from google.protobuf.empty_pb2 import Empty + +class SPELLDriverEVInterfaceServicer(SPELLDriverInterface_EVServicer): + + def __init__(self, server): + add_SPELLDriverInterface_EVServicer_to_server(self, server) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['EV'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['EV'].cleanup() + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + return Empty() + + def RaiseEvent(self, request, context): + msg = str(request.message) + config = UnpackValue(request.config) + + REGISTRY['EV']._raiseEvent(msg, config) + + return Empty() diff --git a/drivers/service/exec_client.py b/drivers/service/exec_client.py new file mode 100644 index 0000000..4486f95 --- /dev/null +++ b/drivers/service/exec_client.py @@ -0,0 +1,130 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLExecutor_pb2_grpc import SPELLExecutorStub +from spell.utils.timing import RepeatingThread + +from google.protobuf.empty_pb2 import Empty +from grpc import RpcError +import os +from spell.config.reader import Config + +debugOn = False +def debug(msg): + if debugOn: print(msg) + +# EXEC interface +class EXEC_Client(SPELLExecutorStub): + + def __init__(self): + self._ready = False + self._ping = None + self._retryCount = 0 + self._spellAccess = False + + def _setup(self, channel=None, options=None): + self._ready = True + if not channel: + return + SPELLExecutorStub.__init__(self, channel) + self.createPingThread() + + def _close(self): + if self._ping is None: + return + self._ping.cancel() + self._ping = None + + def createPingThread(self): + self._ping = RepeatingThread(REGISTRY['CONFIG'].getDriverConfig().getGRPCPingTime(), + self.checkConnectionAlive) + self._ping.start() + + def checkConnectionAlive(self): + try: + self.Ping(Empty(), + timeout = REGISTRY['CONFIG'].getDriverConfig().getGRPCTimeout()) + self._retryCount = 0 + debug('[DRIVER] SPELL Executor is alive!') + except RpcError as e: + import traceback + excMsg = traceback.format_exception_only(RpcError, e) + self._retryCount += 1 + if self._retryCount <= REGISTRY['CONFIG'].getDriverConfig().getGRPCRetry(): + debug('[DRIVER] SPELL Executor does not react anymore! Retry %s/%s' \ + % (self._retryCount, REGISTRY['CONFIG'].getDriverConfig().getGRPCRetry())) + debug(str(excMsg)) + else: + debug('[DRIVER] SPELL Executor stopped reacting! Exiting SPELL Driver') + os._exit(0) + del traceback + + # Obtain executor status + def getStatus(self, *arg): + debug('EXEC: getStatus: %s' % repr(arg)) + + # Obtain execution environment + def getEnvironment(self, *arg): + debug('EXEC: getEnvironment: %s' % repr(arg)) + + # Set the language lock + def processLock(self, *arg): + debug('EXEC: processLock: %s' % repr(arg)) + + # Unset the language lock + def processUnlock(self, *arg): + debug('EXEC: processUnlock: %s' % repr(arg)) + + # Start waiting process + def startWait(self, *arg): + debug('EXEC: startWait: %s' % repr(arg)) + + # End waiting process + def finishWait(self, *arg): + debug('EXEC: finishWait: %s' % repr(arg)) + + # Start prompt + def startPrompt(self, *arg): + debug('EXEC: startPrompt: %s' % repr(arg)) + + # End prompt + def endPrompt(self, *arg): + debug('EXEC: endPrompt: %s' % repr(arg)) + + # Wait for event + def wait(self, *arg): + debug('EXEC: wait: %s' % repr(arg)) + + # Open child procedure + def openSubProcedure(self, *arg): + debug('EXEC: openSubProcedure: %s' % repr(arg)) + + # Close child procedure + def closeSubProcedure(self, *arg): + debug('EXEC: closeSubProcedure: %s' % repr(arg)) + + # Kill child procedure + def killSubProcedure(self, *arg): + debug('EXEC: killSubProcedure: %s' % repr(arg)) + + # Get child procedure status + def getChildStatus(self, *arg): + debug('EXEC: getChildStatus: %s' % repr(arg)) + + # Get child procedure error info + def getChildError(self, *arg): + debug('EXEC: getChildError: %s' % repr(arg)) + + # Is subprocedure in error + def isChildError(self, *arg): + debug('EXEC: isChildError: %s' % repr(arg)) + + # Is subprocedure finished + def isChildFinished(self, *arg): + debug('EXEC: isChildFinished: %s' % repr(arg)) + + # Abort execution + def abort(self, *arg): + debug('EXEC: abort: %s' % repr(arg)) + +# init +REGISTRY['EXEC'] = EXEC_Client() diff --git a/drivers/service/info_service.py b/drivers/service/info_service.py new file mode 100644 index 0000000..d725c59 --- /dev/null +++ b/drivers/service/info_service.py @@ -0,0 +1,35 @@ + +from spell.lib.registry import REGISTRY +import spell.stubs.SPELLDriver_pb2 as SPELLDriver_pb2 +import spell.stubs.SPELLDriver_pb2_grpc as SPELLDriver_pb2_grpc + +from google.protobuf.empty_pb2 import Empty +import socket +from threading import Timer + +class DriverService(SPELLDriver_pb2_grpc.SPELLDriverInterfaceServicer): + + def __init__(self, port): + self.driverName = REGISTRY['CTX'].getDriver() + self.host = socket.getfqdn() + self.port = port + + def _setPort(self, port): + self.port = port + + def GetDriverInfo(self, request, context): + return SPELLDriver_pb2.DriverInfo(driverName = self.driverName, + host = self.host, port = self.port) + + def Cleanup(self, request, context): + return request + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + # TODO: test + def doExit(): + exit(0) + exitTimer = Timer(2,doExit,[]).start() + return Empty() diff --git a/drivers/service/mem_service.py b/drivers/service/mem_service.py new file mode 100644 index 0000000..6cd15bb --- /dev/null +++ b/drivers/service/mem_service.py @@ -0,0 +1,103 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_MEMServicer,\ + add_SPELLDriverInterface_MEMServicer_to_server +from spell.utils.grpc_utils import UnpackValue, LogFunctionInput, PackValue + +from google.protobuf.empty_pb2 import Empty +from google.protobuf.wrappers_pb2 import BoolValue + +class SPELLDriverMEMInterfaceServicer(SPELLDriverInterface_MEMServicer): + + def __init__(self, server): + add_SPELLDriverInterface_MEMServicer_to_server(self, server) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['MEM'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['MEM'].cleanup() + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + return Empty() + + def GenerateReport(self, request, context): + image = request.image + report_type = request.report_type + begin = request.begin + end = request.end + source = request.source + config = UnpackValue(request.config) + + result = REGISTRY['MEM']._generateReport(image, report_type, begin, + end, source, config) + + return BoolValue(value = result) + + def CompareImages(self, request, context): + image1 = request.image1 + image2 = request.image2 + report_type = request.report_type + begin = request.begin + end = request.end + source = request.source + config = UnpackValue(request.config) + + result = REGISTRY['MEM']._compareImages(image1, image2, report_type, + begin, end, source, config) + + return BoolValue(value = result) + + + def CreateGoldenImage(self, request, context): + scp = request.scp + filename = request.filename + config = UnpackValue(request.config) + + result = REGISTRY['MEM']._createGoldenImage(self, scp, filename, config) + + return BoolValue(value = result) + + + def MemoryLookup(self, request, context): + name = request.name + address = request.address + image = request.image + report_type = request.report_type + source = request.source + config = UnpackValue(request.config) + + result = REGISTRY['MEM']._memoryLookup(name, address, image, + report_type, source, config) + + return PackValue(result) + + + def DataObjectLookup(self, request, context): + name = request.name + ltype = request.type + config = UnpackValue(request.config) + + result = REGISTRY['MEM']._dataObjectLookup(name, ltype, config) + + return PackValue(result) + + + def DataItemLookup(self, request, context): + name = request.name + ltype = request.type + source = request.source + valformat = request.valformat + config = UnpackValue(request.config) + + result = REGISTRY['MEM']._dataItemLookup(name, ltype, source, valformat, config) + + return PackValue(result) diff --git a/drivers/service/pcs_service.py b/drivers/service/pcs_service.py new file mode 100644 index 0000000..7b14e4b --- /dev/null +++ b/drivers/service/pcs_service.py @@ -0,0 +1,166 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_PCSServicer,\ + add_SPELLDriverInterface_PCSServicer_to_server +from spell.stubs.SPELLDriver_pb2 import Names, PtcResult +from spell.utils.grpc_utils import UnpackValue, LogFunctionInput, PackValue + +from google.protobuf.empty_pb2 import Empty +from google.protobuf.wrappers_pb2 import BoolValue, StringValue +from spell.lang.functions import PCS_GetStatus + +class SPELLDriverPCSInterfaceServicer(SPELLDriverInterface_PCSServicer): + + def __init__(self, server): + self._ptcItemRegistry = {} + add_SPELLDriverInterface_PCSServicer_to_server(self, server) + + #========================================================================== + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['PCS'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + #========================================================================== + def Cleanup(self, request, context): + REGISTRY['PCS'].cleanup() + return Empty() + + #========================================================================== + def Ping(self, request, context): + return Empty() + + #========================================================================== + def Close(self, request, context): + return Empty() + + #========================================================================== + + def CreatePtcItem(self, request, context): + ptcId = request.id + if not ptcId or ptcId not in list(self._ptcItemRegistry.keys()): + ptcItem = REGISTRY['PCS']._createPtcItem(str(request.mnemonic), + str(request.description)) + ptcId = request.mnemonic + self._ptcItemRegistry[str(ptcId)] = ptcItem + request.id = ptcId + + return request + + #========================================================================== + def _findItem(self, request): + # RSA: do we need all these str conversions? + # strings coming from grpc are all in unicode. + # str and unicode have different hashes even if they got the same content. + # That's why we have to convert all values to str when using dicts. + if str(request.item.id) in self._ptcItemRegistry: + ptcItem = self._ptcItemRegistry[str(request.item.id)] + else: + ptcItem = REGISTRY['PCS']._createPtcItem(str(request.item.mnemonic), + str(request.item.description)) + request.item.id = str(request.item.mnemonic) + + return ptcItem + + #========================================================================== + def PCS_GetStatus(self, request, context): + answer = REGISTRY['PCS']._PCS_GetStatus(UnpackValue(request)) + + return PackValue(answer) + + #========================================================================== + def PCS_IsArqEnabled(self, request, context): + answer = REGISTRY['PCS']._PCS_IsArqEnabled(UnpackValue(request)) + + return answer.value + + #========================================================================== + def PCS_IsVerifyModeEnabled(self, request, context): + answer = REGISTRY['PCS']._PCS_IsVerifyModeEnabled(UnpackValue(request)) + + return answer.value + + #========================================================================== + def PCS_ReadyToCommand(self, request, context): + answer = REGISTRY['PCS']._PCS_ReadyToCommand(UnpackValue(request)) + + return answer.value + + #========================================================================== + def PCS_AbortPtc(self, request, context): + answer = REGISTRY['PCS']._PCS_AbortPtc(UnpackValue(request)) + + return answer.value + + #========================================================================== + def _extractPtc(self, request): + ptcItem = self._ptcItemRegistry[str(request.command.id)] + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + args = UnpackValue(request.args) + self._addArgsToPTC(ptcItem, args, config) + + return ptcItem + + #========================================================================== + def _addArgsToPTC(self, ptcItem, args, config): + ptcItem.clear() + # RSA: Is this correct? + for arg in args: + ptcItem[arg[0]] = arg[1:] + ptcItem.configure(config) + + #========================================================================== + def _createPtcResultAnswer(self,result): + answer = PtcResult(code = result['code'], + success = result['success'], + stage = result['stage'], + data = result['data'], + error = result['error']) + + return answer + + #========================================================================== + def PCS_Control(self, request, context): + ptcItem = self._extractPtc(request) + + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + result = REGISTRY['PCS']._PCS_Control(ptcItem, config) + + return self._createPtcResultAnswer(result) + #========================================================================== + def SendPtcCommand(self, request, context): + ptcItem = self._extractPtc(request) + + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + result = REGISTRY['PCS']._sendPtcCommand(ptcItem, config) + + return self._createPtcResultAnswer(result) + + #========================================================================== + def SendPtcList(self, request, context): + cmdList = [] + for cmd in request.commands: + cmdList += [self._extractPtc(cmd)] + + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + result = REGISTRY['PCS']._sendPtcList(cmdList, config) + + return self._createPtcResultAnswer(result) diff --git a/drivers/service/rng_service.py b/drivers/service/rng_service.py new file mode 100644 index 0000000..96f1246 --- /dev/null +++ b/drivers/service/rng_service.py @@ -0,0 +1,128 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_RNGServicer,\ + add_SPELLDriverInterface_RNGServicer_to_server +from spell.stubs.SPELLDriver_pb2 import Names +from spell.utils.grpc_utils import UnpackValue, LogFunctionInput + +from google.protobuf.empty_pb2 import Empty +from google.protobuf.wrappers_pb2 import BoolValue, StringValue + +class SPELLDriverRNGInterfaceServicer(SPELLDriverInterface_RNGServicer): + + def __init__(self, server): + add_SPELLDriverInterface_RNGServicer_to_server(self, server) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['RNG'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['RNG'].cleanup() + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + return Empty() + + def EnableRanging(self, request, context): + config = UnpackValue(request.config) + + result = REGISTRY['RNG']._enableRanging(config) + + return BoolValue(value = result) + + def DisableRanging(self, request, context): + config = UnpackValue(request.config) + + result = REGISTRY['RNG']._disableRanging(config) + + return BoolValue(value = result) + + def AbortRanging(self, request, context): + config = UnpackValue(request.config) + + result = REGISTRY['RNG']._abortRanging(config) + + return BoolValue(value = result) + + + def StartRanging(self, request, context): + config = UnpackValue(request.config) + bbe = request.bbe + antenna = request.antenna + + result = REGISTRY['RNG']._startRanging(bbe, antenna, config) + + return BoolValue(value = result) + + + def StartCalibration(self, request, context): + config = UnpackValue(request.config) + bbe = request.bbe + antenna = request.antenna + + result = REGISTRY['RNG']._startCalibration(bbe, antenna, config) + + return BoolValue(value = result) + + def GetBasebandConfig(self, request, context): + config = UnpackValue(request.config) + bbe = request.bbe + param = request.param + + result = REGISTRY['RNG']._getBasebandConfig(bbe, param, config) + + request.value = str(result) + + return request + + def SetBasebandConfig(self, request, context): + config = UnpackValue(request.config) + bbe = request.bbe + param = request.param + value = request.value + + result = REGISTRY['RNG']._setBasebandConfig(bbe, param, value, config) + + return BoolValue(value = result) + + def GetAntennaNames(self, request, context): + config = UnpackValue(request) + + result = REGISTRY['RNG']._getAntennaNames(config) + + return Names(names = result) + + def GetRangingPaths(self, request, context): + config = UnpackValue(request) + + result = REGISTRY['RNG']._getRangingPaths(config) + + return Names(names = result) + + def GetBasebandNames(self, request, context): + config = UnpackValue(request) + + result = REGISTRY['RNG']._getBasebandNames(config) + + return Names(names = result) + + def GetRangingStatus(self, request, context): + config = UnpackValue(request) + + result = REGISTRY['RNG']._getRangingStatus(config) + + return StringValue(value = result) + + def UpdateCortexConf(self, request, context): + config = UnpackValue(request) + + result = REGISTRY['RNG']._updateCortexConf(config) + + return BoolValue(value = result) diff --git a/drivers/service/rsc_service.py b/drivers/service/rsc_service.py new file mode 100644 index 0000000..4e139c7 --- /dev/null +++ b/drivers/service/rsc_service.py @@ -0,0 +1,76 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_RSCServicer,\ + add_SPELLDriverInterface_RSCServicer_to_server +from spell.stubs.SPELLDriver_pb2 import ResourceRequest +from spell.utils.grpc_utils import UnpackValue, PackValue, unicode2string,\ + LogFunctionInput + +from google.protobuf.empty_pb2 import Empty +from google.protobuf.wrappers_pb2 import BoolValue + +class SPELLDriverRSCInterfaceServicer(SPELLDriverInterface_RSCServicer): + + def __init__(self, server): + self._tmItemRegistry = {} + add_SPELLDriverInterface_RSCServicer_to_server(self, server) + + def SetLink(self, request, context): + REGISTRY['RSC'].setLink(str(request.name), unicode2string(request.status), + UnpackValue(request.config)) + return BoolValue(value = True) + + def CheckLink(self, request, context): + result = REGISTRY['RSC'].checkLink(str(request.name)) + + return BoolValue(value = result) + + def SetResource(self, request, context): + result = REGISTRY['RSC']._setResource(str(request.name), + UnpackValue(request.value), + UnpackValue(request.config)) + + return BoolValue(value = result) + + def GetResource(self, request, context): + result = REGISTRY['RSC']._getResource(str(request.name), + UnpackValue(request.config)) + + return ResourceRequest(value = PackValue(result)) + + def GetResources(self, request, context): + # FIXME: What about isLocal? + result = REGISTRY['RSC']._getResources(UnpackValue(request.config)) + + return ResourceRequest(value = PackValue(result)) + + def GetResourceStatus(self, request, context): + result = REGISTRY['RSC']._getResourceStatus(str(request.name), + UnpackValue(request.config)) + + return ResourceRequest(value = PackValue(result)) + + def IsResourceOK(self, request, context): + result = REGISTRY['RSC']._isResourceOK(str(request.name), + UnpackValue(request.config)) + + return BoolValue(value = result) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['RSC'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['RSC'].cleanup() + + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + # TODO + return Empty() diff --git a/drivers/service/task_service.py b/drivers/service/task_service.py new file mode 100644 index 0000000..6dea479 --- /dev/null +++ b/drivers/service/task_service.py @@ -0,0 +1,74 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_TASKServicer,\ + add_SPELLDriverInterface_TASKServicer_to_server +from spell.utils.grpc_utils import UnpackValue, LogFunctionInput + +from google.protobuf.empty_pb2 import Empty +from google.protobuf.wrappers_pb2 import BoolValue + +class SPELLDriverTASKInterfaceServicer(SPELLDriverInterface_TASKServicer): + + def __init__(self, server): + add_SPELLDriverInterface_TASKServicer_to_server(self, server) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['TASK'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['TASK'].cleanup() + + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + return Empty() + + def StartTask(self, request, context): + name = request.name + args = UnpackValue(request.args) + config = UnpackValue(request.config) + answer = REGISTRY['TASK']._startTask(name, args, config) + + return BoolValue(value = answer) + + def StopTask(self, request, context): + name = request.name + config = UnpackValue(request.config) + answer = REGISTRY['TASK']._stopTask(name, config) + + return BoolValue(value = answer) + + def CheckTask(self, request, context): + name = request.name + config = UnpackValue(request.config) + answer = REGISTRY['TASK']._checkTask(name, config) + + return BoolValue(value = answer) + + def OpenDisplay(self, request, context): + name = request.name + config = UnpackValue(request.config) + REGISTRY['TASK']._openDisplay(name, config) + + return Empty() + + def CloseDisplay(self, request, context): + name = request.name + config = UnpackValue(request.config) + REGISTRY['TASK']._closeDisplay(name, config) + + return Empty() + + def PrintDisplay(self, request, context): + name = request.name + config = UnpackValue(request.config) + REGISTRY['TASK']._printDisplay(name, config) + + return Empty() diff --git a/drivers/service/tc_service.py b/drivers/service/tc_service.py new file mode 100644 index 0000000..2b06272 --- /dev/null +++ b/drivers/service/tc_service.py @@ -0,0 +1,102 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_TCServicer,\ + add_SPELLDriverInterface_TCServicer_to_server +from spell.utils.grpc_utils import UnpackValue, LogFunctionInput + +from google.protobuf.empty_pb2 import Empty +from google.protobuf.wrappers_pb2 import BoolValue + +class SPELLDriverTCInterfaceServicer(SPELLDriverInterface_TCServicer): + + def __init__(self, server): + self._tcItemRegistry = {} + add_SPELLDriverInterface_TCServicer_to_server(self, server) + + def CreateTcItem(self, request, context): + tcId = request.id + if not tcId or tcId not in list(self._tcItemRegistry.keys()): + tcItem = REGISTRY['TC']._createTcItem(str(request.mnemonic), + str(request.description)) + tcId = id(tcItem) + self._tcItemRegistry[str(tcId)] = tcItem + request.id = tcId + + return request + + def _extractCommand(self, request): + tcItem = self._tcItemRegistry[str(request.command.id)] + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + args = UnpackValue(request.args) + self._addArgsToTC(tcItem, args, config) + + return tcItem + + def _addArgsToTC(self, tcItem, args, config): + tcItem.clear() + # RSA: Is this correct? + for arg in args: + tcItem[arg[0]] = arg[1:] + tcItem.configure(config) + + def SendCommand(self, request, context): + tcItem = self._extractCommand(request) + + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + result = REGISTRY['TC']._sendCommand(tcItem, config) + + return BoolValue(value = result) + + def SendList(self, request, context): + cmdList = [] + for cmd in request.commands: + cmdList += [self._extractCommand(cmd)] + + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + result = REGISTRY['TC']._sendList(cmdList, config) + + return BoolValue(value = result) + + def SendBlock(self, request, context): + cmdList = [] + for cmd in request.commands: + cmdList += [self._extractCommand(cmd)] + + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + result = REGISTRY['TC']._sendBlock(cmdList, config) + + return BoolValue(value = result) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['TC'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['TC'].cleanup() + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + # TODO + return Empty() diff --git a/drivers/service/time_service.py b/drivers/service/time_service.py new file mode 100644 index 0000000..78648ef --- /dev/null +++ b/drivers/service/time_service.py @@ -0,0 +1,37 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_TIMEServicer,\ + add_SPELLDriverInterface_TIMEServicer_to_server +from spell.stubs.SPELLDriver_pb2 import Time +from spell.utils.grpc_utils import UnpackValue, LogFunctionInput + +from google.protobuf.empty_pb2 import Empty + +class SPELLDriverTIMEInterfaceServicer(SPELLDriverInterface_TIMEServicer): + + def __init__(self, server): + add_SPELLDriverInterface_TIMEServicer_to_server(self, server) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['TIME'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['TIME'].cleanup() + + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + return Empty() + + def GetUTC(self, request, context): + config = UnpackValue(request) + answer = REGISTRY['TIME']._getUTC(config) + + return Time(timeMsg = str(answer)) diff --git a/drivers/service/tm_service.py b/drivers/service/tm_service.py new file mode 100644 index 0000000..747ad62 --- /dev/null +++ b/drivers/service/tm_service.py @@ -0,0 +1,142 @@ + +from spell.lib.registry import REGISTRY +from spell.stubs.SPELLDriver_pb2 import LookupAnswer, LimitSet +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_TMServicer,\ + add_SPELLDriverInterface_TMServicer_to_server +from spell.utils.grpc_utils import UnpackValue, PackValue, LogFunctionInput + +from google.protobuf.empty_pb2 import Empty +from google.protobuf.wrappers_pb2 import BoolValue + +class SPELLDriverTMInterfaceServicer(SPELLDriverInterface_TMServicer): + + def __init__(self, server): + self._tmItemRegistry = {} + add_SPELLDriverInterface_TMServicer_to_server(self, server) + + def CreateTmItem(self, request, context): + tmId = request.id + if not tmId or tmId not in list(self._tmItemRegistry.keys()): + tmItem = REGISTRY['TM']._createTmItem(str(request.mnemonic), + str(request.description)) + tmId = request.mnemonic + self._tmItemRegistry[str(tmId)] = tmItem + request.id = tmId + + return request + + def _findItem(self, request): + # RSA: do we need all these str conversions? + if str(request.item.id) in self._tmItemRegistry: + tmItem = self._tmItemRegistry[str(request.item.id)] + else: + tmItem = REGISTRY['TM']._createTmItem(str(request.item.mnemonic), + str(request.item.description)) + request.item.id = str(request.item.mnemonic) + + return tmItem + + def RefreshItem(self, request, context): + tmItem = self._findItem(request) + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + _, validity = REGISTRY['TM']._refreshItem(tmItem, config) + + timestamp = str(tmItem.time()) + request.engValue.CopyFrom(PackValue(tmItem.eng())) + request.rawValue.CopyFrom(PackValue(tmItem.raw())) + + if timestamp and tmItem.time() is not None: + request.timestamp = timestamp + + request.validity = bool(validity) + + return request + + def DatabaseLookup(self, request, context): + answer = REGISTRY['TM']._databaseLookup(str(request.name), + str(request.type), + str(request.source)) + + return LookupAnswer(lookup = PackValue(answer)) + + def GetLimits(self, request, context): + tmItem = self._findItem(request) + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + answer = REGISTRY['TM']._getLimits(tmItem,config) + + return LimitSet(limitset = PackValue(answer)) + + def SetLimits(self, request, context): + tmItem = self._findItem(request) + if request.HasField('set'): + limitset = UnpackValue(request.set.limitset) + else: + limitset = {} + + answer = REGISTRY['TM']._setLimits(tmItem,limitset) + if answer is None: + answer = True + + return BoolValue(value = answer) + + def IsAlarmed(self, request, context): + tmItem = self._findItem(request) + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + + answer = REGISTRY['TM']._isAlarmed(tmItem, config) + if answer is None: + answer = False + + return BoolValue(value = answer) + + def InjectItem(self, request, context): + tmItem = self._findItem(request) + if request.HasField('config'): + config = UnpackValue(request.config) + else: + config = {} + value = UnpackValue(request.engValue) + + answer = REGISTRY['TM']._injectItem(tmItem, value, config) + + if answer is None: + answer = False + + return BoolValue(value = answer) + + def RestoreNormalLimits(self, request, context): + answer = REGISTRY['TM']._restoreNormalLimits(UnpackValue(request)) + + if answer is None: + answer = False + + return BoolValue(value = answer) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['TM'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['TM'].cleanup() + + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + # TODO + return Empty() diff --git a/drivers/service/user_service.py b/drivers/service/user_service.py new file mode 100644 index 0000000..8d0338b --- /dev/null +++ b/drivers/service/user_service.py @@ -0,0 +1,57 @@ + +from spell.stubs.SPELLDriver_pb2_grpc import SPELLDriverInterface_USERServicer,\ + add_SPELLDriverInterface_USERServicer_to_server +from spell.lib.registry import REGISTRY +from spell.utils.grpc_utils import UnpackValue, LogFunctionInput + +from google.protobuf.empty_pb2 import Empty +from google.protobuf.wrappers_pb2 import BoolValue + +class SPELLDriverUSERInterfaceServicer(SPELLDriverInterface_USERServicer): + + def __init__(self, server): + add_SPELLDriverInterface_USERServicer_to_server(self, server) + + def Setup(self, request, context): + from spell.lib.drivermgr import DriverManager + mgr = DriverManager.instance() + REGISTRY['USER'].setup(mgr.contextConfig,mgr.driverConfig) + del DriverManager + return request + + def Cleanup(self, request, context): + REGISTRY['USER'].cleanup() + + return Empty() + + def Ping(self, request, context): + return Empty() + + def Close(self, request, context): + return Empty() + + def Login(self, request, context): + name = request.username + pwd = request.password + config = UnpackValue(request.config) + + result = REGISTRY['USER']._login(name, pwd, config) + + return BoolValue(value = result) + + def Logout(self, request, context): + name = request.username + config = UnpackValue(request.config) + + result = REGISTRY['USER']._logout(name, config) + + return BoolValue(value = result) + + + def IsLoggedIn(self, request, context): + name = request.username + config = UnpackValue(request.config) + + result = REGISTRY['USER']._isLoggedIn(name, config) + + return BoolValue(value = result) diff --git a/include/SPELL_CFG/SPELLbrowsableLibMode.H b/include/SPELL_CFG/SPELLbrowsableLibMode.H index f85ca7f..49e2d19 100644 --- a/include/SPELL_CFG/SPELLbrowsableLibMode.H +++ b/include/SPELL_CFG/SPELLbrowsableLibMode.H @@ -5,7 +5,7 @@ // DESCRIPTION: Browsable modes // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CFG/SPELLconfiguration.H b/include/SPELL_CFG/SPELLconfiguration.H index b758501..1f51510 100644 --- a/include/SPELL_CFG/SPELLconfiguration.H +++ b/include/SPELL_CFG/SPELLconfiguration.H @@ -5,7 +5,7 @@ // DESCRIPTION: Wrapper to configuration reader instance // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CFG/SPELLcontextConfig.H b/include/SPELL_CFG/SPELLcontextConfig.H index 7d8500f..0cf6571 100644 --- a/include/SPELL_CFG/SPELLcontextConfig.H +++ b/include/SPELL_CFG/SPELLcontextConfig.H @@ -5,7 +5,7 @@ // DESCRIPTION: Wrapper for context configuration // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -76,6 +76,7 @@ public: //-------------------------------------------------------------------- // STATIC //////////////////////////////////////////////////////////////// static const std::string ExecutorProgram; + static const std::string ServiceProgram; static const std::string ExecutorStartTimeout; static const std::string ExecutorLoginTimeout; static const std::string UseDriverTime; @@ -137,6 +138,12 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// std::string getProcPath() const { return m_procPath; }; + /** Obtain the current procedure path. + * + * \returns The procedure search path. + **//////////////////////////////////////////////////////////////////// + std::string getServicePath() const { return m_servicePath; }; + ////////////////////////////////////////////////////////////////////// /** Obtain the current library path. * @@ -292,6 +299,8 @@ private: //------------------------------------------------------------------- std::string m_procPath; /** Holds the library path */ std::string m_libPath; + /** Holds the service path */ + std::string m_servicePath; /** Holds the output data directory */ std::string m_outputDir; /** Holds the input data directory */ diff --git a/include/SPELL_CFG/SPELLdriverConfig.H b/include/SPELL_CFG/SPELLdriverConfig.H index a6f60dd..cee0254 100644 --- a/include/SPELL_CFG/SPELLdriverConfig.H +++ b/include/SPELL_CFG/SPELLdriverConfig.H @@ -5,7 +5,7 @@ // DESCRIPTION: Wrapper for driver configuration // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CFG/SPELLexecutorDefaults.H b/include/SPELL_CFG/SPELLexecutorDefaults.H index e92022b..a59b9cf 100644 --- a/include/SPELL_CFG/SPELLexecutorDefaults.H +++ b/include/SPELL_CFG/SPELLexecutorDefaults.H @@ -5,7 +5,7 @@ // DESCRIPTION: Context default values for Executors // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -32,7 +32,7 @@ // Project includes ---------------------------------------------------------- #include "SPELL_WRP/SPELLconstants.H" #include "SPELL_CFG/SPELLbrowsableLibMode.H" - +#include "SPELL_UTIL/SPELLutils.H" /** \addtogroup SPELL_CFG */ /*@{*/ @@ -59,7 +59,20 @@ public: //-------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** Constructor. **//////////////////////////////////////////////////////////////////// - SPELLexecutorDefaults() {}; + SPELLexecutorDefaults() + { + m_browsableLib = OFF; + m_byStep = false; + m_execDelay = 0; + m_watchVariables = false; + m_resourcesDisplay = false; + m_localResourceSupport = false; + m_forcePromptConfirm = false; + m_forceTcConfirm = false; + m_maxVerbosity = 0; + m_runInto = false; + m_promptWarningDelay = 0; + }; ////////////////////////////////////////////////////////////////////// /** Destructor. @@ -74,58 +87,71 @@ public: //-------------------------------------------------------------------- // METHODS /////////////////////////////////////////////////////////////// // Member variables getters - int getExecDelay() + int getExecDelay() const { return m_execDelay; } - int getPromptWarningDelay() + int getPromptWarningDelay() const { return m_promptWarningDelay; } - bool isRunInto() + bool isRunInto() const { return m_runInto; } - bool isByStep() + bool isByStep() const { return m_byStep; } - SPELLbrowsableLibMode getBrowsableLib() + SPELLbrowsableLibMode getBrowsableLib() const { return m_browsableLib; } - std::string getBrowsableLibStr() + std::string getBrowsableLibStr() const { return browsableLibModeToString(m_browsableLib); } - int getMaxVerbosity() + int getMaxVerbosity() const { return m_maxVerbosity; } - bool isForceTcConfirm() + bool isForceTcConfirm() const { return m_forceTcConfirm; } - - std::string getSaveStateMode() + bool isForcePromptConfirm() const + { + return m_forcePromptConfirm; + } + std::string getSaveStateMode() const { return m_saveStateMode; } //getSaveStateMode - bool isWatchVariables() + bool isWatchVariables() const { return m_watchVariables; } - std::string getCtxName() + bool isResourcesDisplay() const + { + return m_resourcesDisplay; + } + + bool isLocalResourcesSupported() const + { + return m_localResourceSupport; + } + + std::string getCtxName() const { return m_ctxName; } @@ -173,7 +199,10 @@ public: //-------------------------------------------------------------------- { m_forceTcConfirm = forceTcConfirm; } - + void setForcePromptConfirm(bool forcePromptConfirm) + { + m_forcePromptConfirm = forcePromptConfirm; + } void setSaveStateMode(std::string saveModeStr) { m_saveStateMode = saveModeStr; @@ -184,6 +213,16 @@ public: //-------------------------------------------------------------------- m_watchVariables = watchVariables; } + void setResourcesDisplay(bool resourcesDisplay) + { + m_resourcesDisplay = resourcesDisplay; + } + + void setLocalResourcesSupported(bool localResourceSupport) + { + m_localResourceSupport = localResourceSupport; + } + void setCtxName( std::string ctxName) { m_ctxName = ctxName; @@ -199,7 +238,10 @@ public: //-------------------------------------------------------------------- str.append("BrowsableLib: ").append( browsableLibModeToString(m_browsableLib) ).append("\n"); str.append("MaxVerbosity: ").append(ISTR(m_maxVerbosity)).append("\n"); str.append("ForceTcConfirm: ").append(BSTR(m_forceTcConfirm)).append("\n"); + str.append("ForcePromptConfirm: ").append(BSTR(m_forcePromptConfirm)).append("\n"); str.append("WatchVariables: ").append(BSTR(m_watchVariables)).append("\n"); + str.append("ResourcesDisplay: ").append(BSTR(m_resourcesDisplay)).append("\n"); + str.append("LocalResourcesSupported: ").append(BSTR(m_localResourceSupport)).append("\n"); str.append("SaveStateMode: ").append(m_saveStateMode).append("\n"); return str; @@ -218,15 +260,18 @@ private: //------------------------------------------------------------------- // DATA MEMBERS ////////////////////////////////////////////////////////// int m_execDelay; - int m_promptWarningDelay; + int m_promptWarningDelay; bool m_runInto; bool m_byStep; //bool m_browsableLib; SPELLbrowsableLibMode m_browsableLib; int m_maxVerbosity; bool m_forceTcConfirm; + bool m_forcePromptConfirm; std::string m_saveStateMode; bool m_watchVariables; + bool m_resourcesDisplay; + bool m_localResourceSupport; std::string m_ctxName; }; diff --git a/include/SPELL_CFG/SPELLxmlConfigReader.H b/include/SPELL_CFG/SPELLxmlConfigReader.H index 7131b89..eedf53c 100644 --- a/include/SPELL_CFG/SPELLxmlConfigReader.H +++ b/include/SPELL_CFG/SPELLxmlConfigReader.H @@ -5,7 +5,7 @@ // DESCRIPTION: Wrapper for the XML configuration reader // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CFG/SPELLxmlConfigReaderFactory.H b/include/SPELL_CFG/SPELLxmlConfigReaderFactory.H index 6f6f0da..50c3251 100644 --- a/include/SPELL_CFG/SPELLxmlConfigReaderFactory.H +++ b/include/SPELL_CFG/SPELLxmlConfigReaderFactory.H @@ -5,7 +5,7 @@ // DESCRIPTION: Factory for XML configuration readers // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CFG/SPELLxmlConfigReaderXC.H b/include/SPELL_CFG/SPELLxmlConfigReaderXC.H index bc3074c..c012458 100644 --- a/include/SPELL_CFG/SPELLxmlConfigReaderXC.H +++ b/include/SPELL_CFG/SPELLxmlConfigReaderXC.H @@ -5,7 +5,7 @@ // DESCRIPTION: Xerces-C based implementation of the XML config. reader // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CFG/SPELLxmlNode.H b/include/SPELL_CFG/SPELLxmlNode.H index 993fd65..eaf6f2e 100644 --- a/include/SPELL_CFG/SPELLxmlNode.H +++ b/include/SPELL_CFG/SPELLxmlNode.H @@ -5,7 +5,7 @@ // DESCRIPTION: Wrapper for XML configuration node // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIF/SPELLasRun.H b/include/SPELL_CIF/SPELLasRun.H index e27df9f..6245b32 100644 --- a/include/SPELL_CIF/SPELLasRun.H +++ b/include/SPELL_CIF/SPELLasRun.H @@ -5,7 +5,7 @@ // DESCRIPTION: As-Run file interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIF/SPELLcif.H b/include/SPELL_CIF/SPELLcif.H index 8eb3d0c..809a0b0 100644 --- a/include/SPELL_CIF/SPELLcif.H +++ b/include/SPELL_CIF/SPELLcif.H @@ -5,7 +5,7 @@ // DESCRIPTION: Main CIF object interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -37,6 +37,7 @@ #include "SPELL_CIF/SPELLcifStartupInfo.H" // Project includes -------------------------------------------------------- #include "SPELL_EXC/SPELLvarInfo.H" +#include "SPELL_EXC/SPELLresourceInfo.H" #include "SPELL_PRD/SPELLprocedureSourceCode.H" #include "SPELL_SYN/SPELLevent.H" @@ -226,6 +227,8 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void setVerbosity( int verbosity ); + void setForcePromptConfirm( bool force ); + ////////////////////////////////////////////////////////////////////// /** Set the current verbosity to the maximum allowed value **//////////////////////////////////////////////////////////////////// @@ -344,7 +347,7 @@ public: //-------------------------------------------------------------------- * \param definition IN: prompt parameters **//////////////////////////////////////////////////////////////////// //virtual std::string prompt( const SPELLpromptDefinition& definition ) = 0; - virtual std::string prompt( const SPELLpromptDefinition& definition ); + virtual std::string prompt( const SPELLpromptDefinition& definition,bool doPromptConfirm = true ); ////////////////////////////////////////////////////////////////////// /** Notify a variable change. @@ -360,6 +363,18 @@ public: //-------------------------------------------------------------------- const std::vector& globals, const std::vector& locals ); + + ////////////////////////////////////////////////////////////////////// + /** Notify a resource change. + **//////////////////////////////////////////////////////////////////// + virtual void notifyResourceChange( const std::vector& changed ); + + ////////////////////////////////////////////////////////////////////// + /** Notify a resource list. + **//////////////////////////////////////////////////////////////////// + virtual void notifyResourceList( const std::vector& resources ); + + ////////////////////////////////////////////////////////////////////// /** Substitute the current ASRUN file by the given one * @@ -535,6 +550,16 @@ protected: //----------------------------------------------------------------- const std::vector& locals ) {;}; + ////////////////////////////////////////////////////////////////////// + /** See Notify a resource change. + **//////////////////////////////////////////////////////////////////// + virtual void specificNotifyResourceChange( const std::vector& changed ) {;}; + + ////////////////////////////////////////////////////////////////////// + /** See Notify a resource list. + **//////////////////////////////////////////////////////////////////// + virtual void specificNotifyResourceList( const std::vector& resources ) {;}; + ////////////////////////////////////////////////////////////////////// /** See OpenSubprocedure **//////////////////////////////////////////////////////////////////// @@ -595,6 +620,8 @@ private: //------------------------------------------------------------------- std::string m_procId; /** True if manual mode is enabled */ bool m_manualMode; + + bool m_forcePromptConfirm; /** Current verbosity level */ int m_verbosity; diff --git a/include/SPELL_CIF/SPELLcifHelper.H b/include/SPELL_CIF/SPELLcifHelper.H index eeeef63..8c58a17 100644 --- a/include/SPELL_CIF/SPELLcifHelper.H +++ b/include/SPELL_CIF/SPELLcifHelper.H @@ -5,7 +5,7 @@ // DESCRIPTION: Utilities for CIF // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIF/SPELLcifStartupInfo.H b/include/SPELL_CIF/SPELLcifStartupInfo.H index 298bf7a..31126d1 100644 --- a/include/SPELL_CIF/SPELLcifStartupInfo.H +++ b/include/SPELL_CIF/SPELLcifStartupInfo.H @@ -5,7 +5,7 @@ // DESCRIPTION: Information for CIF startup // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIF/SPELLnotifications.H b/include/SPELL_CIF/SPELLnotifications.H index 8d5e09a..aa4471d 100644 --- a/include/SPELL_CIF/SPELLnotifications.H +++ b/include/SPELL_CIF/SPELLnotifications.H @@ -5,7 +5,7 @@ // DESCRIPTION: Notification data structures // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIF/SPELLpromptDefinition.H b/include/SPELL_CIF/SPELLpromptDefinition.H index c81a9ab..ca4246f 100644 --- a/include/SPELL_CIF/SPELLpromptDefinition.H +++ b/include/SPELL_CIF/SPELLpromptDefinition.H @@ -5,7 +5,7 @@ // DESCRIPTION: Parameters defining a prompt // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIF/SPELLstatusInfo.H b/include/SPELL_CIF/SPELLstatusInfo.H index fb23a21..a54c545 100644 --- a/include/SPELL_CIF/SPELLstatusInfo.H +++ b/include/SPELL_CIF/SPELLstatusInfo.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor status information structure // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIFC/SPELLautomaticCif.H b/include/SPELL_CIFC/SPELLautomaticCif.H index 7d66e6b..97fa047 100644 --- a/include/SPELL_CIFC/SPELLautomaticCif.H +++ b/include/SPELL_CIFC/SPELLautomaticCif.H @@ -5,7 +5,7 @@ // DESCRIPTION: Automatic (non-interactive) client interface implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -146,6 +146,16 @@ public: //-------------------------------------------------------------------- const std::vector& locals ); + ////////////////////////////////////////////////////////////////////// + /** Notify a resource change. + **//////////////////////////////////////////////////////////////////// + void specificNotifyResourceChange( const std::vector& changed ); + + ////////////////////////////////////////////////////////////////////// + /** Notify a resource list. + **//////////////////////////////////////////////////////////////////// + void specificNotifyResourceList( const std::vector& resources ); + ////////////////////////////////////////////////////////////////////// /** Open subprocedure. * diff --git a/include/SPELL_CIFC/SPELLncursesShell.H b/include/SPELL_CIFC/SPELLncursesShell.H index e992f27..1c87c3e 100644 --- a/include/SPELL_CIFC/SPELLncursesShell.H +++ b/include/SPELL_CIFC/SPELLncursesShell.H @@ -5,7 +5,7 @@ // DESCRIPTION: Command line shell for standalone execution (NCurses) // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -31,8 +31,7 @@ // System includes --------------------------------------------------------- #include #include -#include -#include +#include // Local includes ---------------------------------------------------------- // Project includes -------------------------------------------------------- #include "SPELL_SYN/SPELLthread.H" diff --git a/include/SPELL_CIFC/SPELLstandaloneCif.H b/include/SPELL_CIFC/SPELLstandaloneCif.H index 31a56b9..b6346b2 100644 --- a/include/SPELL_CIFC/SPELLstandaloneCif.H +++ b/include/SPELL_CIFC/SPELLstandaloneCif.H @@ -5,7 +5,7 @@ // DESCRIPTION: Standalone execution client interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIFS/SPELLcifPromptHelper.H b/include/SPELL_CIFS/SPELLcifPromptHelper.H index 17f7711..559e757 100644 --- a/include/SPELL_CIFS/SPELLcifPromptHelper.H +++ b/include/SPELL_CIFS/SPELLcifPromptHelper.H @@ -5,7 +5,7 @@ // DESCRIPTION: Helper for prompt processing // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIFS/SPELLdisplayBuffer.H b/include/SPELL_CIFS/SPELLdisplayBuffer.H index 8b3bd1b..2d8432b 100644 --- a/include/SPELL_CIFS/SPELLdisplayBuffer.H +++ b/include/SPELL_CIFS/SPELLdisplayBuffer.H @@ -5,7 +5,7 @@ // DESCRIPTION: Buffer for Display() messages // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CIFS/SPELLrequestProcessor.H b/include/SPELL_CIFS/SPELLrequestProcessor.H index 6df9f83..2bade15 100644 --- a/include/SPELL_CIFS/SPELLrequestProcessor.H +++ b/include/SPELL_CIFS/SPELLrequestProcessor.H @@ -5,7 +5,7 @@ // DESCRIPTION: Processing code for client requests // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -161,6 +161,33 @@ protected: //----------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void processCheckVariablesEnabled( const SPELLipcMessage& msg, SPELLipcMessage& response ); + ////////////////////////////////////////////////////////////////////// + /** Process a request to get current resource names. + * + * \param msg IN: IPC request. + * \param response INOUT: IPC response to complete. + * + **//////////////////////////////////////////////////////////////////// + void processGetResources( const SPELLipcMessage& msg, SPELLipcMessage& response ); + + ////////////////////////////////////////////////////////////////////// + /** Process a request to change a resource value + * + * \param msg IN: IPC request. + * \param response INOUT: IPC response to complete. + * + **//////////////////////////////////////////////////////////////////// + void processChangeResource( const SPELLipcMessage& msg, SPELLipcMessage& response ); + + ////////////////////////////////////////////////////////////////////// + /** Process a request to check if resource analysis is enabled + * + * \param msg IN: IPC request. + * \param response INOUT: IPC response to complete. + * + **//////////////////////////////////////////////////////////////////// + void processCheckResourcesEnabled( const SPELLipcMessage& msg, SPELLipcMessage& response ); + ////////////////////////////////////////////////////////////////////// /** Process a request to get global dictionary contents * diff --git a/include/SPELL_CIFS/SPELLresourceBuffer.H b/include/SPELL_CIFS/SPELLresourceBuffer.H new file mode 100644 index 0000000..6950743 --- /dev/null +++ b/include/SPELL_CIFS/SPELLresourceBuffer.H @@ -0,0 +1,146 @@ +// ################################################################################ +// FILE : SPELLresourceBuffer.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Buffer for Resource Display of TC Options (RDOTCO) messages +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_RESOURCE_BUFFER_H__ +#define __SPELL_RESOURCE_BUFFER_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +// Project includes -------------------------------------------------------- +#include "SPELL_EXC/SPELLresourceInfo.H" +#include "SPELL_SYN/SPELLthread.H" +#include "SPELL_SYN/SPELLthread.H" +#include "SPELL_IPC/SPELLipcMessage.H" +#include "SPELL_SYN/SPELLmutex.H" + +/** \addtogroup SPELL_CIFS */ +/*@{*/ + + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +class SPELLserverCif; +// TYPES /////////////////////////////////////////////////////////////////// +// DEFINES ///////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief Buffer for RDOTCO messages + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLresourceBuffer : public SPELLthread +{ +public: //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLresourceBuffer( const std::string& procId, SPELLserverCif& cif ); + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + ~SPELLresourceBuffer(); + + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Main thread method + **//////////////////////////////////////////////////////////////////// + void run(); + + ////////////////////////////////////////////////////////////////////// + /** Stop the buffer + **//////////////////////////////////////////////////////////////////// + void stop(); + + ////////////////////////////////////////////////////////////////////// + /** Send a resource update message + **//////////////////////////////////////////////////////////////////// + void resourceChange( const std::vector& changed ); + + ////////////////////////////////////////////////////////////////////// + /** Send a resource list message + **//////////////////////////////////////////////////////////////////// + void resourceList( const std::vector& resources ); + + ////////////////////////////////////////////////////////////////////// + /** Collect the accumulated information and send the IPC message. + **//////////////////////////////////////////////////////////////////// + void flush(); + +protected: //----------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // STATIC //////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + +private: //------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // STATIC //////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Check if data available + **//////////////////////////////////////////////////////////////////// + bool hasDataToSend(); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + + /** Holds the reference to the CIF */ + SPELLserverCif& m_cif; + /** Working flag */ + bool m_working; + /** Synchronization lock */ + SPELLmutex m_lock; + /** Notification message skeletons */ + SPELLipcMessage m_resourceMessage; + /** List of added, updated and removed resources + * Note that it is a map, so updates overwrite: + * if a resource is added, then right away modified, then right away removed, + * only the removal effect will remain + * */ + std::map m_resources; + /** Accumulation of notifications */ + unsigned int m_total; + /** state whether the flush contain result of GetResources */ + bool m_bufferHasListResources; +}; + +/*@}*/ +#endif diff --git a/include/SPELL_CIFS/SPELLserverCif.H b/include/SPELL_CIFS/SPELLserverCif.H index 115ebea..d69a900 100644 --- a/include/SPELL_CIFS/SPELLserverCif.H +++ b/include/SPELL_CIFS/SPELLserverCif.H @@ -5,7 +5,7 @@ // DESCRIPTION: Client interface for server environment // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -33,6 +33,7 @@ #include "SPELL_CIFS/SPELLrequestProcessor.H" #include "SPELL_CIFS/SPELLdisplayBuffer.H" #include "SPELL_CIFS/SPELLvariableBuffer.H" +#include "SPELL_CIFS/SPELLresourceBuffer.H" // Project includes -------------------------------------------------------- #include "SPELL_SYN/SPELLthread.H" #include "SPELL_SYN/SPELLevent.H" @@ -164,6 +165,16 @@ public: //-------------------------------------------------------------------- const std::vector& globals, const std::vector& locals ); + ////////////////////////////////////////////////////////////////////// + /** Notify a resource change. + **//////////////////////////////////////////////////////////////////// + void specificNotifyResourceChange( const std::vector& changed ); + + ////////////////////////////////////////////////////////////////////// + /** Notify a resource list. + **//////////////////////////////////////////////////////////////////// + void specificNotifyResourceList( const std::vector& resources ); + ////////////////////////////////////////////////////////////////////// /** See openSubprocedure **//////////////////////////////////////////////////////////////////// @@ -184,7 +195,9 @@ public: //-------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** Get prompt information if any **//////////////////////////////////////////////////////////////////// - SPELLpromptDefinition getPromptInfo() { return m_promptDef; } + SPELLpromptDefinition getPromptInfo() { return m_promptDef; }; + + SPELLipcMessage getPingMessage(); //==================================================================== // Python bindings end @@ -196,6 +209,7 @@ protected: //----------------------------------------------------------------- friend class SPELLdisplayBuffer; friend class SPELLvariableBuffer; + friend class SPELLresourceBuffer; // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// @@ -248,7 +262,7 @@ protected: //----------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** Wait acknowledge from client *//////////////////////////////////////////////////////////////////// - void waitAcknowledge( const SPELLipcMessage& msg ); + bool waitAcknowledge( const SPELLipcMessage& msg ); //==================================================================== // Shared data handling @@ -341,7 +355,6 @@ protected: //----------------------------------------------------------------- *//////////////////////////////////////////////////////////////////// void prepareMessage( SPELLipcMessage& msg, const std::string dataType, SPELLipcMessageType_ msgType ); - // DATA MEMBERS ////////////////////////////////////////////////////////// private: //------------------------------------------------------------------- @@ -408,6 +421,8 @@ private: //------------------------------------------------------------------- SPELLdisplayBuffer* m_buffer; /** Buffer to accumulate WOV messages */ SPELLvariableBuffer* m_wovBuffer; + /** Buffer to accumulate RDOTCO messages */ + SPELLresourceBuffer* m_rdotcoBuffer; /** True when the CIF is ready to work */ @@ -437,6 +452,8 @@ private: //------------------------------------------------------------------- /** IPC synchronization */ SPELLmutex m_ipcLock; + /** Promp synchronization */ + SPELLmutex m_promptLock; /** Close synchronization */ SPELLmutex m_closeLock; /** Closing flag */ diff --git a/include/SPELL_CIFS/SPELLvariableBuffer.H b/include/SPELL_CIFS/SPELLvariableBuffer.H index d59bd2c..899e7ec 100644 --- a/include/SPELL_CIFS/SPELLvariableBuffer.H +++ b/include/SPELL_CIFS/SPELLvariableBuffer.H @@ -5,7 +5,7 @@ // DESCRIPTION: Buffer for WOV messages // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLclient.H b/include/SPELL_CTX/SPELLclient.H index bf4d23e..3efe632 100644 --- a/include/SPELL_CTX/SPELLclient.H +++ b/include/SPELL_CTX/SPELLclient.H @@ -5,7 +5,7 @@ // DESCRIPTION: Client manager and controller // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLclientIPC.H b/include/SPELL_CTX/SPELLclientIPC.H index c2eaf29..12f6ee4 100644 --- a/include/SPELL_CTX/SPELLclientIPC.H +++ b/include/SPELL_CTX/SPELLclientIPC.H @@ -5,7 +5,7 @@ // DESCRIPTION: IPC interface for clients // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLclientInterestList.H b/include/SPELL_CTX/SPELLclientInterestList.H index 2cef023..7784308 100644 --- a/include/SPELL_CTX/SPELLclientInterestList.H +++ b/include/SPELL_CTX/SPELLclientInterestList.H @@ -5,7 +5,7 @@ // DESCRIPTION: List of the entities interested on a given client // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLclientListener.H b/include/SPELL_CTX/SPELLclientListener.H index 371422a..07b7aa8 100644 --- a/include/SPELL_CTX/SPELLclientListener.H +++ b/include/SPELL_CTX/SPELLclientListener.H @@ -5,7 +5,7 @@ // DESCRIPTION: The implementing class is interested on client messages // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLclientManager.H b/include/SPELL_CTX/SPELLclientManager.H index 74c172e..a2cab8b 100644 --- a/include/SPELL_CTX/SPELLclientManager.H +++ b/include/SPELL_CTX/SPELLclientManager.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL client manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLclientMode.H b/include/SPELL_CTX/SPELLclientMode.H index f25bf30..1e89cd6 100644 --- a/include/SPELL_CTX/SPELLclientMode.H +++ b/include/SPELL_CTX/SPELLclientMode.H @@ -5,7 +5,7 @@ // DESCRIPTION: Client mode definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLclientNotifier.H b/include/SPELL_CTX/SPELLclientNotifier.H index 60c72a1..81222a1 100644 --- a/include/SPELL_CTX/SPELLclientNotifier.H +++ b/include/SPELL_CTX/SPELLclientNotifier.H @@ -5,7 +5,7 @@ // DESCRIPTION: Client notifier for monitoring mode // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLclientOperation.H b/include/SPELL_CTX/SPELLclientOperation.H index dc9e1f1..faca460 100644 --- a/include/SPELL_CTX/SPELLclientOperation.H +++ b/include/SPELL_CTX/SPELLclientOperation.H @@ -5,7 +5,7 @@ // DESCRIPTION: Definition of the client operations // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLcontext.H b/include/SPELL_CTX/SPELLcontext.H index 2571ec0..a4e1073 100644 --- a/include/SPELL_CTX/SPELLcontext.H +++ b/include/SPELL_CTX/SPELLcontext.H @@ -5,7 +5,7 @@ // DESCRIPTION: Main class of the context process // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -53,7 +53,9 @@ // FORWARD REFERENCES //////////////////////////////////////////////////////// // TYPES ///////////////////////////////////////////////////////////////////// // DEFINES /////////////////////////////////////////////////////////////////// - +extern "C" { + void checkService( std::string& serviceId ); +} ////////////////////////////////////////////////////////////////////////////// /** ** \brief @@ -127,6 +129,7 @@ public: //-------------------------------------------------------------------- //==================================================================== SPELLipcMessage openExecutor( const SPELLipcMessage& msg, SPELLclient* controllingClient ); + bool openService( const std::string serviceName ); SPELLipcMessage getInstanceId(const SPELLipcMessage& msg); SPELLipcMessage getCurrentTime(const SPELLipcMessage& msg); SPELLipcMessage closeExecutor(const SPELLipcMessage& msg); @@ -136,10 +139,12 @@ public: //-------------------------------------------------------------------- SPELLipcMessage setExecutorInBackground( const SPELLipcMessage& msg ); SPELLipcMessage getProcedureList( const SPELLipcMessage& msg ); SPELLipcMessage getExecutorList( const SPELLipcMessage& msg ); + SPELLipcMessage getServiceList( const SPELLipcMessage& msg ); SPELLipcMessage getProcedureProperties( const SPELLipcMessage& msg ); SPELLipcMessage getProcedureCode( const SPELLipcMessage& msg ); SPELLipcMessage getExecutorInfo(const SPELLipcMessage& msg); - SPELLipcMessage attachExecutor(const SPELLipcMessage& msg, SPELLclient* client ); + SPELLipcMessage getServiceInfo(const SPELLipcMessage& msg); + SPELLipcMessage attachExecutor(const SPELLipcMessage& msg, SPELLclient* client ); SPELLipcMessage detachExecutor(const SPELLipcMessage& msg, SPELLclient* client ); SPELLipcMessage getClientInfo(const SPELLipcMessage& msg); SPELLipcMessage getServerFilePath(const SPELLipcMessage& msg); @@ -169,6 +174,8 @@ public: //-------------------------------------------------------------------- //==================================================================== void clientLost( int clientKey ); void executorLost( const std::string& procId ); + void serviceLost( const std::string& procId ); + // STATIC //////////////////////////////////////////////////////////////// diff --git a/include/SPELL_CTX/SPELLcontextConfiguration.H b/include/SPELL_CTX/SPELLcontextConfiguration.H index 5ec5c1a..bfc7798 100644 --- a/include/SPELL_CTX/SPELLcontextConfiguration.H +++ b/include/SPELL_CTX/SPELLcontextConfiguration.H @@ -5,7 +5,7 @@ // DESCRIPTION: Configuration for the context process // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLdataHelper.H b/include/SPELL_CTX/SPELLdataHelper.H index 0288af5..d03885d 100644 --- a/include/SPELL_CTX/SPELLdataHelper.H +++ b/include/SPELL_CTX/SPELLdataHelper.H @@ -5,7 +5,7 @@ // DESCRIPTION: Utilities for data handling // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLdataTable.H b/include/SPELL_CTX/SPELLdataTable.H index 223366a..3b64e8f 100644 --- a/include/SPELL_CTX/SPELLdataTable.H +++ b/include/SPELL_CTX/SPELLdataTable.H @@ -5,7 +5,7 @@ // DESCRIPTION: Shared data table // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLexecutor.H b/include/SPELL_CTX/SPELLexecutor.H index 5d60e63..b083f88 100644 --- a/include/SPELL_CTX/SPELLexecutor.H +++ b/include/SPELL_CTX/SPELLexecutor.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor manager and controller // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -46,7 +46,9 @@ // FORWARD REFERENCES //////////////////////////////////////////////////////// + class LoginMonitor; + // TYPES ///////////////////////////////////////////////////////////////////// // DEFINES /////////////////////////////////////////////////////////////////// @@ -55,7 +57,7 @@ class LoginMonitor; ** \brief ** **//////////////////////////////////////////////////////////////////////////// -class SPELLexecutor : public SPELLexecutorListener, SPELLprocessListener +class SPELLexecutor : public SPELLexecutorListener,public SPELLprocessListener { public: //-------------------------------------------------------------------- @@ -79,126 +81,126 @@ public: //-------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** Start the executor process **//////////////////////////////////////////////////////////////////// - void start(); + virtual void start(); ////////////////////////////////////////////////////////////////////// /** Close the executor process **//////////////////////////////////////////////////////////////////// - void close(); + virtual void close(); ////////////////////////////////////////////////////////////////////// /** Kill the executor process **//////////////////////////////////////////////////////////////////// - void kill(); + virtual void kill(); ////////////////////////////////////////////////////////////////////// /** Wait for a given executor status * * \returns True on timeout **//////////////////////////////////////////////////////////////////// - bool waitStatus( const SPELLexecutorStatus& status, unsigned int timeoutSec ); + virtual bool waitStatus( const SPELLexecutorStatus& status, unsigned int timeoutSec ); ////////////////////////////////////////////////////////////////////// /** Wait for executor login * * \returns True if logged in **//////////////////////////////////////////////////////////////////// - bool waitLogin( unsigned int timeoutMsec ); + virtual bool waitLogin( unsigned int timeoutMsec ); ////////////////////////////////////////////////////////////////////// /** Check for executor login * * \returns True if logged in **//////////////////////////////////////////////////////////////////// - bool isLoggedIn() { return m_loggedIn; }; + virtual bool isLoggedIn() { return m_loggedIn; }; ////////////////////////////////////////////////////////////////////// /** Get the status of the executor * * \returns The status **//////////////////////////////////////////////////////////////////// - SPELLexecutorStatus getStatus(); + virtual SPELLexecutorStatus getStatus(); ////////////////////////////////////////////////////////////////////// /** Check if the executor is active * * \returns True if active **//////////////////////////////////////////////////////////////////// - bool isActive(); + virtual bool isActive(); ////////////////////////////////////////////////////////////////////// /** Check if the executor has a parent procedure * * \returns True if has a parent **//////////////////////////////////////////////////////////////////// - bool hasParent() { return m_model.getParentInstanceId() != ""; } ; + virtual bool hasParent() { return m_model.getParentInstanceId() != ""; } ; ////////////////////////////////////////////////////////////////////// /** Get the executor parent procedure * * \returns The parent procedure id **//////////////////////////////////////////////////////////////////// - std::string getParentInstanceId() { return m_model.getParentInstanceId(); } ; + virtual std::string getParentInstanceId() { return m_model.getParentInstanceId(); } ; ////////////////////////////////////////////////////////////////////// /** Send a command to the executor **//////////////////////////////////////////////////////////////////// - void command( const ExecutorCommand& command ); + virtual void command( const ExecutorCommand& command ); ////////////////////////////////////////////////////////////////////// /** Set the controlling client **//////////////////////////////////////////////////////////////////// - void setControllingClient( SPELLclient* client ); + virtual void setControllingClient( SPELLclient* client ); ////////////////////////////////////////////////////////////////////// /** Set the procedure to headless (background) mode **//////////////////////////////////////////////////////////////////// - void setBackground(); + virtual void setBackground(); ////////////////////////////////////////////////////////////////////// /** Check whether the procedure is in headless (background) mode **//////////////////////////////////////////////////////////////////// - bool isBackground() { return m_clientConnection == CLI_BACKGROUND; }; + virtual bool isBackground() { return m_clientConnection == CLI_BACKGROUND; }; ////////////////////////////////////////////////////////////////////// /** Remove the controlling client **//////////////////////////////////////////////////////////////////// - void removeControllingClient( bool clientLost ); + virtual void removeControllingClient( bool clientLost ); ////////////////////////////////////////////////////////////////////// /** Check if it has controlling client **//////////////////////////////////////////////////////////////////// - bool hasControllingClient(); + virtual bool hasControllingClient(); ////////////////////////////////////////////////////////////////////// /** Check if it has controlling client **//////////////////////////////////////////////////////////////////// - SPELLclient* getControllingClient(); + virtual SPELLclient* getControllingClient(); ////////////////////////////////////////////////////////////////////// /** Send a message to the executor process **//////////////////////////////////////////////////////////////////// - void sendMessageToExecutor( const SPELLipcMessage& msg ); + virtual void sendMessageToExecutor( const SPELLipcMessage& msg ); ////////////////////////////////////////////////////////////////////// /** Send a request to the executor process **//////////////////////////////////////////////////////////////////// - SPELLipcMessage sendRequestToExecutor( const SPELLipcMessage& msg ); + virtual SPELLipcMessage sendRequestToExecutor( const SPELLipcMessage& msg ); ////////////////////////////////////////////////////////////////////// /** Get the executor model **//////////////////////////////////////////////////////////////////// - SPELLexecutorModel& getModel() { return m_model; }; + SPELLexecutorModel& getModel() { return m_model; }; ////////////////////////////////////////////////////////////////////// /** Register executor notifier for monitoring mode **//////////////////////////////////////////////////////////////////// - void registerNotifier( std::string id, SPELLexecutorListener* listener ); + virtual void registerNotifier( std::string id, SPELLexecutorListener* listener ); ////////////////////////////////////////////////////////////////////// /** Deregister executor notifier **//////////////////////////////////////////////////////////////////// - void deregisterNotifier( std::string id ); + virtual void deregisterNotifier( std::string id ); // DATA MEMBERS ////////////////////////////////////////////////////////// @@ -207,7 +209,7 @@ protected: //----------------------------------------------------------------- friend class SPELLexecutorInterestList; friend class SPELLprocessManager; friend class SPELLexecutorIPC; - + //friend class SPELLservice; // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// // TYPES ///////////////////////////////////////////////////////////////// @@ -219,9 +221,9 @@ protected: //----------------------------------------------------------------- // EXECUTOR REQUEST HANDLING //==================================================================== - SPELLipcMessage executorLogin( const SPELLipcMessage& msg ); + virtual SPELLipcMessage executorLogin( const SPELLipcMessage& msg ); - void executorNotification( const SPELLipcMessage& msg ); + virtual void executorNotification( const SPELLipcMessage& msg ); //==================================================================== // INTERNAL IPC METHODS @@ -230,27 +232,27 @@ protected: //----------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** \see SPELLexecutorListener. **//////////////////////////////////////////////////////////////////// - void processMessageFromExecutor( SPELLipcMessage msg ); + virtual void processMessageFromExecutor( SPELLipcMessage msg ); ////////////////////////////////////////////////////////////////////// /** \see SPELLexecutorListener. **//////////////////////////////////////////////////////////////////// - SPELLipcMessage processRequestFromExecutor( SPELLipcMessage msg ); + virtual SPELLipcMessage processRequestFromExecutor( SPELLipcMessage msg ); ////////////////////////////////////////////////////////////////////// /** Forward a message from the executor to the client **//////////////////////////////////////////////////////////////////// - void forwardMessageToClient( const SPELLipcMessage& msg ); + virtual void forwardMessageToClient( const SPELLipcMessage& msg ); ////////////////////////////////////////////////////////////////////// /** Forward a request from the executor to the client **//////////////////////////////////////////////////////////////////// - SPELLipcMessage forwardRequestToClient( const SPELLipcMessage& msg ); + virtual SPELLipcMessage forwardRequestToClient( const SPELLipcMessage& msg ); ////////////////////////////////////////////////////////////////////// /** Force an executor pause **//////////////////////////////////////////////////////////////////// - void forcePause(); + virtual void forcePause(); //==================================================================== // INTERNAL PROCESS CONTROL METHODS @@ -261,36 +263,36 @@ protected: //----------------------------------------------------------------- * * \returns True if active **//////////////////////////////////////////////////////////////////// - bool processOk(); + virtual bool processOk(); ////////////////////////////////////////////////////////////////////// /** Wait until the executor process has finished **//////////////////////////////////////////////////////////////////// - void waitFinish(); + virtual void waitFinish(); ////////////////////////////////////////////////////////////////////// /** \see SPELLprocessListener. **//////////////////////////////////////////////////////////////////// - void processStarted( const std::string& identifier ); + virtual void processStarted( const std::string identifier ); ////////////////////////////////////////////////////////////////////// /** \see SPELLprocessListener. **//////////////////////////////////////////////////////////////////// - void processFinished( const std::string& identifier, const int& retValue ); + virtual void processFinished( const std::string identifier, const int& retValue ); ////////////////////////////////////////////////////////////////////// /** \see SPELLprocessListener. **//////////////////////////////////////////////////////////////////// - void processKilled( const std::string& identifier ); + virtual void processKilled( const std::string identifier ); ////////////////////////////////////////////////////////////////////// /** \see SPELLprocessListener. **//////////////////////////////////////////////////////////////////// - void processFailed( const std::string& identifier ); + virtual void processFailed( const std::string identifier ); private: //------------------------------------------------------------------- - + friend class SPELLservice; // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// // TYPES ///////////////////////////////////////////////////////////////// @@ -347,6 +349,19 @@ private: //------------------------------------------------------------------- }; +class LoginMonitor : public SPELLthread +{ +public: + LoginMonitor( SPELLexecutor& exec ) + : SPELLthread("login-" + exec.getModel().getInstanceId() ), + m_exec( exec ) + {} + + void run(); + +private: + SPELLexecutor& m_exec; +}; /*@}*/ #endif diff --git a/include/SPELL_CTX/SPELLexecutorIPC.H b/include/SPELL_CTX/SPELLexecutorIPC.H index ab15f8e..16bf9a7 100644 --- a/include/SPELL_CTX/SPELLexecutorIPC.H +++ b/include/SPELL_CTX/SPELLexecutorIPC.H @@ -5,7 +5,7 @@ // DESCRIPTION: IPC interface for executors // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLexecutorListener.H b/include/SPELL_CTX/SPELLexecutorListener.H index 48d9570..96834fa 100644 --- a/include/SPELL_CTX/SPELLexecutorListener.H +++ b/include/SPELL_CTX/SPELLexecutorListener.H @@ -5,7 +5,7 @@ // DESCRIPTION: The implementing class is interested on executor messages // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLexecutorManager.H b/include/SPELL_CTX/SPELLexecutorManager.H index 7028c58..0fbdc45 100644 --- a/include/SPELL_CTX/SPELLexecutorManager.H +++ b/include/SPELL_CTX/SPELLexecutorManager.H @@ -5,7 +5,7 @@ // DESCRIPTION: Manages the executor operations interacts with clients // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -225,8 +225,6 @@ private: //------------------------------------------------------------------- void addInstanceNumber( const std::string& procId, int instanceNum ); - void removeInstanceNumber( const std::string& procId, int instanceNum ); - void clearModels(); // DATA MEMBERS ////////////////////////////////////////////////////////// diff --git a/include/SPELL_CTX/SPELLexecutorModel.H b/include/SPELL_CTX/SPELLexecutorModel.H index 131bce2..ed1bd8b 100644 --- a/include/SPELL_CTX/SPELLexecutorModel.H +++ b/include/SPELL_CTX/SPELLexecutorModel.H @@ -5,7 +5,7 @@ // DESCRIPTION: Information of a given executor // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -136,7 +136,8 @@ public: //-------------------------------------------------------------------- protected: //----------------------------------------------------------------- - friend class SPELLexecutor; + friend class SPELLexecutor; + friend class SPELLservice; // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// diff --git a/include/SPELL_CTX/SPELLexecutorOperation.H b/include/SPELL_CTX/SPELLexecutorOperation.H index 8bd716e..bec7136 100644 --- a/include/SPELL_CTX/SPELLexecutorOperation.H +++ b/include/SPELL_CTX/SPELLexecutorOperation.H @@ -5,7 +5,7 @@ // DESCRIPTION: Definition of the executor operations // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLexecutorStartupParams.H b/include/SPELL_CTX/SPELLexecutorStartupParams.H index cedb0b6..7e241ee 100644 --- a/include/SPELL_CTX/SPELLexecutorStartupParams.H +++ b/include/SPELL_CTX/SPELLexecutorStartupParams.H @@ -5,7 +5,7 @@ // DESCRIPTION: Configuration for the executor process // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -126,7 +126,7 @@ public: //-------------------------------------------------------------------- void setRecoveryFile( const std::string& recoveryFile ) { m_recoveryFile = recoveryFile; }; private: //------------------------------------------------------------------- - + friend class SPELLserviceStartupParams; // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// // TYPES ///////////////////////////////////////////////////////////////// diff --git a/include/SPELL_CTX/SPELLlistenerIPC.H b/include/SPELL_CTX/SPELLlistenerIPC.H index 1ed02d0..7902e27 100644 --- a/include/SPELL_CTX/SPELLlistenerIPC.H +++ b/include/SPELL_CTX/SPELLlistenerIPC.H @@ -5,7 +5,7 @@ // DESCRIPTION: IPC interface for listener // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLopenMode.H b/include/SPELL_CTX/SPELLopenMode.H index 330e9d7..792390e 100644 --- a/include/SPELL_CTX/SPELLopenMode.H +++ b/include/SPELL_CTX/SPELLopenMode.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor open mode definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLpersistencyTable.H b/include/SPELL_CTX/SPELLpersistencyTable.H index b7912dc..9b59cee 100644 --- a/include/SPELL_CTX/SPELLpersistencyTable.H +++ b/include/SPELL_CTX/SPELLpersistencyTable.H @@ -5,7 +5,7 @@ // DESCRIPTION: Keeps persistency of open executors // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLserverFile.H b/include/SPELL_CTX/SPELLserverFile.H index 9dbcfc8..c5e013c 100644 --- a/include/SPELL_CTX/SPELLserverFile.H +++ b/include/SPELL_CTX/SPELLserverFile.H @@ -5,7 +5,7 @@ // DESCRIPTION: Type of server file definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_CTX/SPELLservice.H b/include/SPELL_CTX/SPELLservice.H new file mode 100644 index 0000000..1b8c3d0 --- /dev/null +++ b/include/SPELL_CTX/SPELLservice.H @@ -0,0 +1,89 @@ +// ################################################################################ +// FILE : SPELLservice.H +// DATE : Apr 11, 2011 +// PROJECT : SPELL +// DESCRIPTION: Executor manager and controller +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_SERVICE_H__ +#define __SPELL_SERVICE_H__ + +// FILES TO INCLUDE ////////////////////////////////////////////////////////// +// Local includes ------------------------------------------------------------ +#include "SPELL_CTX/SPELLserviceListener.H" +#include "SPELL_CTX/SPELLserviceStartupParams.H" +#include "SPELL_CTX/SPELLclient.H" +#include "SPELL_CTX/SPELLexecutor.H" +// Project includes ---------------------------------------------------------- +#include "SPELL_PRC/SPELLprocessManager.H" +#include "SPELL_EXC/SPELLcommand.H" +#include "SPELL_EXC/SPELLexecutorStatus.H" +#include "SPELL_SYN/SPELLevent.H" +#include "SPELL_SYN/SPELLmutex.H" + +/** \addtogroup SPELL_CTX */ +/*@{*/ + + +// FORWARD REFERENCES //////////////////////////////////////////////////////// + +// TYPES ///////////////////////////////////////////////////////////////////// +// DEFINES /////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLservice : public SPELLexecutor +{ +public: //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLservice( const SPELLserviceStartupParams& config ); + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + virtual ~SPELLservice(); + + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Start the executor process + **//////////////////////////////////////////////////////////////////// + void start(); + + + +}; + + +/*@}*/ +#endif diff --git a/include/SPELL_CTX/SPELLserviceListener.H b/include/SPELL_CTX/SPELLserviceListener.H new file mode 100644 index 0000000..a2a6b56 --- /dev/null +++ b/include/SPELL_CTX/SPELLserviceListener.H @@ -0,0 +1,109 @@ +// ################################################################################ +// FILE : SPELLserviceListener.H +// DATE : Apr 11, 2011 +// PROJECT : SPELL +// DESCRIPTION: The implementing class is interested on executor messages +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_SERVICE_LISTENER_H__ +#define __SPELL_SERVICE_LISTENER_H__ + +// FILES TO INCLUDE ////////////////////////////////////////////////////////// +// Local includes ------------------------------------------------------------ +// Project includes ---------------------------------------------------------- +#include "SPELL_IPC/SPELLipcMessage.H" + +/** \addtogroup SPELL_CTX */ +/*@{*/ + + +// FORWARD REFERENCES //////////////////////////////////////////////////////// +// TYPES ///////////////////////////////////////////////////////////////////// +// DEFINES /////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLserviceListener +{ +public: //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLserviceListener() {}; + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + virtual ~SPELLserviceListener() {}; + + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Process an incoming message. + * + * \param msg IN: the message to be processed. + **//////////////////////////////////////////////////////////////////// + virtual void processMessageFromService( SPELLipcMessage msg ) = 0; + + ////////////////////////////////////////////////////////////////////// + /** Process an incoming request. + * + * \param msg IN: request to be processed. + * + * \return The request response. + **//////////////////////////////////////////////////////////////////// + virtual SPELLipcMessage processRequestFromService( SPELLipcMessage msg ) = 0; + + // DATA MEMBERS ////////////////////////////////////////////////////////// + +protected: //----------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // STATIC //////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + +private: //------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // STATIC //////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + // DATA MEMBERS ////////////////////////////////////////////////////////// + +}; + +/*@}*/ +#endif diff --git a/include/SPELL_CTX/SPELLserviceManager.H b/include/SPELL_CTX/SPELLserviceManager.H new file mode 100644 index 0000000..a472b90 --- /dev/null +++ b/include/SPELL_CTX/SPELLserviceManager.H @@ -0,0 +1,250 @@ +// ################################################################################ +// FILE : SPELLexecutorManager.H +// DATE : Apr 11, 2011 +// PROJECT : SPELL +// DESCRIPTION: Manages the executor operations interacts with clients +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_SERVICE_MANAGER_H__ +#define __SPELL_SERVICE_MANAGER_H__ + +// FILES TO INCLUDE ////////////////////////////////////////////////////////// +// Local includes ------------------------------------------------------------ +#include "SPELL_CTX/SPELLservice.H" +#include "SPELL_CTX/SPELLclient.H" +#include "SPELL_CTX/SPELLserviceStartupParams.H" + +#include "SPELL_CTX/SPELLpersistencyTable.H" +// Project includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLcommand.H" +#include "SPELL_SYN/SPELLmutex.H" +#include "SPELL_IPC/SPELLipcMessage.H" + +/** \addtogroup SPELL_CTX */ +/*@{*/ + + +// FORWARD REFERENCES //////////////////////////////////////////////////////// +// TYPES ///////////////////////////////////////////////////////////////////// +// DEFINES /////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief + ** + ** \par Description: + ** + ** \par Usage: + ** + ** \par Synchronization: + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLserviceManager +{ +public: //-------------------------------------------------------------------- + + // ENUMS ///////////////////////////////////////////////////////////////// + + typedef enum ErrorCode_ + { + UNKNOWN_PROC_ID, + SERVICE_FAILED_STARTUP, + SERVICE_CRASHED, + UNKNOWN + } + ErrorCode; + + // EXCEPTIONS //////////////////////////////////////////////////////////// + + typedef struct OperationError_ + { + std::string message; + ErrorCode errorCode; + } + OperationError; + + // TYPES ///////////////////////////////////////////////////////////////// + + typedef std::list ServiceList; + + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLserviceManager(); + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + ~SPELLserviceManager(); + + // STATIC //////////////////////////////////////////////////////////////// + + static SPELLserviceManager& instance(); + + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Start a service + **//////////////////////////////////////////////////////////////////// + void startService( SPELLserviceStartupParams& config ); + + ////////////////////////////////////////////////////////////////////// + /** Stop an executor + **//////////////////////////////////////////////////////////////////// + void closeService(std::string& serviceId ); + + ////////////////////////////////////////////////////////////////////// + /** Kill an executor + **//////////////////////////////////////////////////////////////////// + void killService( const std::string& serviceId ); + + ////////////////////////////////////////////////////////////////////// + /** Kill all executors + **//////////////////////////////////////////////////////////////////// + void killAll(); + + ////////////////////////////////////////////////////////////////////// + /** Check for active executors + **//////////////////////////////////////////////////////////////////// + unsigned int getNumActiveServices(); + + ////////////////////////////////////////////////////////////////////// + /** Obtain an available instance identifier + **//////////////////////////////////////////////////////////////////// + std::string getInstanceId( const std::string& serviceId ); + + ////////////////////////////////////////////////////////////////////// + /** Initialize the manager + **//////////////////////////////////////////////////////////////////// + void setup( const std::string& contextName ); + + ////////////////////////////////////////////////////////////////////// + /** Cleanup the manager + **//////////////////////////////////////////////////////////////////// + void cleanup(); + + ////////////////////////////////////////////////////////////////////// + /** Obtain a given executor + **//////////////////////////////////////////////////////////////////// + SPELLservice* getService( const std::string& serviceId ); + + ////////////////////////////////////////////////////////////////////// + /** Obtain list of existing executors + **//////////////////////////////////////////////////////////////////// + ServiceList getServiceList(); + + ////////////////////////////////////////////////////////////////////// + /** Report an executor lost + **//////////////////////////////////////////////////////////////////// + void serviceLost( SPELLservice& service ); + + ////////////////////////////////////////////////////////////////////// + /** Mark executor to be cleared + **//////////////////////////////////////////////////////////////////// + void clearService( const std::string& instanceId ); + + ////////////////////////////////////////////////////////////////////// + /** Closes a service + **//////////////////////////////////////////////////////////////////// + void closeService( const std::string& instanceId ); + + ////////////////////////////////////////////////////////////////////// + /** Complete the message with executor information + **//////////////////////////////////////////////////////////////////// + void buildServiceInfo( const std::string& serviceId, SPELLipcMessage& msg ); + + ////////////////////////////////////////////////////////////////////// + /** Delete files corresponding to the given executor + **//////////////////////////////////////////////////////////////////// + void deleteServiceFiles( const SPELLserviceStartupParams& config ); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + +protected: //----------------------------------------------------------------- + + friend class ServiceLoginMonitor; + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + void callback_serviceReconnected( const std::string& instanceId ); + void callback_serviceNotReconnected( const std::string& instanceId ); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + +private: //------------------------------------------------------------------- + friend class SPELLexecutorManager; + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + + typedef std::map ServiceMap; + typedef std::list InstanceList; + typedef std::map InstanceMap; + typedef std::list IdentifierList; + + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Prepare to executor reconnection in case of starting after crash + **//////////////////////////////////////////////////////////////////// + void reconnectServices(); + + void addServiceModel( SPELLservice* exec ); + + void removeServiceModel( const std::string& instanceId ); + + bool hasInstanceNumber( const std::string& serviceId, int instanceNum ); + + void addInstanceNumber( const std::string& serviceId, int instanceNum ); + + void clearModels(); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + + /** Synchronization lock */ + SPELLmutex m_lock; + /** Holds the singleton instance */ + static SPELLserviceManager* s_instance; + /** Holds the map of existing executors */ + ServiceMap m_services; + /** Holds the map of existing instance numbers */ + InstanceMap m_instances; + /** Holds the context name */ + std::string m_contextName; + /** Holds the list of executors to be cleared next time */ + IdentifierList m_toClear; + /** Holds the persistency table for executors */ + SPELLpersistencyTable* m_persisTable; + /** Orphan executors pending login */ + ServiceMap m_pendingLogin; + +}; + +/*@}*/ +#endif diff --git a/include/SPELL_CTX/SPELLserviceStartupParams.H b/include/SPELL_CTX/SPELLserviceStartupParams.H new file mode 100644 index 0000000..e999a08 --- /dev/null +++ b/include/SPELL_CTX/SPELLserviceStartupParams.H @@ -0,0 +1,143 @@ +// ################################################################################ +// FILE : SPELLexecutorStartupParams.H +// DATE : Feb 13, 2014 +// PROJECT : SPELL +// DESCRIPTION: Configuration for the executor process +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_SERVICE_STARTUP_PARAMS_H__ +#define __SPELL_SERVICE_STARTUP_PARAMS_H__ + +// FILES TO INCLUDE ////////////////////////////////////////////////////////// +// Local includes ------------------------------------------------------------ +#include "SPELL_CTX/SPELLclientMode.H" +#include "SPELL_CTX/SPELLopenMode.H" +#include "SPELL_CTX/SPELLexecutorStartupParams.H" +// Project includes ---------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" +#include "SPELL_UTIL/SPELLutils.H" + +/** \addtogroup SPELL_CTX */ +/*@{*/ + + +// FORWARD REFERENCES //////////////////////////////////////////////////////// +// TYPES ///////////////////////////////////////////////////////////////////// +// DEFINES /////////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLserviceStartupParams : public SPELLexecutorStartupParams +{ +public: //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLserviceStartupParams( const std::string& instanceId, const std::string& timeId, bool reconnecting = false ) : + SPELLexecutorStartupParams(instanceId,timeId,reconnecting) + { + int idx = instanceId.find("#"); + m_procId = instanceId.substr(0, idx); + m_timeId = timeId; + m_instanceNum = STRI(instanceId.substr(idx+1,instanceId.size()-idx)); + m_instanceId = instanceId; + m_arguments = ""; + m_condition = ""; + m_clientMode = CLIENT_MODE_UNKNOWN; + m_openMode = OPEN_MODE_UNKNOWN; + m_parentInstanceId = ""; + m_groupId = ""; + m_originId = ""; + m_parentCallingLine = 0; + m_configFile = ""; + m_contextName = ""; + m_recoveryFile = ""; + m_ipcPort = 0; + m_PID = 0; + m_reconnecting = reconnecting; + } + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + ~SPELLserviceStartupParams() {}; + + // METHODS /////////////////////////////////////////////////////////////// + + std::string getProcId() const { return m_procId; }; + std::string getTimeId() const { return m_timeId; }; + std::string getInstanceId() const { return m_instanceId; }; + std::string getOriginId() const { return m_originId; }; + std::string getGroupId() const { return m_groupId; }; + unsigned int getInstanceNum() const { return m_instanceNum; }; + + pid_t getPID() const { return m_PID; }; + int getIpcPort() const { return m_ipcPort; }; + bool isReconnecting() const { return m_reconnecting; } + + std::string getArguments() const { return m_arguments; }; + std::string getCondition() const { return m_condition; }; + SPELLclientMode getClientMode() const { return m_clientMode; }; + SPELLopenMode getOpenMode() const { return m_openMode; }; + std::string getParentInstanceId() const { return m_parentInstanceId; }; + int getParentCallingLine() const { return m_parentCallingLine; }; + std::string getConfigFile() const { return m_configFile; }; + std::string getContextName() const { return m_contextName; }; + std::string getRecoveryFile() const { return m_recoveryFile; }; + + void setPID( pid_t pid ) { m_PID = pid; }; + void setIpcPort( int ipcPort ) { m_ipcPort = ipcPort; }; + void setArguments( const std::string& arguments ) { m_arguments = arguments; }; + void setCondition( const std::string& condition ) { m_condition = condition; }; + void setClientMode( const SPELLclientMode& mode ) { m_clientMode = mode; }; + void setOpenMode( const SPELLopenMode& mode ) { m_openMode = mode; }; + void setParentInstanceId( const std::string& parentInstanceId ) { m_parentInstanceId = parentInstanceId; }; + void setGroupId( const std::string& groupId ) { m_groupId = groupId; }; + void setOriginId( const std::string& originId ) { m_originId = originId; }; + void setParentCallingLine( int lineNo ) { m_parentCallingLine = lineNo; }; + void setConfigFile( const std::string& configFile ) { m_configFile = configFile; }; + void setContextName( const std::string& ctxName ) { m_contextName = ctxName; }; + void setRecoveryFile( const std::string& recoveryFile ) { m_recoveryFile = recoveryFile; }; + +private: //------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // STATIC //////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + // DATA MEMBERS ////////////////////////////////////////////////////////// + +}; + +/*@}*/ +#endif diff --git a/include/SPELL_CTX/SPELLsharedData.H b/include/SPELL_CTX/SPELLsharedData.H index 31ba17d..8011f5f 100644 --- a/include/SPELL_CTX/SPELLsharedData.H +++ b/include/SPELL_CTX/SPELLsharedData.H @@ -5,7 +5,7 @@ // DESCRIPTION: Shared data blackboard // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -161,7 +161,7 @@ public: //-------------------------------------------------------------------- protected: //----------------------------------------------------------------- - friend class SPELLclientInterestList; + //friend class SPELLclientInterestList; // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// diff --git a/include/SPELL_CTX/SPELLsharedDataOperation.H b/include/SPELL_CTX/SPELLsharedDataOperation.H index dd181cc..afe09eb 100644 --- a/include/SPELL_CTX/SPELLsharedDataOperation.H +++ b/include/SPELL_CTX/SPELLsharedDataOperation.H @@ -5,7 +5,7 @@ // DESCRIPTION: Definition of the shared data operations // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_DTA/SPELLdtaContainer.H b/include/SPELL_DTA/SPELLdtaContainer.H index 09f8f80..7c05ac8 100644 --- a/include/SPELL_DTA/SPELLdtaContainer.H +++ b/include/SPELL_DTA/SPELLdtaContainer.H @@ -5,7 +5,7 @@ // DESCRIPTION: Data variable container implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_DTA/SPELLdtaContainerObject.H b/include/SPELL_DTA/SPELLdtaContainerObject.H index ae7c792..6c65183 100644 --- a/include/SPELL_DTA/SPELLdtaContainerObject.H +++ b/include/SPELL_DTA/SPELLdtaContainerObject.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL data dictionary // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_DTA/SPELLdtaVariable.H b/include/SPELL_DTA/SPELLdtaVariable.H index 2b3047c..500ef67 100644 --- a/include/SPELL_DTA/SPELLdtaVariable.H +++ b/include/SPELL_DTA/SPELLdtaVariable.H @@ -5,7 +5,7 @@ // DESCRIPTION: Data variable implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_DTA/SPELLdtaVariableObject.H b/include/SPELL_DTA/SPELLdtaVariableObject.H index 0040be3..dd02d3e 100644 --- a/include/SPELL_DTA/SPELLdtaVariableObject.H +++ b/include/SPELL_DTA/SPELLdtaVariableObject.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL variable // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLastAnalyzer.H b/include/SPELL_EXC/SPELLastAnalyzer.H index 54aa0a8..dd2bacb 100644 --- a/include/SPELL_EXC/SPELLastAnalyzer.H +++ b/include/SPELL_EXC/SPELLastAnalyzer.H @@ -6,7 +6,7 @@ // procedure execution. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLbreakpoint.H b/include/SPELL_EXC/SPELLbreakpoint.H index 13e1a2f..058d71f 100644 --- a/include/SPELL_EXC/SPELLbreakpoint.H +++ b/include/SPELL_EXC/SPELLbreakpoint.H @@ -5,7 +5,7 @@ // DESCRIPTION: Management of breakpoints in the current procedure // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLbreakpointType.H b/include/SPELL_EXC/SPELLbreakpointType.H index d3dd559..8820a66 100644 --- a/include/SPELL_EXC/SPELLbreakpointType.H +++ b/include/SPELL_EXC/SPELLbreakpointType.H @@ -5,7 +5,7 @@ // DESCRIPTION: Types of breakpoints // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLbytecode.H b/include/SPELL_EXC/SPELLbytecode.H index 67eab6a..2211a32 100644 --- a/include/SPELL_EXC/SPELLbytecode.H +++ b/include/SPELL_EXC/SPELLbytecode.H @@ -6,7 +6,7 @@ // that affect the executor features at runtime. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -28,6 +28,11 @@ #ifndef __SPELL_BCODE_H__ #define __SPELL_BCODE_H__ +#ifndef JUMP_IF_TRUE +#define JUMP_IF_TRUE JUMP_IF_TRUE_OR_POP +#define JUMP_IF_FALSE JUMP_IF_FALSE_OR_POP +#endif + // FILES TO INCLUDE //////////////////////////////////////////////////////// // System includes --------------------------------------------------------- // Local includes ---------------------------------------------------------- diff --git a/include/SPELL_EXC/SPELLcallstack.H b/include/SPELL_EXC/SPELLcallstack.H index 9b62aa9..1af56e6 100644 --- a/include/SPELL_EXC/SPELLcallstack.H +++ b/include/SPELL_EXC/SPELLcallstack.H @@ -5,7 +5,7 @@ // DESCRIPTION: Call stack model. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -90,7 +90,7 @@ public: //-------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** \see SPELLcallstackIF. **//////////////////////////////////////////////////////////////////// - const std::string& getStack(); + const std::string getStack(); ////////////////////////////////////////////////////////////////////// /** \see SPELLcallstackIF. @@ -105,7 +105,7 @@ public: //-------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** \see SPELLcallstackIF. **//////////////////////////////////////////////////////////////////// - const std::string& getCodeName(); + const std::string getCodeName(); ////////////////////////////////////////////////////////////////////// /** \see SPELLcallstackIF. diff --git a/include/SPELL_EXC/SPELLcallstackIF.H b/include/SPELL_EXC/SPELLcallstackIF.H index 79c022e..cebb586 100644 --- a/include/SPELL_EXC/SPELLcallstackIF.H +++ b/include/SPELL_EXC/SPELLcallstackIF.H @@ -5,7 +5,7 @@ // DESCRIPTION: Call stack interface. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -85,7 +85,7 @@ public: //-------------------------------------------------------------------- * * \returns The current callstack string. **//////////////////////////////////////////////////////////////////// - virtual const std::string& getStack() = 0; + virtual const std::string getStack() = 0; ////////////////////////////////////////////////////////////////////// /** Obtain the current callstack string. @@ -106,7 +106,7 @@ public: //-------------------------------------------------------------------- * * \returns The current code name string. **//////////////////////////////////////////////////////////////////// - virtual const std::string& getCodeName() = 0; + virtual const std::string getCodeName() = 0; ////////////////////////////////////////////////////////////////////// /** Set the step over mode. diff --git a/include/SPELL_EXC/SPELLchildManager.H b/include/SPELL_EXC/SPELLchildManager.H index 7f14e24..f1ca33d 100644 --- a/include/SPELL_EXC/SPELLchildManager.H +++ b/include/SPELL_EXC/SPELLchildManager.H @@ -5,7 +5,7 @@ // DESCRIPTION: Child procedure controller. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLcodeTreeLine.H b/include/SPELL_EXC/SPELLcodeTreeLine.H index caa4716..c060247 100644 --- a/include/SPELL_EXC/SPELLcodeTreeLine.H +++ b/include/SPELL_EXC/SPELLcodeTreeLine.H @@ -5,7 +5,7 @@ // DESCRIPTION: Code tree line model implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLcodeTreeLineIF.H b/include/SPELL_EXC/SPELLcodeTreeLineIF.H index f8c6adf..9f4765d 100644 --- a/include/SPELL_EXC/SPELLcodeTreeLineIF.H +++ b/include/SPELL_EXC/SPELLcodeTreeLineIF.H @@ -5,7 +5,7 @@ // DESCRIPTION: Code tree line interface definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLcodeTreeNode.H b/include/SPELL_EXC/SPELLcodeTreeNode.H index 44b022c..69c3b97 100644 --- a/include/SPELL_EXC/SPELLcodeTreeNode.H +++ b/include/SPELL_EXC/SPELLcodeTreeNode.H @@ -5,7 +5,7 @@ // DESCRIPTION: Code tree node implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLcodeTreeNodeIF.H b/include/SPELL_EXC/SPELLcodeTreeNodeIF.H index 112ef22..5f4422b 100644 --- a/include/SPELL_EXC/SPELLcodeTreeNodeIF.H +++ b/include/SPELL_EXC/SPELLcodeTreeNodeIF.H @@ -5,7 +5,7 @@ // DESCRIPTION: Code tree node interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLcommand.H b/include/SPELL_EXC/SPELLcommand.H index 13c9feb..e64423d 100644 --- a/include/SPELL_EXC/SPELLcommand.H +++ b/include/SPELL_EXC/SPELLcommand.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor command definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -86,6 +86,8 @@ ExecutorCommand; #define CMD_ACTION "CMD_ACTION" /** Identifier for the FINISH command */ #define CMD_FINISH "CMD_FINISH" +/** Identifier for the RESOURCE DISPLAY command */ +#define CMD_RESOURCES "CMD_RESOURCES" /*@}*/ #endif diff --git a/include/SPELL_EXC/SPELLcommandMailbox.H b/include/SPELL_EXC/SPELLcommandMailbox.H index d45a9d5..c3c0c9c 100644 --- a/include/SPELL_EXC/SPELLcommandMailbox.H +++ b/include/SPELL_EXC/SPELLcommandMailbox.H @@ -6,7 +6,7 @@ // processing. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLcontroller.H b/include/SPELL_EXC/SPELLcontroller.H index 9d87f78..374057b 100644 --- a/include/SPELL_EXC/SPELLcontroller.H +++ b/include/SPELL_EXC/SPELLcontroller.H @@ -5,7 +5,7 @@ // DESCRIPTION: Controls the command processing in a separate thread. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -268,6 +268,12 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void removeControllerListener( SPELLcontrollerListener* listener ); + void refreshStatus(); + + ////////////////////////////////////////////////////////////////////// + /** Inject a information event message to GCS + **//////////////////////////////////////////////////////////////////// + void raiseInformationEventToGCS( const std::string& message ); // DATA MEMBERS ////////////////////////////////////////////////////////// protected: //----------------------------------------------------------------- diff --git a/include/SPELL_EXC/SPELLcontrollerIF.H b/include/SPELL_EXC/SPELLcontrollerIF.H index b863230..885c4a0 100644 --- a/include/SPELL_EXC/SPELLcontrollerIF.H +++ b/include/SPELL_EXC/SPELLcontrollerIF.H @@ -5,7 +5,7 @@ // DESCRIPTION: Defines the controller interface. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -253,6 +253,13 @@ public: //-------------------------------------------------------------------- /** Remove a controller listener **//////////////////////////////////////////////////////////////////// virtual void removeControllerListener( SPELLcontrollerListener* listener ) {;}; + + virtual void refreshStatus() {;}; + + ////////////////////////////////////////////////////////////////////// + /** Inject a information event message to GCS + **//////////////////////////////////////////////////////////////////// + virtual void raiseInformationEventToGCS( const std::string& message ) = 0; }; /*@}*/ diff --git a/include/SPELL_EXC/SPELLcontrollerListener.H b/include/SPELL_EXC/SPELLcontrollerListener.H index 1fec2cb..d56caf5 100644 --- a/include/SPELL_EXC/SPELLcontrollerListener.H +++ b/include/SPELL_EXC/SPELLcontrollerListener.H @@ -5,7 +5,7 @@ // DESCRIPTION: Controller listener interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLdispatchListener.H b/include/SPELL_EXC/SPELLdispatchListener.H index 7c4a1cd..534a7bc 100644 --- a/include/SPELL_EXC/SPELLdispatchListener.H +++ b/include/SPELL_EXC/SPELLdispatchListener.H @@ -5,7 +5,7 @@ // DESCRIPTION: Dispatch listener interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLexecutionMode.H b/include/SPELL_EXC/SPELLexecutionMode.H index 7302303..7ec92f4 100644 --- a/include/SPELL_EXC/SPELLexecutionMode.H +++ b/include/SPELL_EXC/SPELLexecutionMode.H @@ -5,7 +5,7 @@ // DESCRIPTION: Defines the diferent execution modes. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLexecutionModel.H b/include/SPELL_EXC/SPELLexecutionModel.H index af74db4..33f6708 100644 --- a/include/SPELL_EXC/SPELLexecutionModel.H +++ b/include/SPELL_EXC/SPELLexecutionModel.H @@ -5,7 +5,7 @@ // DESCRIPTION: Composite of all models and analyzers associated to a given frame. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLexecutionTrace.H b/include/SPELL_EXC/SPELLexecutionTrace.H index c1cdb21..1036253 100644 --- a/include/SPELL_EXC/SPELLexecutionTrace.H +++ b/include/SPELL_EXC/SPELLexecutionTrace.H @@ -5,7 +5,7 @@ // DESCRIPTION: Keeps trace of the visited and executed procedure lines. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLexecutor.H b/include/SPELL_EXC/SPELLexecutor.H index e7ccc45..225a30f 100644 --- a/include/SPELL_EXC/SPELLexecutor.H +++ b/include/SPELL_EXC/SPELLexecutor.H @@ -5,7 +5,7 @@ // DESCRIPTION: Singleton factory for the executor // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -108,12 +108,17 @@ public: //-------------------------------------------------------------------- * * \return The executor implementation instance. **//////////////////////////////////////////////////////////////////// - static SPELLexecutorIF& instance() + static inline SPELLexecutorIF& instance() { assert(s_instance != NULL); return *s_instance; } + static inline bool isInitialized() + { + return s_instance != NULL; + } + ////////////////////////////////////////////////////////////////////// /** Set the executor instance * diff --git a/include/SPELL_EXC/SPELLexecutorConfiguration.H b/include/SPELL_EXC/SPELLexecutorConfiguration.H index f7b4634..e0ca174 100644 --- a/include/SPELL_EXC/SPELLexecutorConfiguration.H +++ b/include/SPELL_EXC/SPELLexecutorConfiguration.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor configuration parameters // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -218,7 +218,14 @@ public: //-------------------------------------------------------------------- bool isForceTcConfirm() const { return m_forceTcConfirm; }; - + ////////////////////////////////////////////////////////////////////// + /** Obtain the force Prompt confirmation flag + * + * \returns The flag value + **//////////////////////////////////////////////////////////////////// + bool isForcePromptConfirm() const { + return m_forcePromptConfirm; + }; ////////////////////////////////////////////////////////////////////// /** Obtain the executor condition. * @@ -256,6 +263,43 @@ public: //-------------------------------------------------------------------- }; + ////////////////////////////////////////////////////////////////////// + /** set resources display is enablement. + * + * \returns The status. + **//////////////////////////////////////////////////////////////////// + void setResourcesDisplayEnabled( bool enabled ) { + m_resourcesDisplay = enabled; + }; + + ////////////////////////////////////////////////////////////////////// + /** Chekc if resource display is enabled or not. + * + * \returns The status. + **//////////////////////////////////////////////////////////////////// + const bool isResourcesDisplayEnabled() const { + return m_resourcesDisplay; + }; + + ////////////////////////////////////////////////////////////////////// + /** Obtain the local resource support flag flag. + * + * \returns The flag value + **//////////////////////////////////////////////////////////////////// + const bool isLocalResourcesSupported() const { + return m_localResourceSupport; + }; + + ////////////////////////////////////////////////////////////////////// + /** Set the local resource support flag. + * + * \param enabled IN: the flag value. + **//////////////////////////////////////////////////////////////////// + void setLocalResourcesSupported( const bool enabled ) { + m_localResourceSupport = enabled; + }; + + ////////////////////////////////////////////////////////////////////// /** Set the run-into flag. * @@ -374,6 +418,9 @@ public: //-------------------------------------------------------------------- void setForceTcConfirm( bool force ) { m_forceTcConfirm = force; }; + void setForcePromptConfirm( bool force ) { + m_forcePromptConfirm = force; + }; ////////////////////////////////////////////////////////////////////// /** Set the executor condition. @@ -544,8 +591,11 @@ private: //------------------------------------------------------------------- SPELLwsWorkingMode m_saveStateMode; /** True if watch variables is enabled */ bool m_watchVariables; + /** True if resources display is enabled */ + bool m_resourcesDisplay; /** True if all TCs should be confirmed */ bool m_forceTcConfirm; + bool m_forcePromptConfirm; /** Identifier of the controlling client if any */ std::string m_controlClient; /** Host of the controlling GUI */ @@ -556,6 +606,8 @@ private: //------------------------------------------------------------------- int m_maxVerbosity; /** Context name */ std::string m_contextName; + /** Support for local resources flag */ + bool m_localResourceSupport; }; /*@}*/ diff --git a/include/SPELL_EXC/SPELLexecutorIF.H b/include/SPELL_EXC/SPELLexecutorIF.H index c2bcc5c..9bb04ce 100644 --- a/include/SPELL_EXC/SPELLexecutorIF.H +++ b/include/SPELL_EXC/SPELLexecutorIF.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor interface definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -38,6 +38,7 @@ #include "SPELL_EXC/SPELLcallstackIF.H" #include "SPELL_EXC/SPELLbreakpointType.H" #include "SPELL_EXC/SPELLvariableManager.H" +#include "SPELL_EXC/SPELLresourceManager.H" #include "SPELL_EXC/SPELLdispatchListener.H" @@ -136,6 +137,7 @@ public: //-------------------------------------------------------------------- * \returns The controller reference **//////////////////////////////////////////////////////////////////// virtual SPELLcontrollerIF& getController() const = 0; + virtual bool hasController() const { return true;}; ////////////////////////////////////////////////////////////////////// /** Obtain the scheduler reference @@ -156,7 +158,7 @@ public: //-------------------------------------------------------------------- * * \returns The configuration object. **//////////////////////////////////////////////////////////////////// - virtual SPELLexecutorConfig& getConfiguration() const = 0; + virtual SPELLexecutorConfig& getConfiguration() = 0; ////////////////////////////////////////////////////////////////////// /** Obtain variable manager. @@ -165,6 +167,13 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// virtual SPELLvariableManager& getVariableManager() const = 0; + ////////////////////////////////////////////////////////////////////// + /** Obtain resource manager. + * + * \returns The resource manager object. + **//////////////////////////////////////////////////////////////////// + virtual SPELLresourceManager& getResourceManager() const = 0; + ////////////////////////////////////////////////////////////////////// /** Obtain execution frame. * @@ -374,7 +383,7 @@ public: //-------------------------------------------------------------------- * \param force IN: the flag value **//////////////////////////////////////////////////////////////////// virtual void setForceTcConfirm( const bool force ) = 0; - + virtual void setForcePromptConfirm( const bool force ) = 0; // ******************************************************************* // LANGUAGE INTERLOCK // ******************************************************************* @@ -515,6 +524,7 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// virtual void displayStage( const std::string& id, const std::string& title ) = 0; + virtual void refreshStatus() {;}; }; /*@}*/ diff --git a/include/SPELL_EXC/SPELLexecutorImpl.H b/include/SPELL_EXC/SPELLexecutorImpl.H index 0e62777..2a34ee4 100644 --- a/include/SPELL_EXC/SPELLexecutorImpl.H +++ b/include/SPELL_EXC/SPELLexecutorImpl.H @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the Executor. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -113,6 +113,7 @@ public: //-------------------------------------------------------------------- /** \see SPELLexecutorIF. **//////////////////////////////////////////////////////////////////// SPELLcontrollerIF& getController() const { return *m_controller; }; + bool hasController() const { return m_controller != NULL; }; ////////////////////////////////////////////////////////////////////// /** \see SPELLexecutorIF. @@ -127,13 +128,18 @@ public: //-------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** \see SPELLexecutorIF. **//////////////////////////////////////////////////////////////////// - SPELLexecutorConfig& getConfiguration() const; + SPELLexecutorConfig& getConfiguration(); ////////////////////////////////////////////////////////////////////// /** \see SPELLexecutorIF. **//////////////////////////////////////////////////////////////////// SPELLvariableManager& getVariableManager() const { return *m_varManager; }; + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLresourceManager& getResourceManager() const { return *m_resourceManager; }; + ////////////////////////////////////////////////////////////////////// /** \see SPELLexecutorIF. **//////////////////////////////////////////////////////////////////// @@ -286,7 +292,7 @@ public: //-------------------------------------------------------------------- /** \see SPELLexecutorIF. **//////////////////////////////////////////////////////////////////// void setForceTcConfirm( const bool force ); - + void setForcePromptConfirm( const bool force ); // ******************************************************************* // LANGUAGE INTERLOCK // ******************************************************************* @@ -377,6 +383,7 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void displayStage( const std::string& id, const std::string& title ); + void refreshStatus(); // DATA MEMBERS ////////////////////////////////////////////////////////// protected: //----------------------------------------------------------------- @@ -524,9 +531,12 @@ private: //------------------------------------------------------------------- /** Change the force TC flag on the adapter layer **//////////////////////////////////////////////////////////////////// void setForceTcConfirmInternal( bool force ); + void setForcePromptConfirmInternal( bool force ); // DATA MEMBERS ////////////////////////////////////////////////////////// + /** Empty configuration */ + SPELLexecutorConfig m_emptyConfig; /** True if the initial configuration parameters have been set */ bool m_initialized; /** Holds the procedure instance identifier */ @@ -570,6 +580,8 @@ private: //------------------------------------------------------------------- SPELLimportChecker m_importChecker; /** Holds the variable manager */ SPELLvariableManager* m_varManager; + /** Holds the resource manager */ + SPELLresourceManager* m_resourceManager; /** Command synchronization lock */ SPELLmutex m_cmdLock; diff --git a/include/SPELL_EXC/SPELLexecutorStatus.H b/include/SPELL_EXC/SPELLexecutorStatus.H index 711ae6b..14f161e 100644 --- a/include/SPELL_EXC/SPELLexecutorStatus.H +++ b/include/SPELL_EXC/SPELLexecutorStatus.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor status definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLexecutorUtils.H b/include/SPELL_EXC/SPELLexecutorUtils.H index a78ecc2..bf63ccc 100644 --- a/include/SPELL_EXC/SPELLexecutorUtils.H +++ b/include/SPELL_EXC/SPELLexecutorUtils.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor utilities // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLframeManager.H b/include/SPELL_EXC/SPELLframeManager.H index f186e69..6567268 100644 --- a/include/SPELL_EXC/SPELLframeManager.H +++ b/include/SPELL_EXC/SPELLframeManager.H @@ -5,7 +5,7 @@ // DESCRIPTION: Operations in relation with the current Python frame (runtime) // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -30,6 +30,7 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // Local includes ---------------------------------------------------------- #include "SPELL_EXC/SPELLexecutionModel.H" +#include "SPELL_EXC/SPELLresourceMonitor.H" #include "SPELL_EXC/SPELLexecutionTrace.H" #include "SPELL_EXC/SPELLcallstackIF.H" #include "SPELL_EXC/SPELLbreakpoint.H" @@ -94,7 +95,7 @@ public: //-------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** Destructor. **//////////////////////////////////////////////////////////////////// - ~SPELLframeManager(); + virtual ~SPELLframeManager(); // METHODS /////////////////////////////////////////////////////////////// @@ -130,6 +131,13 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// SPELLexecutionModel& getModel( const std::string& code_id ); + ////////////////////////////////////////////////////////////////////// + /** Get the resources monitor. + * + * \return Reference to the resources monitor. + **//////////////////////////////////////////////////////////////////// + SPELLresourceMonitor& getResourceMonitor() { return m_resourceMonitor; }; + ////////////////////////////////////////////////////////////////////// /** Replay the frame structure in the given stack **//////////////////////////////////////////////////////////////////// @@ -332,7 +340,7 @@ protected: //------------------------------------------------------------------- // METHODS /////////////////////////////////////////////////////////////// private: //------------------------------------------------------------------- - + friend class SPELLserviceFrameManager; // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// // TYPES ///////////////////////////////////////////////////////////////// @@ -356,7 +364,7 @@ private: //------------------------------------------------------------------- * \throws SPELLcoreException if the file cannot be found, or the * compilation fails. **//////////////////////////////////////////////////////////////////// - void compile(); + virtual void compile(); ////////////////////////////////////////////////////////////////////// /** Compile the given script and obtain the bytecode. @@ -368,7 +376,7 @@ private: //------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** Create initial frame out of the current code **//////////////////////////////////////////////////////////////////// - void createInitialFrame(); + virtual void createInitialFrame(); ////////////////////////////////////////////////////////////////////// /** Create frame model for a new frame @@ -401,7 +409,7 @@ private: //------------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** Update data for watch of variables when a new frame arrives **//////////////////////////////////////////////////////////////////// - void updateVariables( PyFrameObject* frame ); + virtual void updateVariables( PyFrameObject* frame ); ////////////////////////////////////////////////////////////////////// /** Update SPELL builtins using the given frames @@ -435,6 +443,8 @@ private: //------------------------------------------------------------------- PyObject* m_definitions; /** Holds the currently active execution model */ SPELLexecutionModel* m_model; + /** Monitor of resources */ + SPELLresourceMonitor m_resourceMonitor; /** Holds the AST analyzer */ SPELLastAnalyzer m_ast; /** Map of model objects per code object */ diff --git a/include/SPELL_EXC/SPELLgoto.H b/include/SPELL_EXC/SPELLgoto.H index af804a7..b826750 100644 --- a/include/SPELL_EXC/SPELLgoto.H +++ b/include/SPELL_EXC/SPELLgoto.H @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the Goto mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLimportChecker.H b/include/SPELL_EXC/SPELLimportChecker.H index 143f084..782d01e 100644 --- a/include/SPELL_EXC/SPELLimportChecker.H +++ b/include/SPELL_EXC/SPELLimportChecker.H @@ -5,7 +5,7 @@ // DESCRIPTION: Special check for import statements // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLinterpreter.H b/include/SPELL_EXC/SPELLinterpreter.H index 15e13fe..0f796a8 100644 --- a/include/SPELL_EXC/SPELLinterpreter.H +++ b/include/SPELL_EXC/SPELLinterpreter.H @@ -5,7 +5,7 @@ // DESCRIPTION: Main entry point of the executor. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLlnotab.H b/include/SPELL_EXC/SPELLlnotab.H index 3177db1..1709abb 100644 --- a/include/SPELL_EXC/SPELLlnotab.H +++ b/include/SPELL_EXC/SPELLlnotab.H @@ -5,7 +5,7 @@ // DESCRIPTION: LNotab analyzer. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLresourceInfo.H b/include/SPELL_EXC/SPELLresourceInfo.H new file mode 100644 index 0000000..56e0956 --- /dev/null +++ b/include/SPELL_EXC/SPELLresourceInfo.H @@ -0,0 +1,115 @@ +// ################################################################################ +// FILE : SPELLresourceInfo.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Procedure resource information +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_RESOURCE_INFO_H__ +#define __SPELL_RESOURCE_INFO_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +// Project includes -------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" + +/** \addtogroup SPELL_EXC */ +/*@{*/ + + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +// TYPES /////////////////////////////////////////////////////////////////// +// DEFINES ///////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief Resource information + ** +**//////////////////////////////////////////////////////////////////////////// +typedef struct SPELLresourceInfo_ +{ + SPELLresourceInfo_() + { + resourceName = ""; + resourceType = ""; + resourceGlobalValue = ""; + resourceLocalValue = ""; + resourceValueRange.clear(); + resourceGCSName = ""; + } + + SPELLresourceInfo_( const std::string& name, + const std::string& type, + const std::string& globalValue, + const std::string& localValue, + const std::vector >& valueRange, + const std::string& gcsName ) + { + resourceName = name; + resourceType = type; + resourceGlobalValue = globalValue; + resourceLocalValue = localValue; + resourceValueRange.assign(valueRange.begin(), valueRange.end()); + resourceGCSName = gcsName; + } + + SPELLresourceInfo_& operator=( const SPELLresourceInfo_& other ) + { + if (this != &other) + { + resourceName = other.resourceName; + resourceType = other.resourceType; + resourceGlobalValue = other.resourceGlobalValue; + resourceLocalValue = other.resourceLocalValue; + resourceValueRange.assign(other.resourceValueRange.begin(), other.resourceValueRange.end()); + resourceGCSName = other.resourceGCSName; + } + return *this; + } + + SPELLresourceInfo_( const SPELLresourceInfo_& other ) + { + resourceName = other.resourceName; + resourceType = other.resourceType; + resourceGlobalValue = other.resourceGlobalValue; + resourceLocalValue = other.resourceLocalValue; + resourceValueRange.assign(other.resourceValueRange.begin(), other.resourceValueRange.end()); + resourceGCSName = other.resourceGCSName; + } + + /** Holds the resource name */ + std::string resourceName; + /** Holds the resource type string */ + std::string resourceType; + /** Holds the resource global value */ + std::string resourceGlobalValue; + /** Holds the resource local value */ + std::string resourceLocalValue; + /** List of values that the resource can have. Empty means any value */ + std::vector > resourceValueRange; + /** Holds the resource GCS name */ + std::string resourceGCSName; +} +SPELLresourceInfo; + +/*@}*/ +#endif diff --git a/include/SPELL_EXC/SPELLresourceManager.H b/include/SPELL_EXC/SPELLresourceManager.H new file mode 100644 index 0000000..5cf6d36 --- /dev/null +++ b/include/SPELL_EXC/SPELLresourceManager.H @@ -0,0 +1,147 @@ +// ################################################################################ +// FILE : SPELLresourceManager.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Manager of procedure runtime resources +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_RESOURCE_MANAGER_H__ +#define __SPELL_RESOURCE_MANAGER_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLframeManager.H" +// Project includes -------------------------------------------------------- +#include "SPELL_SYN/SPELLqueue.H" + +/** \addtogroup SPELL_EXC */ +/*@{*/ + + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +// TYPES /////////////////////////////////////////////////////////////////// +// DEFINES ///////////////////////////////////////////////////////////////// + +#define RESOURCE_MONITOR_CHANGE_RESOURCE "changeResource" +#define RESOURCE_MONITOR_GET_RESOURCES "getResources" + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief Resource manager + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLresourceManager +{ +public: //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLresourceManager( SPELLframeManager& frameManager ); + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + ~SPELLresourceManager(); + + // STATIC //////////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Enable or disable the manager. + **//////////////////////////////////////////////////////////////////// + void setEnabled( bool enabled ); + + ////////////////////////////////////////////////////////////////////// + /** Check enabled + **//////////////////////////////////////////////////////////////////// + bool isEnabled(); + + ////////////////////////////////////////////////////////////////////// + /** Analysis on demand + **//////////////////////////////////////////////////////////////////// + void analyze(); + + ////////////////////////////////////////////////////////////////////// + /** Get available resources. + * + * \return The list of resources. + **//////////////////////////////////////////////////////////////////// + void getResources(); + + ////////////////////////////////////////////////////////////////////// + /** Change a resource value. + * + * \param resource IN: the resource data. + * + **//////////////////////////////////////////////////////////////////// + void changeResource( SPELLresourceInfo& resource ); + + ////////////////////////////////////////////////////////////////////// + /** Check if the internal executor status is valid to process + * resource manipulation. + * + * \returns True if the status is valid. + **//////////////////////////////////////////////////////////////////// + bool isStatusValid(); + + ////////////////////////////////////////////////////////////////////// + /** Call resource monitor method. + * + * \param moitorTask IN: name of the method to call. + * + **//////////////////////////////////////////////////////////////////// + void callResourceMonitorFromClient(std::string monitorTask); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + +protected: //----------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + // DATA MEMBERS ////////////////////////////////////////////////////////// + +private: //------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + // DATA MEMBERS ////////////////////////////////////////////////////////// + /** Holds reference to the frame manager */ + SPELLframeManager& m_frameManager; + /** Queue to store resources to change */ + SPELLqueue m_changeResourceQueue; +}; + +/*@}*/ +#endif diff --git a/include/SPELL_EXC/SPELLresourceMonitor.H b/include/SPELL_EXC/SPELLresourceMonitor.H new file mode 100644 index 0000000..b924b4d --- /dev/null +++ b/include/SPELL_EXC/SPELLresourceMonitor.H @@ -0,0 +1,199 @@ +// ################################################################################ +// FILE : SPELLresourceMonitor.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Monitor for runtime procedure resources +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_RESOURCE_MONITOR_H__ +#define __SPELL_RESOURCE_MONITOR_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLresourceInfo.H" +// Project includes -------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" + +/** \addtogroup SPELL_EXC */ +/*@{*/ + + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +// TYPES /////////////////////////////////////////////////////////////////// +// DEFINES ///////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief Listener for resource changes. + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLresourceChangeListener +{ +public: + + ////////////////////////////////////////////////////////////////////// + /** Notify about a resource change. + * + * \param changed IN: list of changed resources. + * + **//////////////////////////////////////////////////////////////////// + void resourceChanged( const std::vector& changed ); + + ////////////////////////////////////////////////////////////////////// + /** Notify about a resource list. + * + * \param changed IN: list of resources. + * + **//////////////////////////////////////////////////////////////////// + void resourceList( const std::vector& resources ); +}; + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief Monitor of resources + ** + ** \par Description and usage: + ** + ** Used to implement the display-of-resurces mechanism + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLresourceMonitor: public SPELLresourceChangeListener +{ +public: //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + + /** Type for resource name-value map */ + typedef std::map ResourceMap; + + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLresourceMonitor( SPELLresourceChangeListener* listener ); + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + ~SPELLresourceMonitor(); + + // STATIC //////////////////////////////////////////////////////////////// + + static bool s_enabled; + + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Get available resources. + * + * \return The list of resources. + **//////////////////////////////////////////////////////////////////// + void getResources(); + + ////////////////////////////////////////////////////////////////////// + /** Change resource + * + * \param resource IN: the resource to change. + * + **//////////////////////////////////////////////////////////////////// + void changeResource( SPELLresourceInfo& resource ); + + ////////////////////////////////////////////////////////////////////// + /** Get resource value. + * + * \param resourceName IN: resource name + * + * \return The resource information. + **//////////////////////////////////////////////////////////////////// + void getResource( SPELLresourceInfo& resource ); + + ////////////////////////////////////////////////////////////////////// + /** Analyze resource dictionary to detect changes. + **//////////////////////////////////////////////////////////////////// + void analyze(); + + ////////////////////////////////////////////////////////////////////// + /** Initialize the resource dictionary + **//////////////////////////////////////////////////////////////////// + void initialize(); + +protected: //----------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + // DATA MEMBERS ////////////////////////////////////////////////////////// + +private: //------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Retrieve existing resources, local or global + * \param resultingMap OUT: map of resource information to fill. + * \param isLocal IN: whether to retrieve local resources (true) or global (false). + * \param onlyLocal IN: whether to ONLY retrieve the values of local resources + * Neither the type, global value or ValueRange are retrieved + * + **//////////////////////////////////////////////////////////////////// + void retrieveResources( ResourceMap &resultingMap, bool onlyLocal=false ); + + ////////////////////////////////////////////////////////////////////// + /** Retrieve resource value range and name map + **//////////////////////////////////////////////////////////////////// + void retrieveResourceValueRangeAndNameMap( ); + + ////////////////////////////////////////////////////////////////////// + /** Check if a resource should not be monitored + **//////////////////////////////////////////////////////////////////// + bool shouldDiscard( const std::string& name ); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + + /** Reference to the frame */ + PyFrameObject* m_frame; + /** Scope name */ + std::string m_scopeName; + /** Initialized flag */ + bool m_initialized; + /** Reference to the single change listener */ + SPELLresourceChangeListener* m_listener; + /** Map of resources */ + ResourceMap m_resources; + /** Resources accepted values */ + std::map > > m_resourceAcceptedValues; + /** Resources SPELL/GCS name mapping */ + std::map m_resourceNameMapping; +}; + +/*@}*/ +#endif diff --git a/include/SPELL_EXC/SPELLscheduleCondition.H b/include/SPELL_EXC/SPELLscheduleCondition.H index 3ff3d93..86b7487 100644 --- a/include/SPELL_EXC/SPELLscheduleCondition.H +++ b/include/SPELL_EXC/SPELLscheduleCondition.H @@ -5,7 +5,7 @@ // DESCRIPTION: Defines a scheduler condition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -92,6 +92,7 @@ public: //-------------------------------------------------------------------- period.set(0,0,false); message = ""; m_tmVerificationExpression = NULL; + promptUser = false; } ////////////////////////////////////////////////////////////////////// diff --git a/include/SPELL_EXC/SPELLscheduler.H b/include/SPELL_EXC/SPELLscheduler.H index 841e858..ab1881a 100644 --- a/include/SPELL_EXC/SPELLscheduler.H +++ b/include/SPELL_EXC/SPELLscheduler.H @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor scheduler. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -163,6 +163,11 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void setPromptWarningDelay( unsigned int secs ); + ////////////////////////////////////////////////////////////////////// + /** Inject a warning event message to GCS + **//////////////////////////////////////////////////////////////////// + void raiseWarningEventToGCS( const std::string& message ); + // DATA MEMBERS ////////////////////////////////////////////////////////// protected: //----------------------------------------------------------------- @@ -188,6 +193,11 @@ private: //------------------------------------------------------------------- // LIFECYCLE ///////////////////////////////////////////////////////////// // METHODS /////////////////////////////////////////////////////////////// + ////////////////////////////////////////////////////////////////////// + /** Finish a wait condition (no check lock) + **//////////////////////////////////////////////////////////////////// + void finishWaitInternal( bool setStatus, bool keepLock ); + ////////////////////////////////////////////////////////////////////// /** Release the lock which may be blocking procedure execution **//////////////////////////////////////////////////////////////////// diff --git a/include/SPELL_EXC/SPELLschedulerIF.H b/include/SPELL_EXC/SPELLschedulerIF.H index 507df79..74df657 100644 --- a/include/SPELL_EXC/SPELLschedulerIF.H +++ b/include/SPELL_EXC/SPELLschedulerIF.H @@ -5,7 +5,7 @@ // DESCRIPTION: Defines the interface of the executor scheduler // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -172,6 +172,11 @@ public: //-------------------------------------------------------------------- /** Set default delay for prompt warnings **//////////////////////////////////////////////////////////////////// virtual void setPromptWarningDelay( unsigned int secs ) = 0; + + ////////////////////////////////////////////////////////////////////// + /** Inject a warning event message to GCS + **//////////////////////////////////////////////////////////////////// + virtual void raiseWarningEventToGCS( const std::string& message ) = 0; }; /*@}*/ diff --git a/include/SPELL_EXC/SPELLserviceImpl.H b/include/SPELL_EXC/SPELLserviceImpl.H new file mode 100644 index 0000000..7061d69 --- /dev/null +++ b/include/SPELL_EXC/SPELLserviceImpl.H @@ -0,0 +1,599 @@ +// ################################################################################ +// FILE : SPELLexecutorImpl.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the Executor. +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_SERVICE_IMPL_H__ +#define __SPELL_SERVICE_IMPL_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLexecutorIF.H" +#include "SPELL_EXC/SPELLimportChecker.H" +#include "SPELL_EXC/SPELLbreakpointType.H" +// Project includes -------------------------------------------------------- +#include "SPELL_CFG/SPELLbrowsableLibMode.H" +// System includes --------------------------------------------------------- + + +/** \addtogroup SPELL_EXC */ +/*@{*/ + + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +// TYPES /////////////////////////////////////////////////////////////////// +// DEFINES ///////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief Main implementation of the procedure executor + ** + ** \par Description and usage: + ** + ** Main implementation of the SPELLexecutorIF interface for the SPELL + ** Executor process. + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLserviceImpl: public SPELLexecutorIF +{ +public: //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor + **//////////////////////////////////////////////////////////////////// + SPELLserviceImpl(); + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + virtual ~SPELLserviceImpl(); + + // STATIC //////////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + // ******************************************************************* + // INITIALIZATION + // ******************************************************************* + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void initialize( SPELLcif* cif, + SPELLcontrollerIF* controller, + SPELLschedulerIF* scheduler, + SPELLcallstackIF* callstack, + SPELLframeManager* frameManager ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void prepare( const std::string& serviceId, const SPELLcontextConfig& ctxConfig ); + + // ******************************************************************* + // COMPONENT ACCESSORS + // ******************************************************************* + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLcif& getCIF() const { return *m_cif; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLcallstackIF& getCallstack() const { return *m_callstack; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLcontrollerIF& getController() const { return *m_controller; }; + bool hasController() const { return m_controller != NULL; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLschedulerIF& getScheduler() const { return *m_scheduler; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLchildManager& getChildManager() const { return *m_childMgr; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLexecutorConfig& getConfiguration(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLvariableManager& getVariableManager() const { return *m_varManager; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLresourceManager& getResourceManager() const { return *m_resourceManager; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + SPELLframeManager& getFrameManager() const { return *m_frameManager; }; + + // ******************************************************************* + // EXECUTION ENTRY POINTS + // ******************************************************************* + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void execute(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void recover(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void finalize(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void save(); + + // ******************************************************************* + // MAIN EXECUTION CONTROL + // ******************************************************************* + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void addDispatchListener( SPELLdispatchListener* listener ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void removeDispatchListener( SPELLdispatchListener* listener ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + int dispatch( PyObject* obj, PyFrameObject* frame, int what, PyObject* args ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void command( const ExecutorCommand& cmd, const bool high_priority ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void abort( const std::string& message, bool systemAborted ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void finish( const std::string& message ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void pause(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void interrupt(); + + // ******************************************************************* + // STATUS INFORMATION + // ******************************************************************* + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const SPELLexecutorStatus getStatus() const; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const std::string getContextName() const; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const std::string getInstanceId() const; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setControllingClient( const std::string& clientId ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setControllingHost( const std::string& hostName ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const std::string getControllingHost() const; + + // ******************************************************************* + // EXECUTION CONFIGURATION PARAMETERS + // ******************************************************************* + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setProcedurePath( const std::string& servicePath ) { m_servicePath = servicePath; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setLibraryPath( const std::string& libPath ) { m_libPath = libPath; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setRunInto( const bool enabled ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setByStep( const bool enabled ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setBrowsableLib( const SPELLbrowsableLibMode& browsableLib ); + + void setBrowsableLibStr( const std::string& browsableLibStr ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setExecDelay( const int delay ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setPromptWarningDelay( const int delay ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setForceTcConfirm( const bool force ); + void setForcePromptConfirm( const bool force ); + // ******************************************************************* + // LANGUAGE INTERLOCK + // ******************************************************************* + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void processLock(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void processUnlock(); + + // ******************************************************************* + // EXECUTION MANIPULATION + // ******************************************************************* + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const bool canSkip(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const bool goNextLine(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const bool goLabel( const std::string& label, bool programmed ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const bool goLine( const int line ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const bool setBreakpoint( const std::string& file, + const unsigned int line, + const SPELLbreakpointType type ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void clearBreakpoints(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const bool runScript( const std::string& script ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void executeUserAction(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void setUserAction( const SPELLuserAction& action ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void enableUserAction( const bool enable ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void dismissUserAction(); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + const SPELLuserAction getUserAction() const { return m_userAction; }; + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void stageReached( const std::string& id, const std::string& title ); + + ////////////////////////////////////////////////////////////////////// + /** \see SPELLexecutorIF. + **//////////////////////////////////////////////////////////////////// + void displayStage( const std::string& id, const std::string& title ); + + void refreshStatus(); + // DATA MEMBERS ////////////////////////////////////////////////////////// + +protected: //----------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + /** Identifies the status after an error recovery */ + typedef enum AfterError_ + { + CANNOT_RECOVER, // Will not recover because there is no ws support + CANCEL_RECOVER, // Will not recover because the user does not want + RECOVER_SUCCESS,// Recovery ok + RECOVER_FAILED // Recovery failed + } + AfterError; + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Listener notifications + **//////////////////////////////////////////////////////////////////// + void notifyLineEvent( PyFrameObject* frame, const std::string& file, const int line, const std::string& name ); + void notifyCallEvent( PyFrameObject* frame, const std::string& file, const int line, const std::string& name ); + void notifyReturnEvent( PyFrameObject* frame, const std::string& file, const int line, const std::string& name ); + void notifyErrorEvent( PyFrameObject* frame, const std::string& file, const int line, const std::string& name ); + + ////////////////////////////////////////////////////////////////////// + /** Process a dispatched exception + * + * \return True if the dispatch processing shall not continue + **//////////////////////////////////////////////////////////////////// + bool processException( PyObject* data, int lineno ); + + ////////////////////////////////////////////////////////////////////// + /** Internal implementation of the execute() method + **//////////////////////////////////////////////////////////////////// + void executeInternal( bool reset ); + + ////////////////////////////////////////////////////////////////////// + /** By-Step implementation. If the feature is enabled and the current + * procedure line is a Step, pause the execution. + * + * \param frameLine IN: the current line in the procedure. + **//////////////////////////////////////////////////////////////////// + const bool checkByStep( const int& frameLine ); + + ////////////////////////////////////////////////////////////////////// + /** INIT step feature checker + * + * \param frameLine IN: the current line in the procedure. + * + * \returns True if the dispatching shall be aborted and control + * returned to Python side immediately. + **//////////////////////////////////////////////////////////////////// + const bool checkInitStep( const int& frameLine ); + + ////////////////////////////////////////////////////////////////////// + /** Breakpoint checker. If a breakpoint is set, pause the execution. + * + * \param file IN: current file name. + * \param lineNo IN: the current line in the procedure. + * + **//////////////////////////////////////////////////////////////////// + void checkBreakpoint( const std::string& file, unsigned int lineNo ); + + ////////////////////////////////////////////////////////////////////// + /** Error recovery implementation + * + * \returns True if the error can be recovered + **//////////////////////////////////////////////////////////////////// + const AfterError handleExecutionError(); + + ////////////////////////////////////////////////////////////////////// + /** Load execution environment data and instances + **//////////////////////////////////////////////////////////////////// + void loadExecutionEnvironment(); + + ////////////////////////////////////////////////////////////////////// + /** Load SPELL driver + **//////////////////////////////////////////////////////////////////// + void loadDriver(); + + ////////////////////////////////////////////////////////////////////// + /** Unload SPELL driver. + * + * \param shutdown IN: If true, unload completely all data. If false, + * just cleanup the driver, but do not remove it completely (this + * is handled on driver side, not in executor) + **//////////////////////////////////////////////////////////////////// + void unloadDriver( bool shutdown ); + + ////////////////////////////////////////////////////////////////////// + /** Called when the execution finishes with errors. The behavior + * depends on the recovery mechanism status and user's choice. + * + * \return True if the execution shall be started over. + **//////////////////////////////////////////////////////////////////// + bool executorFinishedWithErrors(); + + ////////////////////////////////////////////////////////////////////// + /** Called when the execution finishes sucessfully. The behavior + * depends on the user's choice. + * + * \return True if the execution shall be started over. + **//////////////////////////////////////////////////////////////////// + bool executorFinishedRight(); + + ////////////////////////////////////////////////////////////////////// + /** Called when the execution is aborted. The behavior + * depends on the user's choice. + * + * \return True if the execution shall be started over. + **//////////////////////////////////////////////////////////////////// + bool executorAborted(); + + ////////////////////////////////////////////////////////////////////// + /** Waits for the user's choice: close the procedure or reload it. + * + * \return True if the execution shall be started over. + **//////////////////////////////////////////////////////////////////// + bool reloadOrClose(); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + +private: //------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Install the calling arguments data container on the main scope. + **//////////////////////////////////////////////////////////////////// + void installCallingArguments(); + + ////////////////////////////////////////////////////////////////////// + /** Install the internal variables data container on the main scope. + **//////////////////////////////////////////////////////////////////// + void installInternalVariables(); + + ////////////////////////////////////////////////////////////////////// + /** Change the force TC flag on the adapter layer + **//////////////////////////////////////////////////////////////////// + void setForceTcConfirmInternal( bool force ); + void setForcePromptConfirmInternal( bool force ); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + + /** Empty configuration */ + SPELLexecutorConfig m_emptyConfig; + /** True if the initial configuration parameters have been set */ + bool m_initialized; + /** Holds the procedure instance identifier */ + std::string m_instanceId; + /** Holds the parent procedure, if any */ + + std::string m_parentId; + /** Holds the child procedure, if any */ + std::string m_childId; + + /** Used for dispatching filter (procedures) */ + std::string m_servicePath; + /** Used for dispatching filter (user library) */ + std::string m_libPath; + /** Output directory location */ + std::string m_outputDir; + /** Input directory location */ + std::string m_inputDir; + + /** True when the INIT step, if any, has been reached */ + bool m_initStepDone; + /** Holds the lines in the main procedure that are executed during INIT mode */ + std::list m_initLines; + + /** Holds the user action data if any */ + SPELLuserAction m_userAction; + + /** Holds the command controller */ + SPELLcontrollerIF* m_controller; + /** Holds the scheduler controller */ + SPELLschedulerIF* m_scheduler; + /** Holds the callstack model */ + SPELLcallstackIF* m_callstack; + /** Holds the reference to client interface */ + SPELLcif* m_cif; + /** Holds the child procedure manager */ + SPELLchildManager* m_childMgr; + /** Holds the execution control frame */ + SPELLframeManager* m_frameManager; + /** Holds the module import checker */ + SPELLimportChecker m_importChecker; + /** Holds the variable manager */ + SPELLvariableManager* m_varManager; + /** Holds the resource manager */ + SPELLresourceManager* m_resourceManager; + + /** Command synchronization lock */ + SPELLmutex m_cmdLock; + /** Dispatch synchronization lock */ + SPELLmutex m_dspLock; + + /** Next programmed goto target */ + std::string m_gotoTarget; + + /** Dispatch event listeners */ + SPELLdispatchListeners m_listeners; +}; + +/*@}*/ +#endif diff --git a/include/SPELL_EXC/SPELLserviceInterpreter.H b/include/SPELL_EXC/SPELLserviceInterpreter.H new file mode 100644 index 0000000..91d0d61 --- /dev/null +++ b/include/SPELL_EXC/SPELLserviceInterpreter.H @@ -0,0 +1,249 @@ +// ################################################################################ +// FILE : SPELLinterpreter.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Main entry point of the executor. +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_SERVICE_INTERPRETER_H__ +#define __SPELL_SERVICE_INTERPRETER_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLexecutorIF.H" +#include "SPELL_EXC/SPELLserviceImpl.H" +#include "SPELL_EXC/SPELLframeManager.H" +// Project includes -------------------------------------------------------- +// System includes --------------------------------------------------------- + + +/** \addtogroup SPELL_EXC */ +/*@{*/ + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +// ENUMS /////////////////////////////////////////////////////////////////// +// TYPES /////////////////////////////////////////////////////////////////// + +/** Configuration parameters for the interpreter */ +typedef struct SPELLinterpreterConfig_ +{ + /** If true, WS is enabled and the given persistent file is used */ + bool warmstart; + /** If true, the given persistent file is used to recover status */ + bool recover; + /** If true, the given procId corresponds to a script, not a proc id */ + bool script; + /** Path to main configuration file */ + std::string configFile; + /** Procedure identifier or script path */ + std::string procId; + /** SPELL context to be used */ + std::string ctxName; + /** Context listening port */ + int ctxPort; + /** Time string to be used in generated files */ + std::string timeId; + /** If given, holds the path to the file where WS data is stored */ + std::string persis; +} +SPELLinterpreterConfig; + +// DEFINES ///////////////////////////////////////////////////////////////// + +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief Encapsulates the Python interpreter framework in a single object. + ** + ** \par Description and usage: + ** + ** The interpreter is the main class of the executor + ** process. It creates all the pieces needed to execute a given script + ** or procedure. It provides also the ability of recovering a proc + ** execution by using a given WS information file. + ** + ** Singleton used in the main program function. + ** +**//////////////////////////////////////////////////////////////////////////// +class SPELLserviceInterpreter +{ +public: //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Destructor. + **//////////////////////////////////////////////////////////////////// + ~SPELLserviceInterpreter(); + + // STATIC //////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Access to the singleton instance + **//////////////////////////////////////////////////////////////////// + static SPELLserviceInterpreter& instance(); + + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Main interpreter program loop. Will first prepare the control + * objects (controller, scheduler, callstack, etc), \see prepareObjects + * method. Then, execution environment will be set up (\see prepareExecution + * method). If both operations are success, the methods recover() or + * execute() are invoked depending on the command line arguments of + * the executor application. Once these finish, it waits for the signal + * to cleanup all things in order to exit the executor process. + **//////////////////////////////////////////////////////////////////// + void mainLoop(); + + ////////////////////////////////////////////////////////////////////// + /** Initialize the interpreter with the given configuration + ** + ** \param config IN: Configuration data + ** \param cif IN: SPELL client interface instance to use. + **//////////////////////////////////////////////////////////////////// + void initialize( const SPELLinterpreterConfig& config, SPELLcif* cif ); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + +protected: //----------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLserviceInterpreter(); + + // METHODS /////////////////////////////////////////////////////////////// + // DATA MEMBERS ////////////////////////////////////////////////////////// + +private: //------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Prepare the CIF component + **//////////////////////////////////////////////////////////////////// + void prepareCIF( const SPELLcontextConfig& ctxConfig ); + + ////////////////////////////////////////////////////////////////////// + /** Recover the CIF component information + **//////////////////////////////////////////////////////////////////// + void recoverCIF( const SPELLcontextConfig& ctxConfig ); + + ////////////////////////////////////////////////////////////////////// + /** Prepare the warm start / error recovery support mechanism. It will + * build the warmstart persistence file name, create the SPELLwarmStart + * object and initialize the execution frame with their references. MAy + * fail if SPELL_DATA environment variable is not set, or the persistent + * file cannot be created. + **//////////////////////////////////////////////////////////////////// + void prepareWarmStart( const SPELLcontextConfig& ctxConfig ); + + ////////////////////////////////////////////////////////////////////// + /** If initial preparation succeeds, additional mechanisms are intialized + * configured and/or installed: + * + * 1. Install special exceptions on Python side + * 2. Install the executor instance on the SPELL registry + * 3. Install the go-to mechanism on Python side + * 4. Install the CIF instance on the SPELL registry + * 5. Get procedure and User Library search paths + * 6. Compile the procedure code and prepare the execution frame. + * + * \returns False in case of failure. + **//////////////////////////////////////////////////////////////////// + const bool prepareExecution(); + + ////////////////////////////////////////////////////////////////////// + /** Prepare objects neede for the execution of the procedure/script. + * The following steps are performed: + * + * 1. Initialize Python API + * 2. Install Log mechanism on Python side + * 3. Import SPELL framework on Python side. + * 4. Load configuration from XML files + * 5. Setup the client interface to connect to the client + * 6. Create control objects (controller, scheduler, frame, etc) + * 7. Prepare the warm start support + * 8. Initialize and configure the executor object + * 9. Start the command controller. + * + * \returns False in case of failure. + **//////////////////////////////////////////////////////////////////// + const bool prepareObjects(); + + ////////////////////////////////////////////////////////////////////// + /** Execute the script/procedure. Basically invoke the execute() method + * of the executor object and catch any error taking place. + **//////////////////////////////////////////////////////////////////// + void execute(); + + ////////////////////////////////////////////////////////////////////// + /** Recover script/procedure execution from persistent file. Invoke + * the recover() method of the executor object. + **//////////////////////////////////////////////////////////////////// + void recover(); + + ////////////////////////////////////////////////////////////////////// + /** Handle errors raised during the initialization performed by the + * prepareExecution method. + **//////////////////////////////////////////////////////////////////// + void handleStartupError(); + + // DATA MEMBERS ////////////////////////////////////////////////////////// + /** Interpreter configuration */ + SPELLinterpreterConfig m_config; + /** Executor instance */ + SPELLserviceImpl* m_serviceImpl; + /** Warm start mechanism */ + SPELLwarmStart* m_warmStart; + /** Client interface */ + SPELLcif* m_cif; + /** Execution controller */ + SPELLcontrollerIF* m_controller; + /** Execution scheduler */ + SPELLschedulerIF* m_scheduler; + /** Callstack manager */ + SPELLcallstackIF* m_callstack; + /** Execution frame */ + SPELLframeManager* m_frameManager; + /** Current procedure or file name */ + std::string m_service; + /** Current procedures path */ + std::string m_servicePath; + /** Current library path */ + std::string m_libPath; +}; + +/*@}*/ +#endif diff --git a/include/SPELL_EXC/SPELLstepOverMode.H b/include/SPELL_EXC/SPELLstepOverMode.H index adeb7eb..de295b1 100644 --- a/include/SPELL_EXC/SPELLstepOverMode.H +++ b/include/SPELL_EXC/SPELLstepOverMode.H @@ -5,7 +5,7 @@ // DESCRIPTION: Defines the step over modes // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLuserAction.H b/include/SPELL_EXC/SPELLuserAction.H index 39f511e..f7d779f 100644 --- a/include/SPELL_EXC/SPELLuserAction.H +++ b/include/SPELL_EXC/SPELLuserAction.H @@ -5,7 +5,7 @@ // DESCRIPTION: Defines a user action // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLvarInfo.H b/include/SPELL_EXC/SPELLvarInfo.H index 357897a..d355712 100644 --- a/include/SPELL_EXC/SPELLvarInfo.H +++ b/include/SPELL_EXC/SPELLvarInfo.H @@ -5,7 +5,7 @@ // DESCRIPTION: Procedure variable information // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLvariableManager.H b/include/SPELL_EXC/SPELLvariableManager.H index fc0a63c..a8c490f 100644 --- a/include/SPELL_EXC/SPELLvariableManager.H +++ b/include/SPELL_EXC/SPELLvariableManager.H @@ -5,7 +5,7 @@ // DESCRIPTION: Manager of procedure runtime variables // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_EXC/SPELLvariableMonitor.H b/include/SPELL_EXC/SPELLvariableMonitor.H index 3d4c58a..4ce695a 100644 --- a/include/SPELL_EXC/SPELLvariableMonitor.H +++ b/include/SPELL_EXC/SPELLvariableMonitor.H @@ -5,7 +5,7 @@ // DESCRIPTION: Monitor for runtime procedure variables // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_IPC/SPELLipc.H b/include/SPELL_IPC/SPELLipc.H index 42f4554..0e046b6 100644 --- a/include/SPELL_IPC/SPELLipc.H +++ b/include/SPELL_IPC/SPELLipc.H @@ -5,7 +5,7 @@ // DESCRIPTION: Inter process communication, data definitions // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_IPC/SPELLipcChannel.H b/include/SPELL_IPC/SPELLipcChannel.H index ecfa0b5..065d413 100644 --- a/include/SPELL_IPC/SPELLipcChannel.H +++ b/include/SPELL_IPC/SPELLipcChannel.H @@ -5,7 +5,7 @@ // DESCRIPTION: Inter-process communication - IPC channel // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -43,6 +43,7 @@ // FORWARD REFERENCES ////////////////////////////////////////////////////// class SPELLipcInterface; +class SPELLipcInput; // TYPES /////////////////////////////////////////////////////////////////// // DEFINES ///////////////////////////////////////////////////////////////// diff --git a/include/SPELL_IPC/SPELLipcClientInterface.H b/include/SPELL_IPC/SPELLipcClientInterface.H index 687e0f8..d24154b 100644 --- a/include/SPELL_IPC/SPELLipcClientInterface.H +++ b/include/SPELL_IPC/SPELLipcClientInterface.H @@ -5,7 +5,7 @@ // DESCRIPTION: IPC - Interface for clients // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -269,6 +269,8 @@ protected: //----------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void incomingResponse( const SPELLipcMessage& msg ); + SPELLipcMessage getPingMessage(); + // DATA MEMBERS ////////////////////////////////////////////////////////// diff --git a/include/SPELL_IPC/SPELLipcDataChunk.H b/include/SPELL_IPC/SPELLipcDataChunk.H index c8cbc81..8119831 100644 --- a/include/SPELL_IPC/SPELLipcDataChunk.H +++ b/include/SPELL_IPC/SPELLipcDataChunk.H @@ -5,7 +5,7 @@ // DESCRIPTION: Mechanism for transferring big data // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_IPC/SPELLipcError.H b/include/SPELL_IPC/SPELLipcError.H index 2df1b2c..f5596fa 100644 --- a/include/SPELL_IPC/SPELLipcError.H +++ b/include/SPELL_IPC/SPELLipcError.H @@ -5,7 +5,7 @@ // DESCRIPTION: IPC error defintions // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_IPC/SPELLipcHelper.H b/include/SPELL_IPC/SPELLipcHelper.H index 8cef46b..3fe969d 100644 --- a/include/SPELL_IPC/SPELLipcHelper.H +++ b/include/SPELL_IPC/SPELLipcHelper.H @@ -5,7 +5,7 @@ // DESCRIPTION: Utilities for IPC // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_IPC/SPELLipcIncoming.H b/include/SPELL_IPC/SPELLipcIncoming.H index 02c660e..9758650 100644 --- a/include/SPELL_IPC/SPELLipcIncoming.H +++ b/include/SPELL_IPC/SPELLipcIncoming.H @@ -5,7 +5,7 @@ // DESCRIPTION: Handler for incoming IPC messages // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -30,13 +30,13 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // System includes --------------------------------------------------------- // Local includes ---------------------------------------------------------- +#include "SPELL_IPC/SPELLipcChannel.H" #include "SPELL_IPC/SPELLipcInterfaceListener.H" // Project includes -------------------------------------------------------- #include "SPELL_SYN/SPELLthread.H" #include "SPELL_SYN/SPELLevent.H" #include "SPELL_SYN/SPELLthreadWork.H" - /** \addtogroup SPELL_IPC */ /*@{*/ @@ -44,6 +44,7 @@ // FORWARD REFERENCES ////////////////////////////////////////////////////// class SPELLipcInterface; class SPELLipcOutput; + // TYPES /////////////////////////////////////////////////////////////////// // DEFINES ///////////////////////////////////////////////////////////////// @@ -201,7 +202,7 @@ private: //------------------------------------------------------------------- /** Holds the listener to be notified */ SPELLipcInterfaceListener& m_listener; /** The message to be processed */ - const SPELLipcMessage m_message; + SPELLipcMessage m_message; /** Event set when the processing finishes */ SPELLevent m_processingFinishEvent; /** Event set when the processing starts */ @@ -294,7 +295,8 @@ public: //-------------------------------------------------------------------- SPELLipcIncomingRequest( const std::string& requestId, const std::string& ifcName, SPELLipcMessage msg, - SPELLipcOutput& writer, + SPELLmutex& mutex, + SPELLipcChannel** channel, SPELLipcInterfaceListener& listener ); ////////////////////////////////////////////////////////////////////// @@ -322,7 +324,8 @@ private: //------------------------------------------------------------------- // DATA MEMBERS ////////////////////////////////////////////////////////// /** Holds the writer to use to send the response */ - SPELLipcOutput& m_writer; + SPELLipcChannel** m_channel; + SPELLmutex& m_mutex; }; /*@}*/ diff --git a/include/SPELL_IPC/SPELLipcInput.H b/include/SPELL_IPC/SPELLipcInput.H index 56b2619..e513036 100644 --- a/include/SPELL_IPC/SPELLipcInput.H +++ b/include/SPELL_IPC/SPELLipcInput.H @@ -5,7 +5,7 @@ // DESCRIPTION: Data reader // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -42,6 +42,7 @@ // FORWARD REFERENCES ////////////////////////////////////////////////////// class SPELLipcChannel; +class SPELLipcInterface; // TYPES /////////////////////////////////////////////////////////////////// // DEFINES ///////////////////////////////////////////////////////////////// @@ -96,6 +97,11 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// bool isConnected(); + void setHandleEOC(bool eocValue) + { + m_requestedEOC = eocValue; + } + // DATA MEMBERS ////////////////////////////////////////////////////////// protected: //----------------------------------------------------------------- diff --git a/include/SPELL_IPC/SPELLipcInterface.H b/include/SPELL_IPC/SPELLipcInterface.H index e62a32e..ba67190 100644 --- a/include/SPELL_IPC/SPELLipcInterface.H +++ b/include/SPELL_IPC/SPELLipcInterface.H @@ -5,7 +5,7 @@ // DESCRIPTION: Common IPC interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -175,6 +175,12 @@ public: //-------------------------------------------------------------------- * \param msg IN: response to be processed. **//////////////////////////////////////////////////////////////////// virtual void incomingResponse( const SPELLipcMessage& msg ) = 0; + + virtual SPELLipcMessage getPingMessage() + { + static SPELLipcMessage empty = SPELLipcMessage(); + return empty; + }; }; /*@}*/ diff --git a/include/SPELL_IPC/SPELLipcInterfaceListener.H b/include/SPELL_IPC/SPELLipcInterfaceListener.H index 100926b..01e6838 100644 --- a/include/SPELL_IPC/SPELLipcInterfaceListener.H +++ b/include/SPELL_IPC/SPELLipcInterfaceListener.H @@ -5,7 +5,7 @@ // DESCRIPTION: Interface IPC message listener // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -89,6 +89,12 @@ public: /** Peer has closed the connection (end of conversation) **//////////////////////////////////////////////////////////////////// virtual void processConnectionClosed( int clientKey ) = 0; + + virtual SPELLipcMessage getPingMessage() + { + static SPELLipcMessage empty = SPELLipcMessage(); + return empty; + }; }; /*@}*/ diff --git a/include/SPELL_IPC/SPELLipcMessage.H b/include/SPELL_IPC/SPELLipcMessage.H index b93ae94..c882f51 100644 --- a/include/SPELL_IPC/SPELLipcMessage.H +++ b/include/SPELL_IPC/SPELLipcMessage.H @@ -5,7 +5,7 @@ // DESCRIPTION: Definition of IPC message // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -239,6 +239,12 @@ public: //-------------------------------------------------------------------- return m_type; } + ////////////////////////////////////////////////////////////////////// + /**Cleans up this message and frees as much meory as possible. + * + **//////////////////////////////////////////////////////////////////// + void clear(); + ////////////////////////////////////////////////////////////////////// /** Set the value of a message field. * diff --git a/include/SPELL_IPC/SPELLipcMessageMailbox.H b/include/SPELL_IPC/SPELLipcMessageMailbox.H index 989acdb..b8b365d 100644 --- a/include/SPELL_IPC/SPELLipcMessageMailbox.H +++ b/include/SPELL_IPC/SPELLipcMessageMailbox.H @@ -5,7 +5,7 @@ // DESCRIPTION: Mailbox for incoming messages // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -166,11 +166,6 @@ private: //------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// SPELLipcMessageQueue* getQueue( std::string id ); - ////////////////////////////////////////////////////////////////////// - /** Cancell all requests waiting - **//////////////////////////////////////////////////////////////////// - void cancelAll(); - // DATA MEMBERS ////////////////////////////////////////////////////////// /** Holds the map of message queues */ SPELLipcMessageQueueMap m_queueMap; diff --git a/include/SPELL_IPC/SPELLipcOutput.H b/include/SPELL_IPC/SPELLipcOutput.H index 7d8d037..5ede685 100644 --- a/include/SPELL_IPC/SPELLipcOutput.H +++ b/include/SPELL_IPC/SPELLipcOutput.H @@ -5,7 +5,7 @@ // DESCRIPTION: Data writer // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_IPC/SPELLipcServerInterface.H b/include/SPELL_IPC/SPELLipcServerInterface.H index 261b597..8b693dc 100644 --- a/include/SPELL_IPC/SPELLipcServerInterface.H +++ b/include/SPELL_IPC/SPELLipcServerInterface.H @@ -5,7 +5,7 @@ // DESCRIPTION: Interface for servers // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -216,7 +216,7 @@ protected: //----------------------------------------------------------------- ////////////////////////////////////////////////////////////////////// /** Obtain the channel for a peer **//////////////////////////////////////////////////////////////////// - SPELLipcChannel& getChannel( int peerKey ); + SPELLipcChannel** getChannel( int peerKey ); // DATA MEMBERS ////////////////////////////////////////////////////////// @@ -251,6 +251,7 @@ private: //------------------------------------------------------------------- * \param key IN: peer key. **//////////////////////////////////////////////////////////////////// void removeClient( int key ); + void removeClientInternal( int key ); ////////////////////////////////////////////////////////////////////// /** Remove all client of the list of connected clients and disconnect @@ -325,6 +326,9 @@ private: //------------------------------------------------------------------- std::string m_ifcName; /** Holds the client keys used */ std::vector m_usedClientKeys; + + std::vector m_clientKeysToRemove; + /** Holds the server key */ int m_serverKey; /** Holds the server port */ @@ -333,6 +337,7 @@ private: //------------------------------------------------------------------- SPELLsocket* m_serverSocket; /** Synchronization mutex for operations */ SPELLmutex m_clientLock; + SPELLmutex m_clientCleanupLock; /** True if the interface is connected */ bool m_connected; /** Associated IPC listener */ diff --git a/include/SPELL_IPC/SPELLipcServerInterfaceSingle.H b/include/SPELL_IPC/SPELLipcServerInterfaceSingle.H index c6c019e..3346ea7 100644 --- a/include/SPELL_IPC/SPELLipcServerInterfaceSingle.H +++ b/include/SPELL_IPC/SPELLipcServerInterfaceSingle.H @@ -5,7 +5,7 @@ // DESCRIPTION: Interface for single-client servers // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -187,6 +187,7 @@ private: //------------------------------------------------------------------- /** Remove the client **//////////////////////////////////////////////////////////////////// void removeClient(); + void removeClientInternal(); ////////////////////////////////////////////////////////////////////// /** \see SPELLipcInterface. @@ -272,7 +273,8 @@ private: //------------------------------------------------------------------- /** Synchronization mutex for operations */ SPELLmutex m_clientLock; /** True if the interface is connected */ - bool m_connected; + bool m_connected; + bool m_removeClient; /** Associated IPC listener */ SPELLipcInterfaceListener* m_listener; /** Holds the set of incoming request processors to be cleaned up */ @@ -289,6 +291,8 @@ private: //------------------------------------------------------------------- SPELLevent m_clientConnectionEvent; /** Blocks processing of incoming requests */ SPELLmutex m_incomingLock; + /** Only allow one thread to cleanup the channel */ + SPELLmutex m_cleanupLock; /** IPC channel */ SPELLipcChannel* m_channel; /** Timeout for request retries */ diff --git a/include/SPELL_IPC/SPELLipcTrash.H b/include/SPELL_IPC/SPELLipcTrash.H index 5ff4201..4b103cc 100644 --- a/include/SPELL_IPC/SPELLipcTrash.H +++ b/include/SPELL_IPC/SPELLipcTrash.H @@ -5,7 +5,7 @@ // DESCRIPTION: Cleaner for obsolete requests // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -111,8 +111,6 @@ public: //-------------------------------------------------------------------- // DATA MEMBERS ////////////////////////////////////////////////////////// -protected: //----------------------------------------------------------------- - // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// // TYPES ///////////////////////////////////////////////////////////////// diff --git a/include/SPELL_IPC/SPELLipc_Context.H b/include/SPELL_IPC/SPELLipc_Context.H index cfe316e..57fe5dc 100644 --- a/include/SPELL_IPC/SPELLipc_Context.H +++ b/include/SPELL_IPC/SPELLipc_Context.H @@ -5,7 +5,7 @@ // DESCRIPTION: Inter process communication, data definitions for context process // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -100,8 +100,11 @@ namespace MessageField static const std::string FIELD_PROMPT_DELAY("PromptWarningDelay"); static const std::string FIELD_BY_STEP("ByStep"); static const std::string FIELD_FORCE_TC_CONFIRM("ForceTcConfirm"); + static const std::string FIELD_FORCE_PROMPT_CONFIRM("ForcePromptConfirm"); static const std::string FIELD_SAVE_STATE_MODE("SaveStateMode"); static const std::string FIELD_WATCH_VARIABLES("WatchVariables"); + static const std::string FIELD_RESOURCES_DISPLAY("ResourcesDisplay"); + static const std::string FIELD_LOCAL_RESOURCE_SUPPORTED("LocalResourcesSupported"); static const std::string FIELD_MAX_VERBOSITY("MaxVerbosity"); static const std::string FIELD_BROWSABLE_LIB("BrowsableLib"); @@ -178,13 +181,18 @@ namespace MessageValue namespace ContextMessages { /** Request list of available executors (running procedures) */ - static const std::string REQ_EXEC_LIST("REQ_EXEC_LIST"); + static const std::string REQ_EXEC_LIST("REQ_EXEC_LIST"); + static const std::string REQ_SERVICE_LIST("REQ_SERVICE_LIST"); + /** Response to REQ_EXEC_LIST */ static const std::string RSP_EXEC_LIST("RSP_EXEC_LIST"); + static const std::string RSP_SERVICE_LIST("RSP_SERVICE_LIST"); /** Request executor (procedure) information */ - static const std::string REQ_EXEC_INFO("REQ_EXEC_INFO"); - /** Response to REQ_EXEC_INFO */ + static const std::string REQ_EXEC_INFO("REQ_EXEC_INFO"); + /** Request service information */ + static const std::string REQ_SERVICE_INFO("REQ_SERVICE_INFO"); + /** Response to REQ_EXEC_INFO */ static const std::string RSP_EXEC_INFO("RSP_EXEC_INFO"); /** Request opening a new executor (procedure) diff --git a/include/SPELL_IPC/SPELLipc_Executor.H b/include/SPELL_IPC/SPELLipc_Executor.H index 582611b..0537f10 100644 --- a/include/SPELL_IPC/SPELLipc_Executor.H +++ b/include/SPELL_IPC/SPELLipc_Executor.H @@ -5,7 +5,7 @@ // DESCRIPTION: Inter process communication, data definitions for executor process // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -80,6 +80,9 @@ namespace MessageId /** Message for enabling or disabling variable analysis */ static const std::string MSG_ID_WVARIABLES_ENABLE("MSG_WVARIABLES_ENABLE"); static const std::string MSG_ID_WVARIABLES_DISABLE("MSG_WVARIABLES_DISABLE"); + /** Message for enabling or disabling resource analysis */ + static const std::string MSG_ID_DRESOURCES_ENABLE("MSG_DRESOURCES_ENABLE"); + static const std::string MSG_ID_DRESOURCES_DISABLE("MSG_DRESOURCES_DISABLE"); }; /** Defines message fields */ @@ -201,6 +204,31 @@ namespace MessageField /** Variable analysis enabled */ static const std::string FIELD_WVARIABLES_ENABLED("WVarEnabled"); + //################################################################################ + //# DISPLAY OF RESOURCES + //################################################################################ + + /** Resource names */ + static const std::string FIELD_RESOURCE_NAME("ResourceName"); + /** Resource global values */ + static const std::string FIELD_RESOURCE_GLOBAL_VALUE("ResourceGlobalValue"); + /** Resource local values */ + static const std::string FIELD_RESOURCE_LOCAL_VALUE("ResourceLocalValue"); + /** Resource types */ + static const std::string FIELD_RESOURCE_TYPE("ResourceType"); + /** Resource value range */ + static const std::string FIELD_RESOURCE_VALUE_RANGE("ResourceValueRange"); + /** Resource gcs value range */ + static const std::string FIELD_RESOURCE_GCS_VALUE_RANGE("ResourceGCSValueRange"); + /** Resource GCS names */ + static const std::string FIELD_RESOURCE_GCS_NAME("ResourceGCSName"); + /** Resource deleted flags */ + static const std::string FIELD_RESOURCE_DELETE("ResourceDelete"); + /** Resource added flags */ + static const std::string FIELD_RESOURCE_ADDED("ResourceAdded"); + /** Resource analysis enabled */ + static const std::string FIELD_DRESOURCES_ENABLED("DResourcesEnabled"); + //################################################################################ //# DICTIONARY EDITING //################################################################################ @@ -399,6 +427,23 @@ namespace ExecutorMessages /** Response */ static const std::string RSP_WVARIABLES_ENABLED("RSP_WVARIABLES_ENABLED"); + //################################################################################ + //# DISPLAY OF RESOURCES + //################################################################################ + + /** Request for retrieving resource names */ + static const std::string REQ_GET_RESOURCES("REQ_GET_RESOURCES"); + /** Response */ + static const std::string RSP_GET_RESOURCES("RSP_GET_RESOURCES"); + /** Request for changing resources */ + static const std::string REQ_CHANGE_RESOURCE("REQ_CHANGE_RESOURCE"); + /** Response */ + static const std::string RSP_CHANGE_RESOURCE("RSP_CHANGE_RESOURCE"); + /** Request to check if resource analysis is enabled */ + static const std::string REQ_DRESOURCES_ENABLED("REQ_DRESOURCES_ENABLED"); + /** Response */ + static const std::string RSP_DRESOURCES_ENABLED("RSP_DRESOURCES_ENABLED"); + //################################################################################ //# DICTIONARY EDITING //################################################################################ @@ -423,6 +468,9 @@ namespace ExecutorMessages /** Oneway message for variable scope change notifications */ static const std::string MSG_SCOPE_CHANGE("MSG_SCOPE_CHANGE"); + /** Oneway message for resource change notifications */ + static const std::string MSG_RESOURCE_CHANGE("MSG_RESOURCE_CHANGE"); + //################################################################################ //# REQUESTS TO CONTEXT //################################################################################ diff --git a/include/SPELL_IPC/SPELLipc_Listener.H b/include/SPELL_IPC/SPELLipc_Listener.H index 5ef1469..8c2a68c 100644 --- a/include/SPELL_IPC/SPELLipc_Listener.H +++ b/include/SPELL_IPC/SPELLipc_Listener.H @@ -5,7 +5,7 @@ // DESCRIPTION: Inter process communication, data definitions for listener process // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_IPC/SPELLsocket.H b/include/SPELL_IPC/SPELLsocket.H index 0df5e6c..485e2b1 100644 --- a/include/SPELL_IPC/SPELLsocket.H +++ b/include/SPELL_IPC/SPELLsocket.H @@ -5,7 +5,7 @@ // DESCRIPTION: Socket wrapper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -60,7 +60,7 @@ public: //-------------------------------------------------------------------- * * \param fd IN: socket file descriptor. *///////////////////////////////////////////////////////////////////// - SPELLsocket( int fd ); + SPELLsocket(int fd, std::string user, std::vector groups); /*//////////////////////////////////////////////////////////////////// * Destructor @@ -116,6 +116,11 @@ public: //-------------------------------------------------------------------- *///////////////////////////////////////////////////////////////////// bool waitData( int timeout ); + + std::string getUser() { + return user; + }; + /*//////////////////////////////////////////////////////////////////// * Shutdown the socket input. *///////////////////////////////////////////////////////////////////// @@ -183,6 +188,9 @@ private: //------------------------------------------------------------------- int m_socketFd; /** Socket connected flag */ bool m_connected; + + std::string user; + std::vector groups; }; /*@}*/ diff --git a/include/SPELL_IPC/SPELLtimeoutValues.H b/include/SPELL_IPC/SPELLtimeoutValues.H index 7157cd1..848b6f3 100644 --- a/include/SPELL_IPC/SPELLtimeoutValues.H +++ b/include/SPELL_IPC/SPELLtimeoutValues.H @@ -5,7 +5,7 @@ // DESCRIPTION: Inter process communication, timeout values // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_LST/SPELLlistener.H b/include/SPELL_LST/SPELLlistener.H index 1b9dc46..068d769 100644 --- a/include/SPELL_LST/SPELLlistener.H +++ b/include/SPELL_LST/SPELLlistener.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL listener library header file // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_LST/SPELLlistenerComm.H b/include/SPELL_LST/SPELLlistenerComm.H index 78ce8b8..a5507a9 100644 --- a/include/SPELL_LST/SPELLlistenerComm.H +++ b/include/SPELL_LST/SPELLlistenerComm.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL listener library header file // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_LST/SPELLlistenerContext.H b/include/SPELL_LST/SPELLlistenerContext.H index 3c0a5b6..836593e 100644 --- a/include/SPELL_LST/SPELLlistenerContext.H +++ b/include/SPELL_LST/SPELLlistenerContext.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL listener library header file // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -97,10 +97,10 @@ public: void notifyClients( const SPELLipcMessage& msg ); // SPELLprocessListener methods - void processStarted( const std::string& identifier ); - void processFinished( const std::string& identifier, const int& retValue ); - void processKilled( const std::string& identifier ); - void processFailed( const std::string& identifier ); + void processStarted( const std::string identifier ); + void processFinished( const std::string identifier, const int& retValue ); + void processKilled( const std::string identifier ); + void processFailed( const std::string identifier ); bool respawnContext(const std::string& ctxName); // Internals diff --git a/include/SPELL_LST/SPELLlistenerGui.H b/include/SPELL_LST/SPELLlistenerGui.H index 4848d39..ed5a28d 100644 --- a/include/SPELL_LST/SPELLlistenerGui.H +++ b/include/SPELL_LST/SPELLlistenerGui.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL listener library header file // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_PRC/SPELLprocess.H b/include/SPELL_PRC/SPELLprocess.H index 37d8315..08501b1 100644 --- a/include/SPELL_PRC/SPELLprocess.H +++ b/include/SPELL_PRC/SPELLprocess.H @@ -5,7 +5,7 @@ // DESCRIPTION: Process model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_PRC/SPELLprocessInfo.H b/include/SPELL_PRC/SPELLprocessInfo.H index 3a9ee2f..5344f2f 100644 --- a/include/SPELL_PRC/SPELLprocessInfo.H +++ b/include/SPELL_PRC/SPELLprocessInfo.H @@ -5,7 +5,7 @@ // DESCRIPTION: Process information // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_PRC/SPELLprocessManager.H b/include/SPELL_PRC/SPELLprocessManager.H index 4efd787..1c8b05d 100644 --- a/include/SPELL_PRC/SPELLprocessManager.H +++ b/include/SPELL_PRC/SPELLprocessManager.H @@ -5,7 +5,7 @@ // DESCRIPTION: Process manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -77,7 +77,7 @@ public://-------------------------------------------------------------------- * * \param identifier IN: unique identifier of the process. **//////////////////////////////////////////////////////////////////// - virtual void processStarted( const std::string& identifier ) = 0; + virtual void processStarted( const std::string identifier ) = 0; ////////////////////////////////////////////////////////////////////// /** Notify the given process has finished. @@ -85,21 +85,21 @@ public://-------------------------------------------------------------------- * \param identifier IN: unique identifier of the process. * \param retValue IN: process exit code. **//////////////////////////////////////////////////////////////////// - virtual void processFinished( const std::string& identifier, const int& retValue ) = 0; + virtual void processFinished( std::string identifier, const int& retValue ) = 0; ////////////////////////////////////////////////////////////////////// /** Notify the given process has been killed. * * \param identifier IN: unique identifier of the process. **//////////////////////////////////////////////////////////////////// - virtual void processKilled( const std::string& identifier ) = 0; + virtual void processKilled( std::string identifier ) = 0; ////////////////////////////////////////////////////////////////////// /** Notify the given process has failed to start. * * \param identifier IN: unique identifier of the process. **//////////////////////////////////////////////////////////////////// - virtual void processFailed( const std::string& identifier ) = 0; + virtual void processFailed( std::string identifier ) = 0; }; ////////////////////////////////////////////////////////////////////////////// @@ -168,6 +168,8 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void killProcess( const std::string& identifier ); + bool checkProcessExist( const std::string& identifier ); + ////////////////////////////////////////////////////////////////////// /** Wait the given process to finish. * @@ -241,7 +243,7 @@ protected: //----------------------------------------------------------------- * * \param identifier IN: process unique identifier. **//////////////////////////////////////////////////////////////////// - void fireProcessStarted( const std::string& identifier ); + void fireProcessStarted( const std::string identifier ); ////////////////////////////////////////////////////////////////////// /** Notify listeners that a given process has finished. @@ -249,21 +251,21 @@ protected: //----------------------------------------------------------------- * \param identifier IN: process unique identifier. * \param retValue IN: process exit code. **//////////////////////////////////////////////////////////////////// - void fireProcessFinished( const std::string& identifier, int retValue ); + void fireProcessFinished( std::string identifier, int retValue ); ////////////////////////////////////////////////////////////////////// /** Notify listeners that a given process has been killed. * * \param identifier IN: process unique identifier. **//////////////////////////////////////////////////////////////////// - void fireProcessKilled( const std::string& identifier ); + void fireProcessKilled( std::string identifier ); ////////////////////////////////////////////////////////////////////// /** Notify listeners that a given process has failed to start. * * \param identifier IN: process unique identifier. **//////////////////////////////////////////////////////////////////// - void fireProcessFailed( const std::string& identifier ); + void fireProcessFailed( std::string identifier ); // DATA MEMBERS ////////////////////////////////////////////////////////// diff --git a/include/SPELL_PRC/SPELLprocessStatus.H b/include/SPELL_PRC/SPELLprocessStatus.H index 8b61681..16923fd 100644 --- a/include/SPELL_PRC/SPELLprocessStatus.H +++ b/include/SPELL_PRC/SPELLprocessStatus.H @@ -5,7 +5,7 @@ // DESCRIPTION: Process status codes // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_PRD/SPELLlibrary.H b/include/SPELL_PRD/SPELLlibrary.H index 0fe2a2f..ecbd6f4 100644 --- a/include/SPELL_PRD/SPELLlibrary.H +++ b/include/SPELL_PRD/SPELLlibrary.H @@ -5,7 +5,7 @@ // DESCRIPTION: Library model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -120,7 +120,7 @@ protected: //----------------------------------------------------------------- * * \param path IN: the path to the procedure file. **//////////////////////////////////////////////////////////////////// - void parseFile( const std::string& path ); + void parseFile( const std::string& path ) {}; // DATA MEMBERS ////////////////////////////////////////////////////////// diff --git a/include/SPELL_PRD/SPELLprocedure.H b/include/SPELL_PRD/SPELLprocedure.H index 3e78a9d..809dbd5 100644 --- a/include/SPELL_PRD/SPELLprocedure.H +++ b/include/SPELL_PRD/SPELLprocedure.H @@ -5,7 +5,7 @@ // DESCRIPTION: Procedure model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -106,15 +106,6 @@ public: //-------------------------------------------------------------------- return m_name; }; - ////////////////////////////////////////////////////////////////////// - /** Obtain the procedure file MD5 checksum. - * - * \returns The checksum. - **//////////////////////////////////////////////////////////////////// - const std::string getMD5() const { - return m_md5; - }; - ////////////////////////////////////////////////////////////////////// /** Obtain the procedure file name and path. * @@ -210,11 +201,6 @@ protected: //----------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// const bool isPropertyContinued( const std::string& line ); - ////////////////////////////////////////////////////////////////////// - /** Calculate de MD5 checksum of the procedure file. - **//////////////////////////////////////////////////////////////////// - std::string obtainChecksum( const std::string& filename ); - // DATA MEMBERS ////////////////////////////////////////////////////////// private: //------------------------------------------------------------------- @@ -229,8 +215,6 @@ private: //------------------------------------------------------------------- std::string m_procId; /** Procedure name */ std::string m_name; - /** MD5 checksum */ - std::string m_md5; /** Procedure file */ std::string m_file; /** Procedure source code */ diff --git a/include/SPELL_PRD/SPELLprocedureManager.H b/include/SPELL_PRD/SPELLprocedureManager.H index b66c66b..e9f431d 100644 --- a/include/SPELL_PRD/SPELLprocedureManager.H +++ b/include/SPELL_PRD/SPELLprocedureManager.H @@ -5,7 +5,7 @@ // DESCRIPTION: Procedure manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -30,6 +30,7 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // System includes --------------------------------------------------------- // Local includes ---------------------------------------------------------- +#include "SPELL_PRD/SPELLpyService.H" #include "SPELL_PRD/SPELLprocedure.H" #include "SPELL_PRD/SPELLlibrary.H" // Project includes -------------------------------------------------------- @@ -38,11 +39,9 @@ /** \addtogroup SPELL_PRD */ /*@{*/ - // FORWARD REFERENCES ////////////////////////////////////////////////////// // TYPES /////////////////////////////////////////////////////////////////// // DEFINES ///////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////////////// /** ** \brief Procedure manager. @@ -54,175 +53,208 @@ ** Used by the executor when loading subprocedures, and to check if a ** given file is on the procedure path. ** -**//////////////////////////////////////////////////////////////////////////// + **//////////////////////////////////////////////////////////////////////////// + +const std::string noInstanceId( const std::string& procId ); + + class SPELLprocedureManager { -public: //-------------------------------------------------------------------- +public: + //-------------------------------------------------------------------- // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// // TYPES ///////////////////////////////////////////////////////////////// - /** Shorcut for procedure lists */ - typedef std::list ProcList; + /** Shorcut for procedure lists */ + typedef std::list ProcList; // LIFECYCLE ///////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - /** Destructor - **//////////////////////////////////////////////////////////////////// - ~SPELLprocedureManager(); + ////////////////////////////////////////////////////////////////////// + /** Destructor + **//////////////////////////////////////////////////////////////////// + ~SPELLprocedureManager(); // STATIC //////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - /** Obtain the singleton instance. - * - * \return The singleton instance - **//////////////////////////////////////////////////////////////////// - static SPELLprocedureManager& instance(); + ////////////////////////////////////////////////////////////////////// + /** Obtain the singleton instance. + * + * \return The singleton instance + **//////////////////////////////////////////////////////////////////// + static SPELLprocedureManager& instance(); // METHODS /////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - /** Setup the procedure manager. - * - * \param ctxName IN: current context name. - **//////////////////////////////////////////////////////////////////// - void setup( const std::string& ctxName ); - - ////////////////////////////////////////////////////////////////////// - /** Obtain the configured library path. - * - * \returns The library path. - **//////////////////////////////////////////////////////////////////// - const std::string getLibPath() const; - - ////////////////////////////////////////////////////////////////////// - /** Obtain the configured procedure path. - * - * \returns The library path. - **//////////////////////////////////////////////////////////////////// - const std::string getProcPath() const; - - ////////////////////////////////////////////////////////////////////// - /** Obtain the filename corresponding to the given procedure. - * - * \param procId IN: the procedure identifier. - * - * \returns The file name. - **//////////////////////////////////////////////////////////////////// - const std::string getProcFile( const std::string& procId ); - - ////////////////////////////////////////////////////////////////////// - /** Obtain the name corresponding to the given procedure. - * - * \param procId IN: the procedure identifier. - * - * \returns The procedure name. - **//////////////////////////////////////////////////////////////////// - const std::string getProcName( const std::string& procId ); - - ////////////////////////////////////////////////////////////////////// - /** Obtain the identifier corresponding to the given procedure file. - * - * \param filename IN: the procedure file. - * - * \returns The identifier. - **//////////////////////////////////////////////////////////////////// - const std::string getProcId( const std::string& filename ); - - ////////////////////////////////////////////////////////////////////// - /** Get the list of available procedures. - * - * \returns The list of procedure identifiers. - **//////////////////////////////////////////////////////////////////// - const ProcList getProcList(); - - ////////////////////////////////////////////////////////////////////// - /** Obtain the source code corresponding to the given procedure. - * - * \param procId IN: the procedure identifier. - * - * \returns The source code. - **//////////////////////////////////////////////////////////////////// - SPELLprocedureSourceCode getSourceCode( const std::string& procId ); - - ////////////////////////////////////////////////////////////////////// - /** Obtain the list of procedure properties - **//////////////////////////////////////////////////////////////////// - SPELLprocedure::PropertyKeys getPropertyKeys( const std::string& procId ); - - ////////////////////////////////////////////////////////////////////// - /** Obtain the value of a procedure property - **//////////////////////////////////////////////////////////////////// - const std::string getProperty( const std::string& procId, const std::string& key ); - - ////////////////////////////////////////////////////////////////////// - /** Refresh and reload the list of available procedures. - **//////////////////////////////////////////////////////////////////// - void refresh(); - - ////////////////////////////////////////////////////////////////////// - /** Get a procedure model - **//////////////////////////////////////////////////////////////////// - SPELLprocedure& getProcedure( const std::string& procId ); + ////////////////////////////////////////////////////////////////////// + /** Setup the procedure manager. + * + * \param ctxName IN: current context name. + **//////////////////////////////////////////////////////////////////// + void setup(const std::string& ctxName); + + ////////////////////////////////////////////////////////////////////// + /** Obtain the configured library path. + * + * \returns The library path. + **//////////////////////////////////////////////////////////////////// + const std::string getLibPath() const; + + ////////////////////////////////////////////////////////////////////// + /** Obtain the configured procedure path. + * + * \returns The library path. + **//////////////////////////////////////////////////////////////////// + const std::string getProcPath() const; + + ////////////////////////////////////////////////////////////////////// + /** Obtain the filename corresponding to the given procedure. + * + * \param procId IN: the procedure identifier. + * + * \returns The file name. + **//////////////////////////////////////////////////////////////////// + const std::string getProcFile(const std::string& procId); + + ////////////////////////////////////////////////////////////////////// + /** Obtain the name corresponding to the given procedure. + * + * \param procId IN: the procedure identifier. + * + * \returns The procedure name. + **//////////////////////////////////////////////////////////////////// + const std::string getProcName(const std::string& procId); + + ////////////////////////////////////////////////////////////////////// + /** Obtain the identifier corresponding to the given procedure file. + * + * \param filename IN: the procedure file. + * + * \returns The identifier. + **//////////////////////////////////////////////////////////////////// + const std::string getProcId(const std::string& filename); + + ////////////////////////////////////////////////////////////////////// + /** Get the list of available procedures. + * + * \returns The list of procedure identifiers. + **//////////////////////////////////////////////////////////////////// + const ProcList getProcList(); + ////////////////////////////////////////////////////////////////////// + /** Get the list of available services. + * + * \returns The list of service identifiers. + **//////////////////////////////////////////////////////////////////// + const ProcList getServiceList(); + + ////////////////////////////////////////////////////////////////////// + /** Obtain the source code corresponding to the given procedure. + * + * \param procId IN: the procedure identifier. + * + * \returns The source code. + **//////////////////////////////////////////////////////////////////// + SPELLprocedureSourceCode getSourceCode(const std::string& procId); + + ////////////////////////////////////////////////////////////////////// + /** Obtain the list of procedure properties + **//////////////////////////////////////////////////////////////////// + SPELLprocedure::PropertyKeys getPropertyKeys(const std::string& procId); + + ////////////////////////////////////////////////////////////////////// + /** Obtain the value of a procedure property + **//////////////////////////////////////////////////////////////////// + const std::string getProperty(const std::string& procId, const std::string& key); + + ////////////////////////////////////////////////////////////////////// + /** Refresh and reload the list of available procedures. + **//////////////////////////////////////////////////////////////////// + void refresh(); + + ////////////////////////////////////////////////////////////////////// + /** Get a procedure model + **//////////////////////////////////////////////////////////////////// + SPELLprocedure& getProcedure(const std::string& procId); + + ////////////////////////////////////////////////////////////////////// + /** Get a service model + **//////////////////////////////////////////////////////////////////// + SPELLpyService& getService(const std::string& serviceId); + + ////////////////////////////////////////////////////////////////////// + /** Callback for registering a SPELL Service + **//////////////////////////////////////////////////////////////////// + void registerServiceHandler(PyObject* serviceImpl); // DATA MEMBERS ////////////////////////////////////////////////////////// -protected: //----------------------------------------------------------------- +protected: + //----------------------------------------------------------------- // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// // TYPES ///////////////////////////////////////////////////////////////// - /** Shortcut for procedure id-name map */ - typedef std::map ProcMap; - /** Shortcut for procedure id-model map */ - typedef std::map ProcModels; - /** Shortcut for library id-model map */ - typedef std::map LibModels; + /** Shortcut for procedure id-name map */ + typedef std::map ProcMap; + /** Shortcut for procedure id-model map */ + typedef std::map ProcModels; + typedef std::map ServiceModelMap; + /** Shortcut for library id-model map */ + typedef std::map LibModels; // LIFECYCLE ///////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - /** Constructor. - **//////////////////////////////////////////////////////////////////// - SPELLprocedureManager(); + ////////////////////////////////////////////////////////////////////// + /** Constructor. + **//////////////////////////////////////////////////////////////////// + SPELLprocedureManager(); // METHODS /////////////////////////////////////////////////////////////// - ////////////////////////////////////////////////////////////////////// - /** Resolve procedure paths - **//////////////////////////////////////////////////////////////////// - void resolvePaths(); - - ////////////////////////////////////////////////////////////////////// - /** Find all procedures in the given search path. Include only those - * procedures whose associated spacecraft matches the given one. - * - * \param basePath IN: search path - * \param sc IN: spacecraft id. - **//////////////////////////////////////////////////////////////////// - void findProcedures( const std::string& basePath, const std::string& sc ); - - ////////////////////////////////////////////////////////////////////// - /** Find all libraries available in UserLib location path. - * - * \param basePath IN: the path of UserLib location. - **//////////////////////////////////////////////////////////////////// - void findLibraries( const std::string& basePath ); - - ////////////////////////////////////////////////////////////////////// - /** Remove the instance number from the given procedure identifier. - * - * \param procId IN: the identifier to process. - * - * \returns The procedure identifier without instance information. - **//////////////////////////////////////////////////////////////////// - const std::string noInstanceId( const std::string& procId ); + ////////////////////////////////////////////////////////////////////// + /** Resolve procedure paths + **//////////////////////////////////////////////////////////////////// + void resolvePaths(); + + ////////////////////////////////////////////////////////////////////// + /** Find all procedures in the given search path. Include only those + * procedures whose associated spacecraft matches the given one. + * + * \param basePath IN: search path + * \param sc IN: spacecraft id. + **//////////////////////////////////////////////////////////////////// + void findProcedures(const std::string& basePath, const std::string& sc); + + ////////////////////////////////////////////////////////////////////// + /** Find all Spell Services in the given search path. Include only those + * procedures whose associated spacecraft matches the given one. + * + * \param basePath IN: search path + * \param sc IN: spacecraft id. + **//////////////////////////////////////////////////////////////////// + void findServices(const std::string& basePath, const std::string& sc); + + ////////////////////////////////////////////////////////////////////// + /** Find all libraries available in UserLib location path. + * + * \param basePath IN: the path of UserLib location. + **//////////////////////////////////////////////////////////////////// + void findLibraries(const std::string& basePath); + + ////////////////////////////////////////////////////////////////////// + /** Remove the instance number from the given procedure identifier. + * + * \param procId IN: the identifier to process. + * + * \returns The procedure identifier without instance information. + **//////////////////////////////////////////////////////////////////// + //const std::string noInstanceId(const std::string& procId); // DATA MEMBERS ////////////////////////////////////////////////////////// -private: //------------------------------------------------------------------- +private: + //------------------------------------------------------------------- // EXCEPTIONS //////////////////////////////////////////////////////////// // ENUMS ///////////////////////////////////////////////////////////////// @@ -230,26 +262,30 @@ private: //------------------------------------------------------------------- // LIFECYCLE ///////////////////////////////////////////////////////////// // METHODS /////////////////////////////////////////////////////////////// // DATA MEMBERS /////////////////////////////////////////////////////////// - /** The current context name */ - std::string m_ctxName; - /** The configured procedure search path */ - std::string m_procPath; - /** The configured library path */ - std::string m_libPath; - /** Map of identifiers to names */ - ProcMap m_idToName; - /** Map of names to identifiers */ - ProcMap m_nameToId; - /** Map of filenames to identifiers */ - ProcMap m_filenameToId; - /** Map of identifiers to filenames */ - ProcMap m_idToFilename; - /** Map of identifiers and procedure models */ - ProcModels m_procModels; - /** Map of identifiers and library models */ - LibModels m_libModels; - /** Synchronization lock */ - SPELLmutex m_lock; + /** The current context name */ + std::string m_ctxName; + /** The configured procedure search path */ + std::string m_procPath; + /** The configured library path */ + std::string m_libPath; + /** The configured service path */ + std::string m_servicePath; + /** Map of identifiers to names */ + ProcMap m_idToName; + /** Map of names to identifiers */ + ProcMap m_nameToId; + /** Map of filenames to identifiers */ + ProcMap m_filenameToId; + /** Map of identifiers to filenames */ + ProcMap m_idToFilename; + /** Map of identifiers and procedure models */ + ProcModels m_procModels; + /** Map of identifiers and Spell service models */ + ServiceModelMap m_serviceModels; + /** Map of identifiers and library models */ + LibModels m_libModels; + /** Synchronization lock */ + SPELLmutex m_lock; }; /*@}*/ diff --git a/include/SPELL_PRD/SPELLprocedureSourceCode.H b/include/SPELL_PRD/SPELLprocedureSourceCode.H index 8375c09..0d5a250 100644 --- a/include/SPELL_PRD/SPELLprocedureSourceCode.H +++ b/include/SPELL_PRD/SPELLprocedureSourceCode.H @@ -5,7 +5,7 @@ // DESCRIPTION: Procedure source code model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -64,7 +64,6 @@ public: //-------------------------------------------------------------------- SPELLprocedureSourceCode( const std::string filename ) { m_filename = filename; - m_source = ""; m_sourceLines.clear(); } @@ -80,23 +79,12 @@ public: //-------------------------------------------------------------------- return m_filename; } ; - ////////////////////////////////////////////////////////////////////// - /** Obtain the procedure source code. - * - * \returns The procedure source code. - **//////////////////////////////////////////////////////////////////// - const std::string getSourceCode() const { - return m_source; - }; - ////////////////////////////////////////////////////////////////////// /** Obtain the procedure source code lines. * * \returns The procedure source code lines. **//////////////////////////////////////////////////////////////////// - const std::vector getSourceCodeLines() const { - return m_sourceLines; - }; + const std::vector getSourceCodeLines(); // DATA MEMBERS ////////////////////////////////////////////////////////// @@ -119,7 +107,6 @@ protected: //----------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void addSourceCodeLine( const std::string& sourceLine ) { - m_source += sourceLine + "\n"; m_sourceLines.push_back(sourceLine); }; @@ -128,7 +115,6 @@ protected: //----------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void clear() { - m_source = ""; m_sourceLines.clear(); }; @@ -142,8 +128,6 @@ private: //------------------------------------------------------------------- // DATA MEMBERS ////////////////////////////////////////////////////////// /** Procedure file name */ std::string m_filename; - /** Procedure source code */ - std::string m_source; /** Procedure source code splitted in lines */ std::vector m_sourceLines; }; diff --git a/include/SPELL_PRD/SPELLpyService.H b/include/SPELL_PRD/SPELLpyService.H new file mode 100644 index 0000000..9e2da78 --- /dev/null +++ b/include/SPELL_PRD/SPELLpyService.H @@ -0,0 +1,160 @@ +// ################################################################################ +// FILE : SPELLservice.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Service model +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_PY_SERVICE_H__ +#define __SPELL_PY_SERVICE_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" +#include "SPELL_PRD/SPELLprocedureSourceCode.H" +// Local includes ---------------------------------------------------------- + +// Project includes -------------------------------------------------------- + +/** \defgroup SPELL_PRD Service management (SPELL_PRD) + * + * This module provides mechanisms for reading procedure files and libraries, + * getting their source code and properties, etc. + * + * */ +/*@{*/ + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +// TYPES /////////////////////////////////////////////////////////////////// +// DEFINES ///////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////// +/** + ** \brief Model of a spell service. + ** + ** \par Description and usage: + ** + ** Used by the procedure manager to keep track of all available + ** services and their related information. + ** + ** The SPELLprocedureManager creates one model per service found in + ** the service search path. + ** + **//////////////////////////////////////////////////////////////////////////// +class SPELLpyService +{ +public: + //-------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + + // LIFECYCLE ///////////////////////////////////////////////////////////// + + ////////////////////////////////////////////////////////////////////// + /** Constructor + * + * \param procPath IN: procedure path + * \param file IN: file name. + **//////////////////////////////////////////////////////////////////// + SPELLpyService(PyObject* serviceImpl); + + ////////////////////////////////////////////////////////////////////// + /** Destructor + **//////////////////////////////////////////////////////////////////// + ~SPELLpyService(); + + // STATIC //////////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + std::string getName() const + { + return m_name; + } + + std::string getBasePath() const + { + return m_basePath; + } + std::string getMainServiceFile() const + { + return m_mainServiceFile; + } + + void setBasePath(std::string basePath) + { + m_basePath = basePath; + } + + bool useCommandOverride() const; + + std::string getCommandOverride(int startTimeout, int loginTimeout, std::string config, std::string contextName, int ipcPort, std::string instanceId, + std::string warmstartFile) const; + + void startWatchDog(void (*callback)(std::string&)); + + void start(); + + PyObject* getStartFunction() const; + // DATA MEMBERS ////////////////////////////////////////////////////////// + + PyObject* getService() const{ + Py_INCREF(m_service); + return m_service; + } + + ////////////////////////////////////////////////////////////////////// + /** Obtain the procedure source code. + * + * \returns The procedure source code. + **//////////////////////////////////////////////////////////////////// + SPELLprocedureSourceCode getSourceCode() const { + return m_source; + }; +protected: + //----------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + + // DATA MEMBERS ////////////////////////////////////////////////////////// + +private: + //------------------------------------------------------------------- + + // EXCEPTIONS //////////////////////////////////////////////////////////// + // ENUMS ///////////////////////////////////////////////////////////////// + // TYPES ///////////////////////////////////////////////////////////////// + // LIFECYCLE ///////////////////////////////////////////////////////////// + // METHODS /////////////////////////////////////////////////////////////// + // DATA MEMBERS ////////////////////////////////////////////////////////// + PyObject* m_service; + SPELLprocedureSourceCode m_source; + std::string m_name; + std::string m_basePath; + std::string m_mainServiceFile; +}; + +/*@}*/ +#endif diff --git a/include/SPELL_PRD/SPELLservicePython.H b/include/SPELL_PRD/SPELLservicePython.H new file mode 100644 index 0000000..f3379e7 --- /dev/null +++ b/include/SPELL_PRD/SPELLservicePython.H @@ -0,0 +1,59 @@ +// ################################################################################ +// FILE : SPELLSpellService.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the SpellService mechanism +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_SpellPyService_H__ +#define __SPELL_SpellPyService_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +// Project includes -------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" + +/** \addtogroup SPELL_CTX */ +/*@{*/ + + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +// TYPES /////////////////////////////////////////////////////////////////// + +/** Python binding for the SpellService object */ +typedef struct PySpellServiceObject_ +{ + PyObject_HEAD; +} +PySpellServiceObject; + + +/** Install the SpellService binding on the Python layer */ +void SpellService_Install(); + +// DEFINES ///////////////////////////////////////////////////////////////// + + + +/*@}*/ +#endif diff --git a/include/SPELL_PRD/SPELLserviceWatchdogPython.H b/include/SPELL_PRD/SPELLserviceWatchdogPython.H new file mode 100644 index 0000000..0bcecfb --- /dev/null +++ b/include/SPELL_PRD/SPELLserviceWatchdogPython.H @@ -0,0 +1,57 @@ +// ################################################################################ +// FILE : SPELLSpellService.H +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the SpellService mechanism +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +#ifndef __SPELL_SpellPyServiceWatchdog_H__ +#define __SPELL_SpellPyServiceWatchdog_H__ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +// Project includes -------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" + +/** \addtogroup SPELL_CTX */ +/*@{*/ + + +// FORWARD REFERENCES ////////////////////////////////////////////////////// +// TYPES /////////////////////////////////////////////////////////////////// + +/** Python binding for the SpellService object */ +typedef struct PySpellServiceWatchdogObject_ +{ + PyObject_HEAD; + PyObject *callback; +} +PySpellServiceWatchdogObject; + + +// DEFINES ///////////////////////////////////////////////////////////////// +PyObject* SpellServiceWatchdog_Create(void (*callback)(std::string&)); + + +/*@}*/ +#endif diff --git a/include/SPELL_SDB/SPELLdatabase.H b/include/SPELL_SDB/SPELLdatabase.H index d142567..8ccfd60 100644 --- a/include/SPELL_SDB/SPELLdatabase.H +++ b/include/SPELL_SDB/SPELLdatabase.H @@ -5,7 +5,7 @@ // DESCRIPTION: Base class of SPELL databases // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SDB/SPELLdatabaseFactory.H b/include/SPELL_SDB/SPELLdatabaseFactory.H index cf0109e..08aa558 100644 --- a/include/SPELL_SDB/SPELLdatabaseFactory.H +++ b/include/SPELL_SDB/SPELLdatabaseFactory.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL database factory // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SDB/SPELLdatabaseFile.H b/include/SPELL_SDB/SPELLdatabaseFile.H index 77e0dfd..8ab6e82 100644 --- a/include/SPELL_SDB/SPELLdatabaseFile.H +++ b/include/SPELL_SDB/SPELLdatabaseFile.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL database based on local files // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SDB/SPELLdatabaseFileSPB.H b/include/SPELL_SDB/SPELLdatabaseFileSPB.H index 94147e8..4b2e6b3 100644 --- a/include/SPELL_SDB/SPELLdatabaseFileSPB.H +++ b/include/SPELL_SDB/SPELLdatabaseFileSPB.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL database based on SPB local files // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SDB/SPELLpyDatabaseObject.H b/include/SPELL_SDB/SPELLpyDatabaseObject.H index 03daf22..f3f2765 100644 --- a/include/SPELL_SDB/SPELLpyDatabaseObject.H +++ b/include/SPELL_SDB/SPELLpyDatabaseObject.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL database Python extension // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -218,8 +218,7 @@ PyObject* SPELLpyDatabase_IterLen( PyDatabaseIteratorObject* iterator ); #define DATABASE_DEFINITION(__type__,__name__,__doc__) \ static PyTypeObject __type__ = \ { \ - PyObject_HEAD_INIT(NULL) /* basic object head */ \ - 0, /* var objects */ \ + PyVarObject_HEAD_INIT(NULL,0) /* basic object head */ \ __name__, /* type name */ \ sizeof(PyDatabaseObject), /* type size */ \ 0, /* item size */ \ @@ -278,7 +277,7 @@ PyObject* SPELLpyDatabase_IterLen( PyDatabaseIteratorObject* iterator ); err += std::string(__type__.tp_name); \ PyErr_SetString( PyExc_RuntimeError, err.c_str()); \ std::cerr << "ERROR: " << err << std::endl; \ - return; \ + return MOD_ERROR_VAL; \ } /** Convenience macro to load a new type into the given module */ diff --git a/include/SPELL_SYN/SPELLcondition.H b/include/SPELL_SYN/SPELLcondition.H index d743eaa..6f3b592 100644 --- a/include/SPELL_SYN/SPELLcondition.H +++ b/include/SPELL_SYN/SPELLcondition.H @@ -5,7 +5,7 @@ // DESCRIPTION: Condition mechanism wrapper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLconditionFactory.H b/include/SPELL_SYN/SPELLconditionFactory.H index 126e797..424fd6a 100644 --- a/include/SPELL_SYN/SPELLconditionFactory.H +++ b/include/SPELL_SYN/SPELLconditionFactory.H @@ -5,7 +5,7 @@ // DESCRIPTION: Factory of conditions // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLconditionIF.H b/include/SPELL_SYN/SPELLconditionIF.H index 9ed1ba5..9152569 100644 --- a/include/SPELL_SYN/SPELLconditionIF.H +++ b/include/SPELL_SYN/SPELLconditionIF.H @@ -5,7 +5,7 @@ // DESCRIPTION: Condition interface definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLconditionPOSIX.H b/include/SPELL_SYN/SPELLconditionPOSIX.H index 0a58d31..963bb44 100644 --- a/include/SPELL_SYN/SPELLconditionPOSIX.H +++ b/include/SPELL_SYN/SPELLconditionPOSIX.H @@ -5,7 +5,7 @@ // DESCRIPTION: POSIX implementation of condition mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLevent.H b/include/SPELL_SYN/SPELLevent.H index cae3c52..bba34a5 100644 --- a/include/SPELL_SYN/SPELLevent.H +++ b/include/SPELL_SYN/SPELLevent.H @@ -5,7 +5,7 @@ // DESCRIPTION: Event mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLmonitor.H b/include/SPELL_SYN/SPELLmonitor.H index 428e9eb..6aacfec 100644 --- a/include/SPELL_SYN/SPELLmonitor.H +++ b/include/SPELL_SYN/SPELLmonitor.H @@ -5,7 +5,7 @@ // DESCRIPTION: Monitor mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLmutex.H b/include/SPELL_SYN/SPELLmutex.H index 50ef616..23ce473 100644 --- a/include/SPELL_SYN/SPELLmutex.H +++ b/include/SPELL_SYN/SPELLmutex.H @@ -5,7 +5,7 @@ // DESCRIPTION: Mutex mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLmutexFactory.H b/include/SPELL_SYN/SPELLmutexFactory.H index 136a6aa..762a0c7 100644 --- a/include/SPELL_SYN/SPELLmutexFactory.H +++ b/include/SPELL_SYN/SPELLmutexFactory.H @@ -5,7 +5,7 @@ // DESCRIPTION: Factory for mutexes // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLmutexIF.H b/include/SPELL_SYN/SPELLmutexIF.H index 88d5122..0a4014a 100644 --- a/include/SPELL_SYN/SPELLmutexIF.H +++ b/include/SPELL_SYN/SPELLmutexIF.H @@ -5,7 +5,7 @@ // DESCRIPTION: Mutex interface definition // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLmutexPOSIX.H b/include/SPELL_SYN/SPELLmutexPOSIX.H index 1661028..7690dcc 100644 --- a/include/SPELL_SYN/SPELLmutexPOSIX.H +++ b/include/SPELL_SYN/SPELLmutexPOSIX.H @@ -5,7 +5,7 @@ // DESCRIPTION: POSIX implementation for mutex // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLqueue.H b/include/SPELL_SYN/SPELLqueue.H index f7facce..6cf7485 100644 --- a/include/SPELL_SYN/SPELLqueue.H +++ b/include/SPELL_SYN/SPELLqueue.H @@ -5,7 +5,7 @@ // DESCRIPTION: Queue mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLsyncError.H b/include/SPELL_SYN/SPELLsyncError.H index cc9bbdc..f9cb76b 100644 --- a/include/SPELL_SYN/SPELLsyncError.H +++ b/include/SPELL_SYN/SPELLsyncError.H @@ -5,7 +5,7 @@ // DESCRIPTION: Synchronization error // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLthread.H b/include/SPELL_SYN/SPELLthread.H index d6dd68e..63aa5d3 100644 --- a/include/SPELL_SYN/SPELLthread.H +++ b/include/SPELL_SYN/SPELLthread.H @@ -5,7 +5,7 @@ // DESCRIPTION: Thread mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLthreadPool.H b/include/SPELL_SYN/SPELLthreadPool.H index 933f377..bb21ddc 100644 --- a/include/SPELL_SYN/SPELLthreadPool.H +++ b/include/SPELL_SYN/SPELLthreadPool.H @@ -5,7 +5,7 @@ // DESCRIPTION: Thread pool mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLthreadWork.H b/include/SPELL_SYN/SPELLthreadWork.H index f50e641..1d57c2b 100644 --- a/include/SPELL_SYN/SPELLthreadWork.H +++ b/include/SPELL_SYN/SPELLthreadWork.H @@ -5,7 +5,7 @@ // DESCRIPTION: Thread worker for thread pools // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLthreadWorker.H b/include/SPELL_SYN/SPELLthreadWorker.H index af6c14c..d60e2c5 100644 --- a/include/SPELL_SYN/SPELLthreadWorker.H +++ b/include/SPELL_SYN/SPELLthreadWorker.H @@ -5,7 +5,7 @@ // DESCRIPTION: Thread worker for thread pools // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_SYN/SPELLtimer.H b/include/SPELL_SYN/SPELLtimer.H index e7c0e05..975b7ac 100644 --- a/include/SPELL_SYN/SPELLtimer.H +++ b/include/SPELL_SYN/SPELLtimer.H @@ -5,7 +5,7 @@ // DESCRIPTION: Timer mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_UTIL/SPELLbase.H b/include/SPELL_UTIL/SPELLbase.H index 8e8a32a..3b2c062 100644 --- a/include/SPELL_UTIL/SPELLbase.H +++ b/include/SPELL_UTIL/SPELLbase.H @@ -5,7 +5,7 @@ // DESCRIPTION: Base includes for SPELL framework // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -58,6 +58,7 @@ #include #include #include +#include #include "log4cplus/appender.h" #include "log4cplus/fileappender.h" #include "log4cplus/layout.h" @@ -89,5 +90,50 @@ // DEFINES ///////////////////////////////////////////////////////////////// #define BYTE unsigned char +//Python 3 Compatibility +#if PY_MAJOR_VERSION >= 3 +#define PyString_AsString PyUnicode_AsUTF8 +#define PyString_CheckExact PyUnicode_CheckExact +#define PyString_FromString PyUnicode_FromString +#define PyString_Check PyUnicode_Check +#define PyString_Size PyUnicode_GetLength +#define PyString_AsStringAndSize PyBytes_AsStringAndSize +#define CHAR wchar_t +#define PyInt_Check PyLong_Check +#define PyInt_AsLong PyLong_AsLong +#define PyInt_FromSize_t PyLong_FromSize_t +#define PyInt_FromLong PyLong_FromLong +#define PyClass_Check(value) PyObject_IsInstance(value, (PyObject *)&PyType_Type) +#define PyInstance_Check(value) PyObject_IsInstance(value, (PyObject *)&PyBaseObject_Type) +#define PyInt_CheckExact PyLong_CheckExact +#define PyInt_AS_LONG PyLong_AS_LONG + +#define PyFloat_FromString(floatObj,pend) PyFloat_FromString(floatObj) + +#define PyEval_EvalCode(code,globals,locals) PyEval_EvalCode((PyObject*)code,globals,locals) + +#define MOD_ERROR_VAL NULL +#define MOD_SUCCESS_VAL(val) val +#define MOD_NAME(name) #name"_v3" +#define MOD_INIT(name) PyMODINIT_FUNC PyInit_##name##_v3(void) +#define MOD_DEF(ob, name, doc, methods) \ + static struct PyModuleDef moduledef = { \ + PyModuleDef_HEAD_INIT, name, doc, -1, methods, }; \ + ob = PyModule_Create(&moduledef); \ + if (ob == NULL) \ + return NULL; +#define BUILTINS "builtins" +#else +#define BUILTINS "__builtin__" +#define CHAR char +#define MOD_ERROR_VAL +#define MOD_SUCCESS_VAL(val) +#define MOD_NAME(name) #name"_v2" +#define MOD_INIT(name) PyMODINIT_FUNC init##name##_v2(void) +#define MOD_DEF(ob, name, doc, methods) \ + ob = Py_InitModule3(name, methods, doc); +#define _Py_CODEUNIT unsigned char +#endif + /*@}*/ #endif diff --git a/include/SPELL_UTIL/SPELLcompression.H b/include/SPELL_UTIL/SPELLcompression.H index 17bd5d6..5d03267 100644 --- a/include/SPELL_UTIL/SPELLcompression.H +++ b/include/SPELL_UTIL/SPELLcompression.H @@ -5,7 +5,7 @@ // DESCRIPTION: General utilities // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_UTIL/SPELLerror.H b/include/SPELL_UTIL/SPELLerror.H index e4b9eb9..58e7014 100644 --- a/include/SPELL_UTIL/SPELLerror.H +++ b/include/SPELL_UTIL/SPELLerror.H @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL core exceptions // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/include/SPELL_UTIL/SPELLlog.H b/include/SPELL_UTIL/SPELLlog.H index eb9a582..9a94602 100644 --- a/include/SPELL_UTIL/SPELLlog.H +++ b/include/SPELL_UTIL/SPELLlog.H @@ -5,7 +5,7 @@ // DESCRIPTION: Executor logging mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -30,6 +30,7 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // Local includes ---------------------------------------------------------- // Project includes -------------------------------------------------------- +#include "SPELL_UTIL/SPELLutils.H" #include "SPELL_SYN/SPELLmutex.H" // System includes --------------------------------------------------------- @@ -202,19 +203,19 @@ static const std::string LOG_LEVEL_LBL[] = ** \brief Log an information message. ** *//////////////////////////////////////////////////////////////////////////// -#define LOG_INFO(x) log4cplus::Logger::getRoot().log(log4cplus::INFO_LOG_LEVEL, x, __FILE__, __LINE__ ) +#define LOG_INFO(x) log4cplus::Logger::getRoot().log(log4cplus::INFO_LOG_LEVEL, x, __FILE__, __LINE__, NULL ) ////////////////////////////////////////////////////////////////////////////// /** ** \brief Log a warning message. ** *//////////////////////////////////////////////////////////////////////////// -#define LOG_WARN(x) log4cplus::Logger::getRoot().log(log4cplus::WARN_LOG_LEVEL, x, __FILE__, __LINE__ ) +#define LOG_WARN(x) log4cplus::Logger::getRoot().log(log4cplus::WARN_LOG_LEVEL, x, __FILE__, __LINE__, NULL ) ////////////////////////////////////////////////////////////////////////////// /** ** \brief Log an error message. ** *//////////////////////////////////////////////////////////////////////////// -#define LOG_ERROR(x) log4cplus::Logger::getRoot().log(log4cplus::ERROR_LOG_LEVEL, x, __FILE__, __LINE__ ) +#define LOG_ERROR(x) log4cplus::Logger::getRoot().log(log4cplus::ERROR_LOG_LEVEL, x, __FILE__, __LINE__, NULL ) ////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////// @@ -242,8 +243,9 @@ static const std::string LOG_LEVEL_LBL[] = ** the \c WITH_DEBUG macro is defined. ** *//////////////////////////////////////////////////////////////////////////// + #ifdef WITH_DEBUG -#define DEBUG(x) log4cplus::Logger::getRoot().log(log4cplus::DEBUG_LOG_LEVEL, x, __FILE__, __LINE__ ) +#define DEBUG(x) log4cplus::Logger::getRoot().log(log4cplus::DEBUG_LOG_LEVEL, x, __FILE__, __LINE__ ); #else #define DEBUG(x) #endif diff --git a/include/SPELL_UTIL/SPELLpyArgs.H b/include/SPELL_UTIL/SPELLpyArgs.H index e07eb4b..a092f15 100644 --- a/include/SPELL_UTIL/SPELLpyArgs.H +++ b/include/SPELL_UTIL/SPELLpyArgs.H @@ -5,7 +5,7 @@ // DESCRIPTION: Utility for Python arguments processing // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -30,6 +30,7 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // Local includes ---------------------------------------------------------- // Project includes -------------------------------------------------------- +#include "SPELL_UTIL/SPELLutils.H" #include "SPELL_UTIL/SPELLtime.H" // System includes --------------------------------------------------------- @@ -234,6 +235,20 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// std::string getModifier_HiYel() const; + ////////////////////////////////////////////////////////////////////// + /** Obtain the value of the Select modifier. + * + * \return The modifier value. + **//////////////////////////////////////////////////////////////////// + std::string getModifier_Select() const; + + ////////////////////////////////////////////////////////////////////// + /** Obtain the value of the Type modifier for Limits. + * + * \return The modifier value. + **//////////////////////////////////////////////////////////////////// + std::string getModifier_LimitType() const; + ////////////////////////////////////////////////////////////////////// /** Obtain the value of the Message modifier. * @@ -288,6 +303,7 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// std::string getModifier_Default() const; + // DATA MEMBERS ////////////////////////////////////////////////////////// protected: //----------------------------------------------------------------- diff --git a/include/SPELL_UTIL/SPELLpythonHelper.H b/include/SPELL_UTIL/SPELLpythonHelper.H index 7bdecca..8c56fba 100644 --- a/include/SPELL_UTIL/SPELLpythonHelper.H +++ b/include/SPELL_UTIL/SPELLpythonHelper.H @@ -5,7 +5,7 @@ // DESCRIPTION: Python utilities and manipulation functions // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -118,6 +118,18 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// void importAllFrom( const std::string& package ); + ////////////////////////////////////////////////////////////////////// + /** Import all items from the given package into the builtins package. + * + * \throws SPELLcoreException if the given module cannot be imported. + * \throws SPELLcoreException if the __main__ module cannot be accessed. + * \throws SPELLcoreException if the __main__ module dictionary cannot be accessed. + * + * \param package IN: the package to import objects from. + **//////////////////////////////////////////////////////////////////// + void updateBuiltinsWith( const std::string& package ); + + ////////////////////////////////////////////////////////////////////// /** Import an user library module * @@ -127,7 +139,7 @@ public: //-------------------------------------------------------------------- * * \param package IN: the package to import objects from. **//////////////////////////////////////////////////////////////////// - void importUserLibraries( const std::string& libraryPath ); + bool importUserLibraries( const std::string& libraryPath, std::string& errors ); ////////////////////////////////////////////////////////////////////// /** Install an object in main dictionary @@ -210,7 +222,9 @@ public: //-------------------------------------------------------------------- **//////////////////////////////////////////////////////////////////// PyFrameObject* createFrame( const std::string& filename, PyCodeObject* code ); - ////////////////////////////////////////////////////////////////////// + PyFrameObject* createFrameFromFunction( const std::string& filename, PyObject* func ); + + ////////////////////////////////////////////////////////////////////// /** Call the given method of the given object, with variable arguments * * \param obj IN: object providing the desired method. diff --git a/include/SPELL_UTIL/SPELLpythonMonitors.H b/include/SPELL_UTIL/SPELLpythonMonitors.H index 5eba813..3d9add8 100644 --- a/include/SPELL_UTIL/SPELLpythonMonitors.H +++ b/include/SPELL_UTIL/SPELLpythonMonitors.H @@ -5,7 +5,7 @@ // DESCRIPTION: Python safe operation monitors // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -59,14 +59,71 @@ class SPELLsafePythonOperations public: SPELLsafePythonOperations( const std::string& where ) { - //std::cerr << "PYTHON OPERATIONS IN " << where << std::endl; + //std::cerr << "PYTHON OPERATIONS TRY-IN " << where << std::endl; m_state = SPELLpythonHelper::instance().acquireGIL(); + //std::cerr << ">>PYTHON OPERATIONS IN " << where << std::endl; m_where = where; }; - ~SPELLsafePythonOperations() + virtual ~SPELLsafePythonOperations() noexcept(false) { + PyObject* err = PyErr_Occurred(); + if (err != NULL) + { + std::cerr << std::endl << "===============================" << std::endl; + //PyErr_Print(); + PyObject* ptype; + PyObject* pvalue; + PyObject* ptraceback; + PyErr_Fetch( &ptype, &pvalue, &ptraceback ); + std::cerr << "TYPE : " << PYREPR(ptype) << std::endl; + std::cerr << "VALUE: " << PYREPR(pvalue) << std::endl; + if (ptraceback) + { + std::cerr << "Traceback: " << std::endl; + PyTracebackObject* traceback = (PyTracebackObject*) ptraceback; + while(traceback != NULL) + { + std::cerr << "at " << PYREPR(traceback->tb_frame->f_code->co_filename) << ":" << traceback->tb_lineno << std::endl; + traceback = traceback->tb_next; + } + } + std::cerr << "===============================" << std::endl << std::endl; + // Parse the exception to give better information about the error on python side + PyObject* spellException = SPELLpythonHelper::instance().getObject("spell.lib.exception", "SpellException"); + std::string excType = PYREPR(ptype); + if (PyObject_IsInstance( pvalue, spellException )) + { + PyObject* msg = PyObject_GetAttrString( pvalue, "message" ); + PyObject* rea = PyObject_GetAttrString( pvalue, "reason" ); + std::string message = ""; + std::string reason = ""; + if (msg != NULL) + { + message = PYSSTR(msg); + } + if (rea != NULL ) + { + reason = PYSSTR(rea); + } + /** \todo decide the fatal flag value */ + std::cerr << "RAISING INTERNAL DRIVER EXCEPTION" << std::endl; + PyErr_Clear(); + THROW_EXCEPTION("Driver error: " + message, reason, SPELL_DRIVER_ERROR ); + } + else if ( PYSTR(pvalue) == "EXECUTION ABORTED" || PYSTR(pvalue) == "EXECUTION TERMINATED" ) + { + // Do not raise exceptions if the detected exception is an aborted or finished internal exception + return; + } + else + { + std::cerr << "RAISING PYTHON ERROR EXCEPTION" << std::endl; + PyErr_Clear(); + THROW_EXCEPTION("Python error", PYSSTR(PyObject_Str(pvalue)), SPELL_ERROR_PYTHON_API ); + } + } SPELLpythonHelper::instance().releaseGIL(m_state); - //std::cerr << "PYTHON OPERATIONS OUT " << m_where << std::endl; + //std::cerr << "<. +# +################################################################################ -if WITH_LIBS SUBDIRS = SPELL_UTIL \ SPELL_SYN \ SPELL_CFG \ @@ -13,22 +31,8 @@ SUBDIRS = SPELL_UTIL \ SPELL_SDB \ SPELL_DTA \ SPELL_EXC \ - SPELL_WS - -if WITH_EXECUTOR -SUBDIRS += SPELL_CIFS -endif - -if WITH_EXECUTOR_CMD -SUBDIRS += SPELL_CIFC -endif - -if WITH_CONTEXT -SUBDIRS += SPELL_CTX -endif - -if WITH_LISTENER -SUBDIRS += SPELL_LST -endif - -endif + SPELL_WS \ + SPELL_CIFS \ + SPELL_CIFC \ + SPELL_CTX \ + SPELL_LST diff --git a/lib/SPELL_CFG/Makefile.am b/lib/SPELL_CFG/Makefile.am index 1b326a2..d8a92f2 100644 --- a/lib/SPELL_CFG/Makefile.am +++ b/lib/SPELL_CFG/Makefile.am @@ -1,37 +1,47 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_CFG.la -libSPELL_CFG_la_LDFLAGS = -version-info 3:0:0 -libSPELL_CFG_la_SOURCES = \ - src/SPELLxmlConfigReaderFactory.C \ - src/SPELLxmlConfigReaderXC.C \ - src/SPELLconfiguration.C \ - src/SPELLcontextConfig.C \ - src/SPELLdriverConfig.C \ - src/SPELLbrowsableLibMode.C - + +lib_LTLIBRARIES = libSPELL_CFG_v2.la libSPELL_CFG_v3.la + +libSPELL_CFG_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_CFG_v2_la_CDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_CFG_v2_la_SOURCES = src/SPELLxmlConfigReaderFactory.C \ + src/SPELLxmlConfigReaderXC.C \ + src/SPELLconfiguration.C \ + src/SPELLcontextConfig.C \ + src/SPELLdriverConfig.C \ + src/SPELLbrowsableLibMode.C + + +libSPELL_CFG_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_CFG_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_CFG_v3_la_SOURCES = src/SPELLxmlConfigReaderFactory.C \ + src/SPELLxmlConfigReaderXC.C \ + src/SPELLconfiguration.C \ + src/SPELLcontextConfig.C \ + src/SPELLdriverConfig.C \ + src/SPELLbrowsableLibMode.C \ No newline at end of file diff --git a/lib/SPELL_CFG/src/SPELLbrowsableLibMode.C b/lib/SPELL_CFG/src/SPELLbrowsableLibMode.C index e819ae1..4d48f74 100644 --- a/lib/SPELL_CFG/src/SPELLbrowsableLibMode.C +++ b/lib/SPELL_CFG/src/SPELLbrowsableLibMode.C @@ -5,7 +5,7 @@ // DESCRIPTION: // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -39,9 +39,6 @@ std::string s_BROWSABLELIB_MODE_STR[] = "Disabled" }; -unsigned int s_NUM_MODES = sizeof(s_BROWSABLELIB_MODE_STR) / sizeof(std::string); - - // Methods @@ -51,6 +48,7 @@ unsigned int s_NUM_MODES = sizeof(s_BROWSABLELIB_MODE_STR) / sizeof(std::string) SPELLbrowsableLibMode stringToBrowsableLibMode( const std::string& mode ) { unsigned int index = 0; + unsigned int s_NUM_MODES = sizeof(s_BROWSABLELIB_MODE_STR) / sizeof(std::string); for( index = 0; index < s_NUM_MODES; index++) { diff --git a/lib/SPELL_CFG/src/SPELLconfiguration.C b/lib/SPELL_CFG/src/SPELLconfiguration.C index 7ba8d4c..50f1fb1 100644 --- a/lib/SPELL_CFG/src/SPELLconfiguration.C +++ b/lib/SPELL_CFG/src/SPELLconfiguration.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the configuration reader // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CFG/src/SPELLcontextConfig.C b/lib/SPELL_CFG/src/SPELLcontextConfig.C index 2553e19..725e9c8 100644 --- a/lib/SPELL_CFG/src/SPELLcontextConfig.C +++ b/lib/SPELL_CFG/src/SPELLcontextConfig.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the context configuration model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -37,6 +37,7 @@ // GLOBALS ///////////////////////////////////////////////////////////////// const std::string SPELLcontextConfig::ExecutorProgram = "ExecutorProgram"; +const std::string SPELLcontextConfig::ServiceProgram = "ServiceProgram"; const std::string SPELLcontextConfig::ExecutorStartTimeout = "ExecutorStartTimeout"; const std::string SPELLcontextConfig::ExecutorLoginTimeout = "ExecutorLoginTimeout"; const std::string SPELLcontextConfig::UseDriverTime = "UseDriverTime"; @@ -57,6 +58,7 @@ SPELLcontextConfig::SPELLcontextConfig( const std::string& contextFile ) m_gcs = "unknown"; m_family = "unknown"; m_procPath = "unknown"; + m_servicePath = "unknown"; m_libPath = "unknown"; m_outputDir = ""; m_inputDir = ""; @@ -178,6 +180,18 @@ void SPELLcontextConfig::loadBasics() m_libPath = ""; } + list = m_reader->findElementsByName( XMLTags::TAG_CTX_SPATH ); + if (list.size()==1) + { + node = *(list.begin()); + m_servicePath = node->getValue(); + dealloc_list(list); + } + else + { + m_servicePath = "Services"; + } + list = m_reader->findElementsByName( XMLTags::TAG_CTX_OPATH ); if (list.size()==1) { diff --git a/lib/SPELL_CFG/src/SPELLdriverConfig.C b/lib/SPELL_CFG/src/SPELLdriverConfig.C index 69668a3..186a661 100644 --- a/lib/SPELL_CFG/src/SPELLdriverConfig.C +++ b/lib/SPELL_CFG/src/SPELLdriverConfig.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the driver configuration model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CFG/src/SPELLexecutorDefaults.C b/lib/SPELL_CFG/src/SPELLexecutorDefaults.C index 56db148..abc9e93 100644 --- a/lib/SPELL_CFG/src/SPELLexecutorDefaults.C +++ b/lib/SPELL_CFG/src/SPELLexecutorDefaults.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the context execution defaults. // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -42,14 +42,17 @@ SPELLexecutorDefaults::SPELLexecutorDefaults( const SPELLexecutorDefaults& from LOG_INFO("[CTX] Copied Executor Defaults for " + m_ctxName); m_execDelay = from.getExecDelay(); - m_promptWarningDelay = from.getpromptWarningDelay(); + m_promptWarningDelay = from.getPromptWarningDelay(); m_runInto = from.isRunInto(); m_byStep = from.isByStep(); m_browsableLib = from.getBrowsableLib(); m_maxVerbosity = from.getMaxVerbosity(); - m_forceTcConfirm = from.getForceTcConfirm(); + m_forceTcConfirm = from.isForceTcConfirm(); + m_forcePromptConfirm = from.isForcePromptConfirm(); m_saveStateMode = from.getSaveStateMode(); m_watchVariables = from.isWatchVariables(); + m_resourcesDisplay = from.isResourcesDisplay(); + m_localResourceSupport = from.isLocalResourcesSupported(); m_ctxName = from.getCtxName(); } //copy constructor diff --git a/lib/SPELL_CFG/src/SPELLxmlConfigReaderFactory.C b/lib/SPELL_CFG/src/SPELLxmlConfigReaderFactory.C index af1c71e..cd6c56c 100644 --- a/lib/SPELL_CFG/src/SPELLxmlConfigReaderFactory.C +++ b/lib/SPELL_CFG/src/SPELLxmlConfigReaderFactory.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the configuration reader factory // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CFG/src/SPELLxmlConfigReaderXC.C b/lib/SPELL_CFG/src/SPELLxmlConfigReaderXC.C index ae370df..7638235 100644 --- a/lib/SPELL_CFG/src/SPELLxmlConfigReaderXC.C +++ b/lib/SPELL_CFG/src/SPELLxmlConfigReaderXC.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the configuration reader with Xerces-C // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CIF/Makefile.am b/lib/SPELL_CIF/Makefile.am index ab595e1..1e1fa1f 100644 --- a/lib/SPELL_CIF/Makefile.am +++ b/lib/SPELL_CIF/Makefile.am @@ -1,35 +1,46 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_CIF.la -libSPELL_CIF_la_LDFLAGS = -version-info 3:0:0 -libSPELL_CIF_la_SOURCES = \ - src/SPELLcif.C \ - src/SPELLcifPython.C \ - src/SPELLcifHelper.C \ - src/SPELLasRun.C \ - src/SPELLnotifications.C + +lib_LTLIBRARIES = libSPELL_CIF_v2.la libSPELL_CIF_v3.la + +libSPELL_CIF_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_CIF_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_CIF_v2_la_SOURCES = src/SPELLcif.C \ + src/SPELLcifPython.C \ + src/SPELLcifHelper.C \ + src/SPELLasRun.C \ + src/SPELLnotifications.C + +libSPELL_CIF_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_CIF_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_CIF_v3_la_SOURCES = src/SPELLcif.C \ + src/SPELLcifPython.C \ + src/SPELLcifHelper.C \ + src/SPELLasRun.C \ + src/SPELLnotifications.C + + \ No newline at end of file diff --git a/lib/SPELL_CIF/src/SPELLasRun.C b/lib/SPELL_CIF/src/SPELLasRun.C index aa739ac..d7e5ba2 100644 --- a/lib/SPELL_CIF/src/SPELLasRun.C +++ b/lib/SPELL_CIF/src/SPELLasRun.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the As-RUN interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CIF/src/SPELLcif.C b/lib/SPELL_CIF/src/SPELLcif.C index 525a289..2468767 100644 --- a/lib/SPELL_CIF/src/SPELLcif.C +++ b/lib/SPELL_CIF/src/SPELLcif.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the CIF // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -56,8 +56,9 @@ SPELLcif::SPELLcif() m_ctxName = ""; m_ctxPort = 0; m_notificationsEnabled = true; - + m_forcePromptConfirm = false; m_finishEvent.clear(); + m_errorState = false; } //============================================================================= @@ -135,6 +136,15 @@ void SPELLcif::setVerbosity( int verbosity ) { m_verbosity = verbosity; } + +//============================================================================= +// METHOD : SPELLcif::setVerbosity +//============================================================================= +void SPELLcif::setForcePromptConfirm( bool force ) +{ + m_forcePromptConfirm = force; +} + //============================================================================= // METHOD : SPELLcif::setVerbosityFilter //============================================================================= @@ -188,6 +198,8 @@ const int SPELLcif::getVerbosityFilter() const //============================================================================= const std::string SPELLcif::getProcId() const { + if (m_procId.find("#") == std::string::npos) + return m_procId + "#0"; return m_procId; } @@ -450,24 +462,53 @@ void SPELLcif::error( const std::string& msg, unsigned int scope ) //============================================================================= // METHOD: SPELLcif::prompt //============================================================================= -std::string SPELLcif::prompt( const SPELLpromptDefinition& def ) +std::string SPELLcif::prompt( const SPELLpromptDefinition& def ,bool doPromptConfirm /* = true*/) { - // Local variables + bool canExit = true; std::string answerToShow= ""; std::string rawAnswer = ""; - - // Debug trace - DEBUG("[CIF] Prompt message"); - - // Write the prompt in the asrun - m_asRun->writePrompt( getStack(), def ); - - //Run Implementation Specifics - specificPrompt( def, rawAnswer, answerToShow ); - - // Write the answer in asrun - m_asRun->writeAnswer( getStack(), rawAnswer, def.scope ); - + // Local variables + do { + // Debug trace + canExit = true; + DEBUG("[CIF] Prompt message"); + + // Write the prompt in the asrun + m_asRun->writePrompt( getStack(), def ); + + //Run Implementation Specifics + specificPrompt( def, rawAnswer, answerToShow ); + + // Write the answer in asrun + if ( (def.typecode & LanguageConstants::PROMPT_PASSWORD)>0) + m_asRun->writeAnswer( getStack(), "***", def.scope ); + else + m_asRun->writeAnswer( getStack(), answerToShow, def.scope ); + + if (m_forcePromptConfirm && doPromptConfirm) + { + DEBUG("[CIF] Prompt Confirm message"); + SPELLpromptDefinition confirmPromptDefiniton; + confirmPromptDefiniton.message = "The answer '" + answerToShow + "' was provided for the previous Prompt.\nIs this value correct?"; + confirmPromptDefiniton.options.push_back("YES"); + confirmPromptDefiniton.options.push_back("NO"); + confirmPromptDefiniton.expected.push_back("YES"); + confirmPromptDefiniton.expected.push_back("NO"); + confirmPromptDefiniton.scope = def.scope; + confirmPromptDefiniton.typecode = 128; + + std::string confirmResult = this->prompt(confirmPromptDefiniton,false); + + if (confirmResult != "YES") + { + canExit = false; + } + else + { + canExit = true; + } + } + } while (!canExit); return answerToShow; } //std::string SPELLcif::prompt( const SPELLpromptDefinition& definition ) @@ -485,6 +526,7 @@ void SPELLcif::canClose() //============================================================================= void SPELLcif::waitClose() { + SPELLsafeThreadOperations ops("waitClose"); m_finishEvent.wait(); } @@ -560,6 +602,23 @@ void SPELLcif::notifyVariableScopeChange( const std::string& scopeName, } +//============================================================================= +// METHOD: SPELLcif::notifyResourceChange() +//============================================================================= +void SPELLcif::notifyResourceChange( const std::vector& changed ) +{ + specificNotifyResourceChange( changed ); +} + +//============================================================================= +// METHOD: SPELLcif::notifyResourceList() +//============================================================================= +void SPELLcif::notifyResourceList( const std::vector& resources ) +{ + specificNotifyResourceList( resources ); +} + + //============================================================================= // METHOD: SPELLcif::openSubprocedure //============================================================================= diff --git a/lib/SPELL_CIF/src/SPELLcifHelper.C b/lib/SPELL_CIF/src/SPELLcifHelper.C index 8437191..923d33b 100644 --- a/lib/SPELL_CIF/src/SPELLcifHelper.C +++ b/lib/SPELL_CIF/src/SPELLcifHelper.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the CIF helper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -360,6 +360,9 @@ std::string SPELLcifHelper::getResult( const std::string& result, const SPELLpro if ((def.options.size()>0)) { int optionIndex = STRI(result); + if (optionIndex < 0 || (unsigned)optionIndex >= def.options.size()){ + return ""; + } // Conversion LIST|ALPHA if ( (def.typecode & LanguageConstants::PROMPT_ALPHA)>0) { @@ -411,6 +414,7 @@ std::string SPELLcifHelper::getResult( const std::string& result, const SPELLpro } break; } + return promptResult; } @@ -477,15 +481,15 @@ std::string SPELLcifHelper::commandLinePrompt( const SPELLpromptDefinition& def, if (def.options.size()>0) { SPELLpromptDefinition::Options::const_iterator eit; - int count = 0; + unsigned int count = 0; for( eit = def.expected.begin(); eit != def.expected.end(); eit++) { if (*eit == answer ) { - if (useRaw) - { - answer = ISTR(count); - } +// if (useRaw) +// { +// answer = ISTR(count); +// } keepTrying = false; break; } @@ -498,7 +502,8 @@ std::string SPELLcifHelper::commandLinePrompt( const SPELLpromptDefinition& def, for( eit = def.expected.begin(); eit != def.expected.end(); eit++) { std::cout << " " << (*eit); - if (countob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ @@ -307,6 +314,33 @@ static PyObject* ClientIF_ResetVerbosity( PyObject* self, PyObject* args ) Py_RETURN_NONE; } +static PyObject* ClientIF_ForcePromptConfirm( PyObject* self, PyObject* args ) +{ + SPELLpyArgs pargs(args); + bool forcePromptConfirm = false; + + if (pargs.size()>0) + { + PyObject* pyForcePrompt = pargs[0]; + if (PyBool_Check(pyForcePrompt)) + { + + forcePromptConfirm = pyForcePrompt == Py_True; + } + else if(pyForcePrompt == Py_None) + { + // Nothing to do + } + else + { + THROW_SYNTAX_EXCEPTION("Syntax error on CIF::forcePromptConfirm()", "Malformed argument: " + PYREPR(args)); + } + } + SPELLexecutor::instance().getCIF().setForcePromptConfirm(forcePromptConfirm); + + Py_RETURN_NONE; +} + //============================================================================ // FUNCTION : ClientIF_Write //============================================================================ @@ -467,6 +501,10 @@ static PyObject* ClientIF_Prompt( PyObject* self, PyObject* args, PyObject* kwds if (argumentsC.hasModifier(LanguageModifiers::Timeout)) { def.timeout = argumentsC.getModifier_Timeout(); + } + else + { + def.timeout = SPELLtime(0, true); } // The prompt scope def.scope = argumentsC.getModifier_Scope(); diff --git a/lib/SPELL_CIF/src/SPELLnotifications.C b/lib/SPELL_CIF/src/SPELLnotifications.C index 927b3cb..afbcb89 100644 --- a/lib/SPELL_CIF/src/SPELLnotifications.C +++ b/lib/SPELL_CIF/src/SPELLnotifications.C @@ -5,7 +5,7 @@ // DESCRIPTION: Notification data class // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CIFC/Makefile.am b/lib/SPELL_CIFC/Makefile.am index 9a5cf32..ae2684e 100644 --- a/lib/SPELL_CIFC/Makefile.am +++ b/lib/SPELL_CIFC/Makefile.am @@ -1,34 +1,42 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_CIFC.la -libSPELL_CIFC_la_LDFLAGS = -version-info 1:0:0 -libSPELL_CIFC_la_SOURCES = \ - src/SPELLautomaticCif.C \ - src/SPELLncursesShell.C \ - src/SPELLstandaloneCif.C - + +lib_LTLIBRARIES = libSPELL_CIFC_v2.la libSPELL_CIFC_v3.la + +libSPELL_CIFC_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_CIFC_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} -lmenu + +libSPELL_CIFC_v2_la_SOURCES = src/SPELLautomaticCif.C \ + src/SPELLncursesShell.C \ + src/SPELLstandaloneCif.C + + +libSPELL_CIFC_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_CIFC_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} -lmenu + +libSPELL_CIFC_v3_la_SOURCES = src/SPELLautomaticCif.C \ + src/SPELLncursesShell.C \ + src/SPELLstandaloneCif.C + \ No newline at end of file diff --git a/lib/SPELL_CIFC/src/SPELLautomaticCif.C b/lib/SPELL_CIFC/src/SPELLautomaticCif.C index 632370b..d250ae9 100644 --- a/lib/SPELL_CIFC/src/SPELLautomaticCif.C +++ b/lib/SPELL_CIFC/src/SPELLautomaticCif.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the automatic (non-interactive) CIF // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -96,12 +96,17 @@ void SPELLautomaticCif::specificSetup( const SPELLcifStartupInfo& info ) getExecutorConfig().setPromptWarningDelay( STRI((ctx.getExecutorParameter(ExecutorConstants::PromptDelay))) ); getExecutorConfig().setBrowsableLib( stringToBrowsableLibMode(ctx.getExecutorParameter(ExecutorConstants::BrowsableLib) ) ); getExecutorConfig().setForceTcConfirm( (ctx.getExecutorParameter(ExecutorConstants::ForceTcConfirm) == PythonConstants::True) ); + getExecutorConfig().setForcePromptConfirm( (ctx.getExecutorParameter(ExecutorConstants::ForcePromptConfirm) == PythonConstants::True) ); std::string saveMode = ctx.getExecutorParameter(ExecutorConstants::SaveStateMode); getExecutorConfig().setSaveStateMode( saveMode ); std::string wvMode = ctx.getExecutorParameter(ExecutorConstants::WatchVariables); bool wvEnabled = wvMode == ExecutorConstants::ENABLED; getExecutorConfig().setWatchEnabled( wvEnabled ); + std::string rdMode = ctx.getExecutorParameter(ExecutorConstants::ResourcesDisplay); + bool rdEnabled = rdMode == ExecutorConstants::ENABLED; + getExecutorConfig().setResourcesDisplayEnabled( rdEnabled ); + getExecutorConfig().setLocalResourcesSupported( (ctx.getExecutorParameter(ExecutorConstants::LocalResourcesSupported) == PythonConstants::True) ); } //============================================================================= @@ -322,6 +327,22 @@ void SPELLautomaticCif::specificNotifyVariableScopeChange( const std::string& sc std::cout << "[ NOTIFY VARIABLE SCOPE CHANGE ] " << "Variable Scope Change" << std::endl; } +//============================================================================= +// METHOD: SPELLautomaticCif::specificNotifyResourceChange() +//============================================================================= +void SPELLautomaticCif::specificNotifyResourceChange( const std::vector& changed ) +{ + std::cout << "[ NOTIFY RESOURCE CHANGE ] " << "Resource Change" << std::endl; +} + +//============================================================================= +// METHOD: SPELLautomaticCif::specificNotifyResourceList() +//============================================================================= +void SPELLautomaticCif::specificNotifyResourceList( const std::vector& resources ) +{ + std::cout << "[ NOTIFY RESOURCE LIST ] " << "Resource List" << std::endl; +} + //============================================================================= // METHOD: SPELLautomaticCif::specificNotify //============================================================================= diff --git a/lib/SPELL_CIFC/src/SPELLncursesShell.C b/lib/SPELL_CIFC/src/SPELLncursesShell.C index 18649bb..a34ea90 100644 --- a/lib/SPELL_CIFC/src/SPELLncursesShell.C +++ b/lib/SPELL_CIFC/src/SPELLncursesShell.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the NCurses shell for standalone executor // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -47,7 +47,7 @@ #define COLOR_WARNING 3 #define COLOR_OK 4 -char *choices[] = { +static const std::string choices[] = { "RUN", "PAUSE", "SKIP", @@ -60,7 +60,7 @@ char *choices[] = { "RELOAD" }; -char *descriptions[] = { +static const std::string descriptions[] = { "Run the procedure", "Pause the execution", "Skip current line", @@ -333,7 +333,7 @@ void SPELLncursesShell::setupMenu() m_menuItems = (ITEM **)calloc(n_choices, sizeof(ITEM *)); for(i = 0; i < n_choices; ++i) { - m_menuItems[i] = new_item(choices[i], descriptions[i]); + m_menuItems[i] = new_item(choices[i].c_str(), descriptions[i].c_str()); } // Crate menu diff --git a/lib/SPELL_CIFC/src/SPELLstandaloneCif.C b/lib/SPELL_CIFC/src/SPELLstandaloneCif.C index b122fb2..1eef08c 100644 --- a/lib/SPELL_CIFC/src/SPELLstandaloneCif.C +++ b/lib/SPELL_CIFC/src/SPELLstandaloneCif.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the CIF for standalone executor // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CIFS/Makefile.am b/lib/SPELL_CIFS/Makefile.am index 18834d7..12a323d 100644 --- a/lib/SPELL_CIFS/Makefile.am +++ b/lib/SPELL_CIFS/Makefile.am @@ -1,36 +1,47 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_CIFS.la -libSPELL_CIFS_la_LDFLAGS = -version-info 2:1:0 -libSPELL_CIFS_la_SOURCES = \ - src/SPELLrequestProcessor.C \ - src/SPELLdisplayBuffer.C \ - src/SPELLvariableBuffer.C \ - src/SPELLcifPromptHelper.C \ - src/SPELLserverCif.C - + +lib_LTLIBRARIES = libSPELL_CIFS_v2.la libSPELL_CIFS_v3.la + +libSPELL_CIFS_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_CIFS_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_CIFS_v2_la_SOURCES = src/SPELLrequestProcessor.C \ + src/SPELLdisplayBuffer.C \ + src/SPELLvariableBuffer.C \ + src/SPELLresourceBuffer.C \ + src/SPELLcifPromptHelper.C \ + src/SPELLserverCif.C + +libSPELL_CIFS_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_CIFS_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_CIFS_v3_la_SOURCES = src/SPELLrequestProcessor.C \ + src/SPELLdisplayBuffer.C \ + src/SPELLvariableBuffer.C \ + src/SPELLresourceBuffer.C \ + src/SPELLcifPromptHelper.C \ + src/SPELLserverCif.C + \ No newline at end of file diff --git a/lib/SPELL_CIFS/src/SPELLcifPromptHelper.C b/lib/SPELL_CIFS/src/SPELLcifPromptHelper.C index 3ecf0e2..0561ffe 100644 --- a/lib/SPELL_CIFS/src/SPELLcifPromptHelper.C +++ b/lib/SPELL_CIFS/src/SPELLcifPromptHelper.C @@ -5,7 +5,7 @@ // DESCRIPTION: Helper for prompt processing // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -38,7 +38,7 @@ std::string SPELLcifPromptHelper::completeOptions( const SPELLpromptDefinition& { std::string msgToShow = origMsgToShow; - msgToShow += "\nAvailable options:\n"; + msgToShow += "Available options:\n"; int keyCount = 0; std::string optionStr = ""; diff --git a/lib/SPELL_CIFS/src/SPELLdisplayBuffer.C b/lib/SPELL_CIFS/src/SPELLdisplayBuffer.C index 5a4a46c..aa97e9f 100644 --- a/lib/SPELL_CIFS/src/SPELLdisplayBuffer.C +++ b/lib/SPELL_CIFS/src/SPELLdisplayBuffer.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the display messages buffer // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CIFS/src/SPELLrequestProcessor.C b/lib/SPELL_CIFS/src/SPELLrequestProcessor.C index d51c8c7..31644ce 100644 --- a/lib/SPELL_CIFS/src/SPELLrequestProcessor.C +++ b/lib/SPELL_CIFS/src/SPELLrequestProcessor.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the message request processor // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -160,12 +160,14 @@ void SPELLrequestProcessor::processGetConfig( const SPELLipcMessage& msg, SPELLi std::string byStep = config.isByStep() ? True : False; std::string browsableLibStr = config.getBrowsableLibStr(); std::string forceTcConfirm = config.isForceTcConfirm() ? True : False; + std::string forcePromptConfirm = config.isForcePromptConfirm() ? True : False; response.set(ExecutorConstants::ExecDelay, execDelay); response.set(ExecutorConstants::PromptDelay, promptDelay); response.set(ExecutorConstants::ByStep, byStep); response.set(ExecutorConstants::BrowsableLib, browsableLibStr ); response.set(ExecutorConstants::ForceTcConfirm, forceTcConfirm); + response.set(ExecutorConstants::ForcePromptConfirm, forcePromptConfirm); LOG_INFO("Current executor configuration:"); LOG_INFO(" run into : " + (config.isRunInto() ? True : False) ); @@ -174,6 +176,7 @@ void SPELLrequestProcessor::processGetConfig( const SPELLipcMessage& msg, SPELLi LOG_INFO(" by step : " + byStep); LOG_INFO(" browsable lib : " + browsableLibStr ); LOG_INFO(" force confirm : " + forceTcConfirm); + LOG_INFO(" force prompt confirm : " + forcePromptConfirm); DEBUG("[CIF] Request to get executor config done"); } @@ -237,6 +240,7 @@ void SPELLrequestProcessor::processSetConfig( const SPELLipcMessage& msg, SPELLi std::string byStep = msg.get(ExecutorConstants::ByStep); std::string browsableLibStr = msg.get(ExecutorConstants::BrowsableLib); std::string forceTcConfirm = msg.get(ExecutorConstants::ForceTcConfirm); + std::string forcePromptConfirm = msg.get(ExecutorConstants::ForcePromptConfirm); LOG_INFO("New executor configuration ----------------------------------"); @@ -246,6 +250,7 @@ void SPELLrequestProcessor::processSetConfig( const SPELLipcMessage& msg, SPELLi LOG_INFO(" By step : " + byStep); LOG_INFO(" Br. lib : " + browsableLibStr); LOG_INFO(" TC confirm: " + forceTcConfirm); + LOG_INFO("Prompt confirm: " + forcePromptConfirm); LOG_INFO("-------------------------------------------------------------"); SPELLexecutor::instance().setRunInto( (runInto == True) ); @@ -254,6 +259,7 @@ void SPELLrequestProcessor::processSetConfig( const SPELLipcMessage& msg, SPELLi SPELLexecutor::instance().setByStep( (byStep == True) ); SPELLexecutor::instance().setBrowsableLibStr( browsableLibStr ); SPELLexecutor::instance().setForceTcConfirm( forceTcConfirm == True ); + SPELLexecutor::instance().setForcePromptConfirm( forcePromptConfirm == True ); DEBUG("[CIF] Request to change executor config done"); } @@ -280,7 +286,6 @@ void SPELLrequestProcessor::processSetBreakpoint( const SPELLipcMessage& msg, SP //============================================================================= void SPELLrequestProcessor::processClearBreakpoints( const SPELLipcMessage& msg, SPELLipcMessage& response ) { - SPELLmonitor mon(m_requestLock); DEBUG("[CIF] Request to clear breakpoints for the given code"); // Update the response message response.setId(ExecutorMessages::RSP_CLEAR_BREAKPOINT); @@ -293,7 +298,6 @@ void SPELLrequestProcessor::processClearBreakpoints( const SPELLipcMessage& msg, //============================================================================= void SPELLrequestProcessor::processCheckVariablesEnabled( const SPELLipcMessage& msg, SPELLipcMessage& response ) { - SPELLmonitor mon(m_requestLock); // Update the response message response.setId(ExecutorMessages::RSP_WVARIABLES_ENABLED); bool enabled = SPELLexecutor::instance().getVariableManager().isEnabled(); @@ -449,6 +453,82 @@ void SPELLrequestProcessor::processChangeVariable( const SPELLipcMessage& msg, S SPELLexecutor::instance().getVariableManager().changeVariable( info ); } +//============================================================================= +// METHOD: SPELLrequestProcessor::processCheckResourcesEnabled +//============================================================================= +void SPELLrequestProcessor::processCheckResourcesEnabled( const SPELLipcMessage& msg, SPELLipcMessage& response ) +{ + // Update the response message + response.setId(ExecutorMessages::RSP_DRESOURCES_ENABLED); + bool enabled = SPELLexecutor::instance().getResourceManager().isEnabled(); + bool localSupport = SPELLexecutor::instance().getConfiguration().isLocalResourcesSupported(); + response.set( MessageField::FIELD_DRESOURCES_ENABLED, enabled ? "True" : "False"); + response.set( MessageField::FIELD_LOCAL_RESOURCE_SUPPORTED, localSupport ? "True" : "False"); +} + +//============================================================================= +// METHOD: SPELLrequestProcessor::processGetResources +//============================================================================= +void SPELLrequestProcessor::processGetResources( const SPELLipcMessage& msg, SPELLipcMessage& response ) +{ + SPELLmonitor mon(m_requestLock); + // Update the response message + response.setId(ExecutorMessages::RSP_GET_RESOURCES); + + if (!SPELLexecutor::instance().getResourceManager().isStatusValid()) + { + LOG_WARN("Resource manager is not in valid status"); + response.setType(MSG_TYPE_ERROR); + response.set( MessageField::FIELD_ERROR, "Cannot retrieve resources"); + response.set( MessageField::FIELD_REASON, "Resource analysis cannot be done in the current status"); + response.set( MessageField::FIELD_FATAL, PythonConstants::False ); + return; + } + + if (!SPELLexecutor::instance().getResourceManager().isEnabled()) + { + LOG_WARN("Resource manager is disabled, but received getResources request"); + return; + } + + SPELLexecutor::instance().getResourceManager().getResources(); +} + +//============================================================================= +// METHOD: SPELLrequestProcessor::processChangeResource +//============================================================================= +void SPELLrequestProcessor::processChangeResource( const SPELLipcMessage& msg, SPELLipcMessage& response ) +{ + SPELLmonitor mon(m_requestLock); + // Update the response message + response.setId(ExecutorMessages::RSP_CHANGE_RESOURCE); + + if (!SPELLexecutor::instance().getResourceManager().isStatusValid()) + { + LOG_WARN("Resource manager is not in valid status"); + response.setType(MSG_TYPE_ERROR); + response.set( MessageField::FIELD_ERROR, "Cannot change resources"); + response.set( MessageField::FIELD_REASON, "Resource analysis cannot be done in the current status"); + response.set( MessageField::FIELD_FATAL, PythonConstants::False ); + return; + } + + if (!SPELLexecutor::instance().getResourceManager().isEnabled()) + { + LOG_WARN("Resource manager is disabled, but received change resource request"); + return; + } + + std::string name = msg.get( MessageField::FIELD_RESOURCE_NAME ); + std::string globalValueExpression = msg.get( MessageField::FIELD_RESOURCE_GLOBAL_VALUE ); + std::string localValueExpression = msg.get( MessageField::FIELD_RESOURCE_LOCAL_VALUE ); + + // Type and registration flag are not important here + SPELLresourceInfo info(name,"",globalValueExpression,localValueExpression, std::vector >(), ""); + + SPELLexecutor::instance().getResourceManager().changeResource( info ); +} + //============================================================================= // METHOD: SPELLrequestProcessor::processGetDictionary //============================================================================= diff --git a/lib/SPELL_CIFS/src/SPELLresourceBuffer.C b/lib/SPELL_CIFS/src/SPELLresourceBuffer.C new file mode 100644 index 0000000..0e5f95a --- /dev/null +++ b/lib/SPELL_CIFS/src/SPELLresourceBuffer.C @@ -0,0 +1,197 @@ +// ################################################################################ +// FILE : SPELLresourceBuffer.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the RDOTCO messages buffer +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +#include "SPELL_CIFS/SPELLresourceBuffer.H" +#include "SPELL_CIFS/SPELLserverCif.H" +// Project includes -------------------------------------------------------- +#include "SPELL_IPC/SPELLipc.H" +#include "SPELL_SYN/SPELLmonitor.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_WRP/SPELLconstants.H" +// System includes --------------------------------------------------------- + + +#define FLUSH_PERIOD_USEC 250000 //half a sec + +//============================================================================= +// CONSTRUCTOR: SPELLresourceBuffer::SPELLresourceBuffer +//============================================================================= +SPELLresourceBuffer::SPELLresourceBuffer( const std::string& procId, SPELLserverCif& cif ) +: SPELLthread("display-buffer"), + m_cif(cif), + m_resourceMessage( ExecutorMessages::MSG_RESOURCE_CHANGE ) +{ + DEBUG("[CIF] Created display buffer"); + m_working = true; + m_resourceMessage.setType(MSG_TYPE_ONEWAY); + m_resourceMessage.set( MessageField::FIELD_PROC_ID, procId ); + m_total = 0; + m_bufferHasListResources = false; +} + +//============================================================================= +// DESTRUCTOR: SPELLresourceBuffer::~SPELLresourceBuffer +//============================================================================= +SPELLresourceBuffer::~SPELLresourceBuffer() +{ + DEBUG("[CIF] Destroyed RDOTCO buffer"); +} + +//============================================================================= +// METHOD: SPELLresourceBuffer::run() +//============================================================================= +void SPELLresourceBuffer::run() +{ + while(m_working) + { + usleep(FLUSH_PERIOD_USEC); + { + SPELLmonitor m(m_lock); + if (hasDataToSend()) + { + flush(); + } + } + } +} + +//============================================================================= +// METHOD: SPELLresourceBuffer::run() +//============================================================================= +void SPELLresourceBuffer::stop() +{ + m_working = false; +} + +//============================================================================= +// METHOD: SPELLresourceBuffer::resourceChange() +//============================================================================= +void SPELLresourceBuffer::resourceChange( const std::vector& changed ) +{ + SPELLmonitor m(m_lock); + std::vector::const_iterator it; + std::map::iterator mit; + + for( it = changed.begin(); it != changed.end(); it++) + { + // If the resource was added and not yet notified, just not process + // this notification and leave the add operation but with the new value. + std::string name = (*it).resourceName; + m_resources[ name ] = (*it); + } + m_total++; +} + +//============================================================================= +// METHOD: SPELLresourceBuffer::resourceList() +//============================================================================= +void SPELLresourceBuffer::resourceList( const std::vector& resources ) +{ + resourceChange(resources); + m_bufferHasListResources = true; +} + +//============================================================================= +// METHOD: SPELLresourceBuffer::flush() +//============================================================================= +void SPELLresourceBuffer::flush() +{ + SPELLmonitor m(m_lock); + + std::string names = ""; + std::string types = ""; + std::string globalValues = ""; + std::string localValues = ""; + std::string valueRange = ""; + std::string gcsValueRange = ""; + std::string gcsNames = ""; + + std::map::const_iterator it; + for(it = m_resources.begin(); it != m_resources.end(); it++) + { + if (names != "") + { + names += VARIABLE_SEPARATOR; + types += VARIABLE_SEPARATOR; + globalValues += VARIABLE_SEPARATOR; + localValues += VARIABLE_SEPARATOR; + gcsNames += VARIABLE_SEPARATOR; + if (m_bufferHasListResources) + { + valueRange += VARIABLE_SEPARATOR; + gcsValueRange += VARIABLE_SEPARATOR; + } + } + names += it->second.resourceName; + types += it->second.resourceType; + globalValues += it->second.resourceGlobalValue; + localValues += it->second.resourceLocalValue; + gcsNames += it->second.resourceGCSName; + if (m_bufferHasListResources) + { + for (unsigned int vri = 0; vri < it->second.resourceValueRange.size(); vri++) + { + if (vri > 0) + { + valueRange += VARIABLE_PROPERTY_SEPARATOR; + gcsValueRange += VARIABLE_PROPERTY_SEPARATOR; + } + valueRange += it->second.resourceValueRange[vri].first; + gcsValueRange += it->second.resourceValueRange[vri].second; + } + } + } + + m_resources.clear(); + + m_resourceMessage.set(MessageField::FIELD_RESOURCE_NAME, names); + m_resourceMessage.set(MessageField::FIELD_RESOURCE_TYPE, types); + m_resourceMessage.set(MessageField::FIELD_RESOURCE_GLOBAL_VALUE, globalValues); + m_resourceMessage.set(MessageField::FIELD_RESOURCE_LOCAL_VALUE, localValues); + // An empty string is passed in case of resource change (no need to resend the ranges) + m_resourceMessage.set(MessageField::FIELD_RESOURCE_VALUE_RANGE, valueRange); + m_resourceMessage.set(MessageField::FIELD_RESOURCE_GCS_VALUE_RANGE, gcsValueRange); + m_resourceMessage.set(MessageField::FIELD_RESOURCE_GCS_NAME, gcsNames); + +#ifdef WITH_DEBUG + std::cerr << "SEND RDOTCO (" << m_total << ")" << std::endl; +#endif + + m_cif.sendGUIMessage(m_resourceMessage); + m_total = 0; + m_bufferHasListResources = false; +} + +//============================================================================= +// METHOD: SPELLresourceBuffer::hasDataToSend() +//============================================================================= +bool SPELLresourceBuffer::hasDataToSend() +{ + SPELLmonitor m(m_lock); + return (m_resources.size()>0); +} diff --git a/lib/SPELL_CIFS/src/SPELLserverCif.C b/lib/SPELL_CIFS/src/SPELLserverCif.C index 85a133d..0be5af8 100644 --- a/lib/SPELL_CIFS/src/SPELLserverCif.C +++ b/lib/SPELL_CIFS/src/SPELLserverCif.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the CIF for server environment // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -51,7 +51,7 @@ // DEFINES ///////////////////////////////////////////////////////////////// -#define ACKNOWLEDGE_TIMEOUT_SEC 30 +#define ACKNOWLEDGE_TIMEOUT_MSEC 30000 // GLOBALS ///////////////////////////////////////////////////////////////// SPELLserverCif* s_handle = NULL; @@ -72,11 +72,18 @@ SPELLserverCif::SPELLserverCif() m_ifc = NULL; m_buffer = NULL; m_wovBuffer = NULL; + m_rdotcoBuffer = NULL; m_ready = false; - + m_timeoutOpenProcMsec = 0; + m_ipcTimeoutGuiRequestMsec = 0; + m_ipcTimeoutCtxRequestMsec = 0; + m_ipcInterruptionNotified = false; m_errorState = false; m_sequence = 0; m_sequenceStack = 0; + m_numNotAcknowledged = 0; + m_timeoutExecLoginMsec = 0; + m_timeoutAcknowledgeSec = 0; getExecutorConfig().setVisible(true); getExecutorConfig().setAutomatic(false); @@ -101,12 +108,6 @@ SPELLserverCif::SPELLserverCif() m_closing = false; m_lastStack = ""; - - m_ipcTimeoutGuiRequestMsec = SPELLconfiguration::instance().commonOrDefault( "GuiRequestTimeout", IPC_GUIREQUEST_DEFAULT_TIMEOUT_MSEC ); - m_ipcTimeoutCtxRequestMsec = SPELLconfiguration::instance().commonOrDefault( "CtxRequestTimeout", IPC_CTXREQUEST_DEFAULT_TIMEOUT_MSEC ); - m_timeoutOpenProcMsec = SPELLconfiguration::instance().commonOrDefault( "OpenProcTimeout", IPC_OPENPROC_DEFAULT_TIMEOUT_MSEC ); - m_timeoutExecLoginMsec = SPELLconfiguration::instance().commonOrDefault( "ExecutorLoginTimeout", IPC_EXLOGIN_DEFAULT_TIMEOUT_MSEC ); - m_timeoutAcknowledgeSec = SPELLconfiguration::instance().commonOrDefault( "AcknowledgeTimeout", ACKNOWLEDGE_TIMEOUT_SEC ); } //============================================================================= @@ -129,6 +130,11 @@ SPELLserverCif::~SPELLserverCif() delete m_wovBuffer; m_wovBuffer = NULL; } + if (m_rdotcoBuffer != NULL) + { + delete m_rdotcoBuffer; + m_rdotcoBuffer = NULL; + } } //============================================================================= @@ -136,6 +142,18 @@ SPELLserverCif::~SPELLserverCif() //============================================================================= void SPELLserverCif::specificSetup( const SPELLcifStartupInfo& info ) { + m_ipcTimeoutGuiRequestMsec = SPELLconfiguration::instance().commonOrDefault( "GuiRequestTimeout", IPC_GUIREQUEST_DEFAULT_TIMEOUT_MSEC ); + m_ipcTimeoutCtxRequestMsec = SPELLconfiguration::instance().commonOrDefault( "CtxRequestTimeout", IPC_CTXREQUEST_DEFAULT_TIMEOUT_MSEC ); + m_timeoutOpenProcMsec = SPELLconfiguration::instance().commonOrDefault( "OpenProcTimeout", IPC_OPENPROC_DEFAULT_TIMEOUT_MSEC ); + m_timeoutExecLoginMsec = SPELLconfiguration::instance().commonOrDefault( "ExecutorLoginTimeout", IPC_EXLOGIN_DEFAULT_TIMEOUT_MSEC ); + m_timeoutAcknowledgeSec = SPELLconfiguration::instance().commonOrDefault( "AcknowledgeTimeout", ACKNOWLEDGE_TIMEOUT_MSEC ) / 1000; + + LOG_INFO("GUI request timeout: " + ISTR(m_ipcTimeoutGuiRequestMsec) + " ms"); + LOG_INFO("CTX request timeout: " + ISTR(m_ipcTimeoutCtxRequestMsec) + " ms"); + LOG_INFO(" OPEN timeout: " + ISTR(m_timeoutOpenProcMsec) + " ms"); + LOG_INFO(" LOGIN timeout: " + ISTR(m_timeoutExecLoginMsec) + " ms"); + LOG_INFO(" ACK timeout: " + ISTR(m_timeoutAcknowledgeSec) + " sec"); + //Initialize Executor Config parameters getExecutorConfig().setVisible(true); getExecutorConfig().setAutomatic(false); @@ -157,6 +175,9 @@ void SPELLserverCif::specificSetup( const SPELLcifStartupInfo& info ) m_wovBuffer = new SPELLvariableBuffer( getProcId(), *this ); m_wovBuffer->start(); + m_rdotcoBuffer = new SPELLresourceBuffer( getProcId(), *this ); + m_rdotcoBuffer->start(); + // Initialize message sequencer m_sequence = 0; m_sequenceStack = 0; @@ -188,8 +209,10 @@ void SPELLserverCif::specificCleanup( bool force ) cancelPrompt(); m_buffer->stop(); m_wovBuffer->stop(); + m_rdotcoBuffer->stop(); m_buffer->join(); m_wovBuffer->join(); + m_rdotcoBuffer->join(); DEBUG("[CIF] Disconnecting server CIF"); if (!force) { @@ -246,14 +269,12 @@ SPELLipcMessage SPELLserverCif::login() loginmsg.set( MessageField::FIELD_CSP, getProcId()); // status loaded is not correct if the procedure did not compile, but the error will be - // notified afterwards anyhow. - loginmsg.set( MessageField::FIELD_EXEC_STATUS, SPELLexecutorUtils::statusToString(STATUS_LOADED)); + // notified afterwards anyhow.c loginmsg.set( MessageField::FIELD_ASRUN_NAME, getAsRunName() ); loginmsg.set( MessageField::FIELD_LOG_NAME, SPELLlog::instance().getLogFile() ); LOG_INFO("Login information:"); LOG_INFO("Proc : " + getProcId() ); - LOG_INFO("Status: LOADED"); LOG_INFO("AsRun : " + getAsRunName() ); LOG_INFO("Log : " + SPELLlog::instance().getLogFile() ); @@ -376,7 +397,10 @@ void SPELLserverCif::processLogin( const SPELLipcMessage& loginResp ) { getExecutorConfig().setForceTcConfirm( loginResp.get(MessageField::FIELD_FORCE_TC_CONFIRM) == ExecutorConstants::TRUE_VALUE ); } - + if (loginResp.hasField(MessageField::FIELD_FORCE_PROMPT_CONFIRM)) + { + getExecutorConfig().setForcePromptConfirm( loginResp.get(MessageField::FIELD_FORCE_PROMPT_CONFIRM) == ExecutorConstants::TRUE_VALUE ); + } if (loginResp.hasField(MessageField::FIELD_SAVE_STATE_MODE)) { getExecutorConfig().setSaveStateMode( loginResp.get(MessageField::FIELD_SAVE_STATE_MODE) ); @@ -387,6 +411,16 @@ void SPELLserverCif::processLogin( const SPELLipcMessage& loginResp ) getExecutorConfig().setWatchEnabled( loginResp.get(MessageField::FIELD_WATCH_VARIABLES) == ExecutorConstants::TRUE_VALUE ); } + if (loginResp.hasField(MessageField::FIELD_RESOURCES_DISPLAY)) + { + getExecutorConfig().setResourcesDisplayEnabled( loginResp.get(MessageField::FIELD_RESOURCES_DISPLAY) == ExecutorConstants::TRUE_VALUE ); + } + + if (loginResp.hasField(MessageField::FIELD_LOCAL_RESOURCE_SUPPORTED)) + { + getExecutorConfig().setLocalResourcesSupported( loginResp.get(MessageField::FIELD_LOCAL_RESOURCE_SUPPORTED) == ExecutorConstants::TRUE_VALUE ); + } + if (loginResp.hasField(MessageField::FIELD_MAX_VERBOSITY)) { getExecutorConfig().setMaxVerbosity( STRI(loginResp.get(MessageField::FIELD_MAX_VERBOSITY)) ); @@ -412,8 +446,11 @@ void SPELLserverCif::processLogin( const SPELLipcMessage& loginResp ) LOG_INFO("Prompt Delay : " + ISTR(getExecutorConfig().getPromptWarningDelay())); LOG_INFO("By Step : " + BSTR(getExecutorConfig().isByStep())); LOG_INFO("Force Tc Conf : " + BSTR(getExecutorConfig().isForceTcConfirm())); + LOG_INFO("Force Prompt Conf : " + BSTR(getExecutorConfig().isForcePromptConfirm())); LOG_INFO("Save State Mod: " + getExecutorConfig().getSaveStateMode()); LOG_INFO("WatchVariables: " + BSTR(getExecutorConfig().isWatchEnabled())); + LOG_INFO("ResourcesDisplay: " + BSTR(getExecutorConfig().isResourcesDisplayEnabled())); + LOG_INFO("Local Resource supported: " + BSTR(getExecutorConfig().isLocalResourcesSupported())); LOG_INFO("Max Verbosity : " + ISTR(getExecutorConfig().getMaxVerbosity())); } @@ -479,6 +516,8 @@ void SPELLserverCif::sendGUIMessage( const SPELLipcMessage& msg ) LOG_ERROR("Unable to communicate with Context: " + std::string(ex.what())); SPELLexecutor::instance().pause(); } + } else { + usleep(500000); } } @@ -576,35 +615,30 @@ void SPELLserverCif::prepareMessage( SPELLipcMessage& msg, const std::string da void SPELLserverCif::specificNotifyLine() { // Local variables - std::string stage = ""; - + SPELLsafeThreadOperations ops("Notify"); + std::string stage = ""; + SPELLipcMessage msg = m_lnMessage; // prepares the message to be sent to GUI - prepareMessage( m_lnMessage, MessageValue::DATA_TYPE_LINE, MSG_TYPE_NOTIFY_ASYNC); + prepareMessage( msg, MessageValue::DATA_TYPE_LINE, MSG_TYPE_NOTIFY_ASYNC); // Update message with stage info stage = getStage(); - - if (stage.find(":") != std::string::npos) + size_t delim = stage.find(":"); + if (delim != std::string::npos) { - std::vector stage_title = SPELLutils::tokenize(stage,":"); - if (stage_title.size()==2) - { - m_lnMessage.set(MessageField::FIELD_STAGE_ID,stage_title[0]); - m_lnMessage.set(MessageField::FIELD_STAGE_TL,stage_title[1]); - } + msg.set(MessageField::FIELD_STAGE_ID,stage.substr(0, delim)); + if (delim + 1 < stage.length()) + msg.set(MessageField::FIELD_STAGE_TL,stage.substr(delim + 1)); else - { - m_lnMessage.set(MessageField::FIELD_STAGE_ID,"(none)"); - m_lnMessage.set(MessageField::FIELD_STAGE_TL,"(none)"); - } + msg.set(MessageField::FIELD_STAGE_TL,"(none)"); } else { - m_lnMessage.set(MessageField::FIELD_STAGE_ID,stage); - m_lnMessage.set(MessageField::FIELD_STAGE_TL,stage); + msg.set(MessageField::FIELD_STAGE_ID,stage); + msg.set(MessageField::FIELD_STAGE_TL,stage); } - sendGUIMessage(m_lnMessage); + sendGUIMessage(msg); } //SPELLserverCif::specificNotifyLine() @@ -615,10 +649,16 @@ void SPELLserverCif::specificNotifyLine() void SPELLserverCif::specificNotifyCall() { // prepares the message to be sent to GUI - prepareMessage( m_lnMessage, MessageValue::DATA_TYPE_CALL, MSG_TYPE_NOTIFY); + SPELLsafeThreadOperations ops("NotifyCall"); + SPELLipcMessage msg = m_lnMessage; + prepareMessage( msg, MessageValue::DATA_TYPE_CALL, MSG_TYPE_NOTIFY); + + do + { + sendGUIMessage(msg); + } + while (!waitAcknowledge(msg)); - sendGUIMessage(m_lnMessage); - waitAcknowledge(m_lnMessage); } //void SPELLserverCif::specificNotifyCall() @@ -627,11 +667,15 @@ void SPELLserverCif::specificNotifyCall() //============================================================================= void SPELLserverCif::specificNotifyReturn() { - // prepares the message to be sent to GUI - prepareMessage( m_lnMessage, MessageValue::DATA_TYPE_RETURN, MSG_TYPE_NOTIFY); + // prepares the message to be sent to GUI + SPELLsafeThreadOperations ops("NotifyReturn"); + SPELLipcMessage msg = m_lnMessage; + prepareMessage(msg, MessageValue::DATA_TYPE_RETURN, MSG_TYPE_NOTIFY); - sendGUIMessage(m_lnMessage); - waitAcknowledge(m_lnMessage); + do + { + sendGUIMessage(msg); + } while (!waitAcknowledge(msg)); } //============================================================================= @@ -640,24 +684,28 @@ void SPELLserverCif::specificNotifyReturn() void SPELLserverCif::specificNotifyStatus( const SPELLstatusInfo& st ) { // Prepare message - m_stMessage.set(MessageField::FIELD_EXEC_STATUS, SPELLexecutorUtils::statusToString(st.status)); - completeMessage( m_stMessage ); + SPELLsafeThreadOperations ops("NotifyStatus"); + SPELLipcMessage msg = m_stMessage; + msg.set(MessageField::FIELD_EXEC_STATUS, SPELLexecutorUtils::statusToString(st.status)); + completeMessage( msg ); // Set message Condition information if (st.condition.size()>0) { - m_stMessage.set( MessageField::FIELD_CONDITION, st.condition ); + msg.set( MessageField::FIELD_CONDITION, st.condition ); } // Set message Action information if (st.actionLabel != "") { - m_stMessage.set( MessageField::FIELD_ACTION_LABEL, st.actionLabel ); - m_stMessage.set( MessageField::FIELD_ACTION_ENABLED, st.actionEnabled ? MessageValue::DATA_TRUE : MessageValue::DATA_FALSE ); + msg.set( MessageField::FIELD_ACTION_LABEL, st.actionLabel ); + msg.set( MessageField::FIELD_ACTION_ENABLED, st.actionEnabled ? MessageValue::DATA_TRUE : MessageValue::DATA_FALSE ); } - sendGUIMessage(m_stMessage); - waitAcknowledge(m_stMessage); + do{ + sendGUIMessage(msg); + } while(!waitAcknowledge(msg)); + } //============================================================================= @@ -665,6 +713,7 @@ void SPELLserverCif::specificNotifyStatus( const SPELLstatusInfo& st ) //============================================================================= void SPELLserverCif::specificNotifyUserActionSet( const std::string& label, const unsigned int severity ) { + SPELLsafeThreadOperations ops("NotifyActionSet"); SPELLipcMessage actionMessage(MessageId::MSG_ID_SET_UACTION); actionMessage.setType(MSG_TYPE_ONEWAY); actionMessage.set( MessageField::FIELD_PROC_ID, getProcId() ); @@ -692,6 +741,7 @@ void SPELLserverCif::specificNotifyUserActionSet( const std::string& label, cons //============================================================================= void SPELLserverCif::specificNotifyUserActionUnset() { + SPELLsafeThreadOperations ops("NotifyActionUnset"); SPELLipcMessage actionMessage(MessageId::MSG_ID_DISMISS_UACTION); actionMessage.setType(MSG_TYPE_ONEWAY); actionMessage.set( MessageField::FIELD_PROC_ID, getProcId() ); @@ -703,6 +753,7 @@ void SPELLserverCif::specificNotifyUserActionUnset() //============================================================================= void SPELLserverCif::specificNotifyUserActionEnable( bool enable ) { + SPELLsafeThreadOperations ops("Notify"); if (enable) { SPELLipcMessage actionMessage(MessageId::MSG_ID_ENABLE_UACTION); @@ -725,7 +776,9 @@ void SPELLserverCif::specificNotifyUserActionEnable( bool enable ) void SPELLserverCif::specificNotify( ItemNotification notification ) { //Prepare message - completeMessage( m_ntMessage ); + SPELLsafeThreadOperations ops("Notify"); + SPELLipcMessage msg = m_ntMessage; + completeMessage( msg ); //DEBUG("[CIF] Processing status"); @@ -733,27 +786,30 @@ void SPELLserverCif::specificNotify( ItemNotification notification ) std::stringstream buffer; buffer << notification.getSuccessfulCount(); - m_ntMessage.set(MessageField::FIELD_NOTIF_ITEM_TYPE, NOTIF_TYPE_STR[notification.type]); - m_ntMessage.set(MessageField::FIELD_NOTIF_ITEM_NAME, notification.name); - m_ntMessage.set(MessageField::FIELD_NOTIF_ITEM_VALUE, notification.value); - m_ntMessage.set(MessageField::FIELD_NOTIF_ITEM_STATUS, notification.status); - m_ntMessage.set(MessageField::FIELD_NOTIF_ITEM_REASON, notification.getTokenizedComment()); - m_ntMessage.set(MessageField::FIELD_NOTIF_ITEM_TIME, notification.getTokenizedTime()); - m_ntMessage.set(MessageField::FIELD_NOTIF_ITEM_SCOUNT, buffer.str()); + msg.set(MessageField::FIELD_NOTIF_ITEM_TYPE, NOTIF_TYPE_STR[notification.type]); + msg.set(MessageField::FIELD_NOTIF_ITEM_NAME, notification.name); + msg.set(MessageField::FIELD_NOTIF_ITEM_VALUE, notification.value); + msg.set(MessageField::FIELD_NOTIF_ITEM_STATUS, notification.status); + msg.set(MessageField::FIELD_NOTIF_ITEM_REASON, notification.getTokenizedComment()); + msg.set(MessageField::FIELD_NOTIF_ITEM_TIME, notification.getTokenizedTime()); + msg.set(MessageField::FIELD_NOTIF_ITEM_SCOUNT, buffer.str()); //DEBUG("[CIF] Message prepared, sending"); - sendGUIMessage(m_ntMessage); - waitAcknowledge(m_ntMessage); + do + { + sendGUIMessage(msg); + } while(!waitAcknowledge(msg)); + //DEBUG("[CIF] Notification sent"); } //============================================================================= // METHOD: SPELLserverCif::waitAcknowledge() //============================================================================= -void SPELLserverCif::waitAcknowledge( const SPELLipcMessage& msg ) +bool SPELLserverCif::waitAcknowledge( const SPELLipcMessage& msg ) { - if (m_ready && !m_ipcInterruptionNotified) + if (m_ready) { bool found = false; std::string seq = msg.get(MessageField::FIELD_MSG_SEQUENCE); @@ -766,11 +822,12 @@ void SPELLserverCif::waitAcknowledge( const SPELLipcMessage& msg ) SPELLmonitor m(m_ackLock); it = std::find(m_ackSequences.begin(), m_ackSequences.end(), seq); - found = it != m_ackSequences.end(); if (found) { m_ackSequences.erase(it); + + return true; } else { @@ -782,41 +839,16 @@ void SPELLserverCif::waitAcknowledge( const SPELLipcMessage& msg ) LOG_WARN("Acknowledge not received!"); LOG_WARN(msg.dataStr()); LOG_WARN("#########################"); - m_numNotAcknowledged++; - - if (m_numNotAcknowledged==5) - { - m_numNotAcknowledged = 0; - if (!m_ipcInterruptionNotified) - { - m_ipcInterruptionNotified = true; - SPELLexecutorStatus st = SPELLexecutor::instance().getStatus(); - if ( st == STATUS_RUNNING ) - { - ExecutorCommand cmd; - cmd.id = CMD_PAUSE; - warning("Communication with controlling GUI interrupted. Procedure paused for safety.", LanguageConstants::SCOPE_SYS); - warning("You may try to resume execution.", LanguageConstants::SCOPE_SYS); - SPELLexecutor::instance().command(cmd,false); - } - else if ( st == STATUS_WAITING ) - { - ExecutorCommand cmd; - cmd.id = CMD_INTERRUPT; - warning("Communication with controlling GUI interrupted. Procedure interruptedfor safety.", LanguageConstants::SCOPE_SYS); - warning("You may try to resume execution.", LanguageConstants::SCOPE_SYS); - SPELLexecutor::instance().command(cmd,false); - } - } - } - return; + + return false; } } } - if (!m_ready) return; + if (!m_ready) return false; usleep(52000); } } + return false; } //============================================================================= @@ -825,7 +857,7 @@ void SPELLserverCif::waitAcknowledge( const SPELLipcMessage& msg ) void SPELLserverCif::specificNotifyError( const std::string& error, const std::string& reason, bool fatal ) { //LOG_ERROR("[CIF] Error notification: " + error + " (" + reason + ")"); - + SPELLsafeThreadOperations ops("Notify"); // prepare error message SPELLipcMessage errorMsg( MessageId::MSG_ID_ERROR); errorMsg.setType(MSG_TYPE_ERROR); @@ -852,71 +884,77 @@ void SPELLserverCif::specificNotifyError( const std::string& error, const std::s //============================================================================= // METHOD: SPELLserverCif::specificWrite //============================================================================= -void SPELLserverCif::specificWrite( const std::string& msg, unsigned int scope ) +void SPELLserverCif::specificWrite( const std::string& msgText, unsigned int scope ) { if (isManual()) { - completeMessage( m_wrMessage ); + SPELLsafeThreadOperations ops("Notify"); + SPELLipcMessage msg = m_wrMessage; + completeMessage( msg ); std::string timeStr = SPELLutils::timestampUsec(); - m_wrMessage.set(MessageField::FIELD_TEXT,msg); - m_wrMessage.set(MessageField::FIELD_LEVEL,MessageValue::DATA_SEVERITY_INFO); - m_wrMessage.set(MessageField::FIELD_MSGTYPE,LanguageConstants::DISPLAY); - m_wrMessage.set(MessageField::FIELD_TIME, timeStr); - m_wrMessage.set(MessageField::FIELD_SCOPE, ISTR(scope)); - sendGUIMessage(m_wrMessage); + msg.set(MessageField::FIELD_TEXT,msgText); + msg.set(MessageField::FIELD_LEVEL,MessageValue::DATA_SEVERITY_INFO); + msg.set(MessageField::FIELD_MSGTYPE,LanguageConstants::DISPLAY); + msg.set(MessageField::FIELD_TIME, timeStr); + msg.set(MessageField::FIELD_SCOPE, ISTR(scope)); + sendGUIMessage(msg); } else { - m_buffer->write( msg, scope ); + m_buffer->write( msgText, scope ); } } //============================================================================= // METHOD: SPELLserverCif::specificWarning //============================================================================= -void SPELLserverCif::specificWarning( const std::string& msg, unsigned int scope ) +void SPELLserverCif::specificWarning( const std::string& msgText, unsigned int scope ) { // We shall not bufferize in manual mode if (isManual()) { - completeMessage( m_wrMessage ); + SPELLsafeThreadOperations ops("Notify"); + SPELLipcMessage msg = m_wrMessage; + completeMessage( msg ); std::string timeStr = SPELLutils::timestampUsec(); - m_wrMessage.set(MessageField::FIELD_TEXT,msg); - m_wrMessage.set(MessageField::FIELD_LEVEL,MessageValue::DATA_SEVERITY_WARN); - m_wrMessage.set(MessageField::FIELD_MSGTYPE,LanguageConstants::DISPLAY); - m_wrMessage.set(MessageField::FIELD_TIME, timeStr); - m_wrMessage.set(MessageField::FIELD_SCOPE, ISTR(scope)); - sendGUIMessage(m_wrMessage); + msg.set(MessageField::FIELD_TEXT,msgText); + msg.set(MessageField::FIELD_LEVEL,MessageValue::DATA_SEVERITY_WARN); + msg.set(MessageField::FIELD_MSGTYPE,LanguageConstants::DISPLAY); + msg.set(MessageField::FIELD_TIME, timeStr); + msg.set(MessageField::FIELD_SCOPE, ISTR(scope)); + sendGUIMessage(msg); } else { - m_buffer->warning( msg, scope ); + m_buffer->warning( msgText, scope ); } } //============================================================================= // METHOD: SPELLserverCif::specificError -//============================================================================= -void SPELLserverCif::specificError( const std::string& msg, unsigned int scope ) +//==============================b=============================================== +void SPELLserverCif::specificError( const std::string& msgText, unsigned int scope ) { // We shall not bufferize in manual mode if (isManual()) { - completeMessage( m_wrMessage ); + SPELLsafeThreadOperations ops("Notify"); + SPELLipcMessage msg = m_wrMessage; + completeMessage( msg ); std::string timeStr = SPELLutils::timestampUsec(); - m_wrMessage.set(MessageField::FIELD_TEXT,msg); - m_wrMessage.set(MessageField::FIELD_LEVEL,MessageValue::DATA_SEVERITY_ERROR); - m_wrMessage.set(MessageField::FIELD_MSGTYPE,LanguageConstants::DISPLAY); - m_wrMessage.set(MessageField::FIELD_TIME, timeStr); - m_wrMessage.set(MessageField::FIELD_SCOPE, ISTR(scope)); - sendGUIMessage(m_wrMessage); + msg.set(MessageField::FIELD_TEXT,msgText); + msg.set(MessageField::FIELD_LEVEL,MessageValue::DATA_SEVERITY_ERROR); + msg.set(MessageField::FIELD_MSGTYPE,LanguageConstants::DISPLAY); + msg.set(MessageField::FIELD_TIME, timeStr); + msg.set(MessageField::FIELD_SCOPE, ISTR(scope)); + sendGUIMessage(msg); } else { - m_buffer->error( msg, scope ); + m_buffer->error( msgText, scope ); } } //void SPELLserverCif::specificError( const std::string& msg, unsigned int scope ) @@ -928,6 +966,7 @@ void SPELLserverCif::specificNotifyVariableChange( const std::vector& changed, const std::vector& deleted ) { + SPELLsafeThreadOperations ops("Notify"); m_wovBuffer->variableChange(added,changed,deleted); } @@ -938,7 +977,8 @@ void SPELLserverCif::specificNotifyVariableScopeChange( const std::string& scope const std::vector& globals, const std::vector& locals ) { - SPELLipcMessage notifyMsg(ExecutorMessages::MSG_SCOPE_CHANGE); + SPELLsafeThreadOperations ops("Notify"); + SPELLipcMessage notifyMsg(ExecutorMessages::MSG_SCOPE_CHANGE); notifyMsg.setType(MSG_TYPE_ONEWAY); std::string names = ""; @@ -989,11 +1029,34 @@ void SPELLserverCif::specificNotifyVariableScopeChange( const std::string& scope sendGUIMessage(notifyMsg); } +//============================================================================= +// METHOD: SPELLserverCif::specificNotifyResourceChange() +//============================================================================= +void SPELLserverCif::specificNotifyResourceChange( const std::vector& changed ) +{ + SPELLsafeThreadOperations ops("Notify"); + m_rdotcoBuffer->resourceChange(changed); +} + +//============================================================================= +// METHOD: SPELLserverCif::specificNotifyResourceList() +//============================================================================= +void SPELLserverCif::specificNotifyResourceList( const std::vector& resources ) +{ + SPELLsafeThreadOperations ops("Notify"); + m_rdotcoBuffer->resourceList(resources); +} + //============================================================================= // METHOD: SPELLserverCif::specificPrompt //============================================================================= void SPELLserverCif::specificPrompt( const SPELLpromptDefinition& def, std::string& rawAnswer, std::string& answerToShow ) { + PyThreadState* m_state; + m_state = SPELLpythonHelper::instance().beginSafeAllowThreads(); + SPELLmonitor m(m_promptLock); + SPELLpythonHelper::instance().endSafeAllowThreads( m_state ); + m_promptDef = def; std::string timeStr = SPELLutils::timestampUsec(); @@ -1013,7 +1076,7 @@ void SPELLserverCif::specificPrompt( const SPELLpromptDefinition& def, std::stri DEBUG("[CIF] Prompt typecode " + ISTR(def.typecode)); // the prompt display - std::string msgToShow = def.message; + std::string msgToShow = ""; if ( def.options.size() == 0 ) { @@ -1030,7 +1093,9 @@ void SPELLserverCif::specificPrompt( const SPELLpromptDefinition& def, std::stri DEBUG("[CIF] Expected string: " + m_promptMessage.get(MessageField::FIELD_EXPECTED)); // Send the display message via the buffer to ensure synchronization - m_buffer->prompt(msgToShow, LanguageConstants::SCOPE_PROMPT); + m_asRun->writeInfo( getStack(), msgToShow, LanguageConstants::SCOPE_PROMPT ); + + m_buffer->prompt(def.message + "\n" + msgToShow, LanguageConstants::SCOPE_PROMPT); // Ensure buffer is flushed m_buffer->flush(); @@ -1097,11 +1162,14 @@ void SPELLserverCif::specificPrompt( const SPELLpromptDefinition& def, std::stri // \todo When there is no controlling client we should keep the child procedure in prompt waiting state // Send the display message via the buffer to ensure synchronization - m_buffer->write("Answer: '" + toShow + "'", LanguageConstants::SCOPE_PROMPT); - m_buffer->flush(); + if ( (def.typecode & LanguageConstants::PROMPT_PASSWORD)>0) + m_buffer->write("Answer: '***'", LanguageConstants::SCOPE_PROMPT); + else + m_buffer->write("Answer: '" + toShow + "'", LanguageConstants::SCOPE_PROMPT); + m_buffer->flush(); - rawAnswer = toProcess; - answerToShow = toShow; + rawAnswer = toProcess; + answerToShow = toShow; } //============================================================================= @@ -1109,6 +1177,7 @@ void SPELLserverCif::specificPrompt( const SPELLpromptDefinition& def, std::stri //============================================================================= std::string SPELLserverCif::specificOpenSubprocedure( const std::string& procId, int callingLine, const std::string& args, bool automatic, bool blocking, bool visible ) { + SPELLsafeThreadOperations ops("Notify"); std::string openModeStr = "{"; DEBUG("[CIF] Open subprocedure options: " + BSTR(automatic) + "," + BSTR(blocking) + "," + BSTR(visible)); openModeStr += (automatic ? (LanguageModifiers::Automatic + ":" + PythonConstants::True) : (LanguageModifiers::Automatic + ":" + PythonConstants::False)) + ","; @@ -1185,6 +1254,7 @@ std::string SPELLserverCif::specificOpenSubprocedure( const std::string& procId, //============================================================================= void SPELLserverCif::specificCloseSubprocedure( const std::string& procId ) { + SPELLsafeThreadOperations ops("Notify"); SPELLipcMessage closeMsg(ContextMessages::REQ_CLOSE_EXEC); closeMsg.setType(MSG_TYPE_REQUEST); closeMsg.set(MessageField::FIELD_SPROC_ID, procId); @@ -1197,6 +1267,7 @@ void SPELLserverCif::specificCloseSubprocedure( const std::string& procId ) //============================================================================= void SPELLserverCif::specificKillSubprocedure( const std::string& procId ) { + SPELLsafeThreadOperations ops("Notify"); SPELLipcMessage killMsg(ContextMessages::REQ_KILL_EXEC); killMsg.setType(MSG_TYPE_REQUEST); killMsg.set(MessageField::FIELD_SPROC_ID, procId); @@ -1265,6 +1336,7 @@ void SPELLserverCif::processMessage( const SPELLipcMessage& msg ) if (msgId == MessageId::MSG_ID_ADD_CLIENT) { LOG_INFO("Add controlling client"); + SPELLexecutor::instance().refreshStatus(); if (msg.hasField(MessageField::FIELD_GUI_CONTROL)) { getExecutorConfig().setHeadless( false ); @@ -1275,6 +1347,16 @@ void SPELLserverCif::processMessage( const SPELLipcMessage& msg ) else if (msgId == MessageId::MSG_ID_REMOVE_CLIENT) { LOG_WARN("Controlling client removed"); + + // In case of a (Warning) text to submit to GCS send it + if (msg.hasField(MessageField::FIELD_TEXT) && + msg.hasField(MessageField::FIELD_GUI_CONTROL) && + msg.get(MessageField::FIELD_GUI_CONTROL) == getExecutorConfig().getControlClient()) { + LOG_WARN("[CIF] " + msg.get(MessageField::FIELD_TEXT)); + SPELLexecutor::instance().getScheduler().raiseWarningEventToGCS(msg.get(MessageField::FIELD_TEXT)); + } + + SPELLexecutor::instance().refreshStatus(); switch(SPELLexecutor::instance().getStatus()) { case STATUS_FINISHED: @@ -1315,6 +1397,14 @@ void SPELLserverCif::processMessage( const SPELLipcMessage& msg ) { SPELLexecutor::instance().getVariableManager().setEnabled(false); } + else if (msgId == MessageId::MSG_ID_DRESOURCES_ENABLE) + { + SPELLexecutor::instance().getResourceManager().setEnabled(true); + } + else if (msgId == MessageId::MSG_ID_DRESOURCES_DISABLE) + { + SPELLexecutor::instance().getResourceManager().setEnabled(false); + } else { processMessageCommand(msg); @@ -1446,6 +1536,18 @@ SPELLipcMessage SPELLserverCif::processRequest( const SPELLipcMessage& msg ) { m_processor.processCheckVariablesEnabled(msg, response); } + else if (requestId == ExecutorMessages::REQ_GET_RESOURCES) + { + m_processor.processGetResources(msg, response); + } + else if (requestId == ExecutorMessages::REQ_CHANGE_RESOURCE) + { + m_processor.processChangeResource(msg, response); + } + else if (requestId == ExecutorMessages::REQ_DRESOURCES_ENABLED) + { + m_processor.processCheckResourcesEnabled(msg, response); + } else if (requestId == ExecutorMessages::REQ_GET_DICTIONARY) { m_processor.processGetDictionary(msg, response); @@ -1468,6 +1570,20 @@ void SPELLserverCif::processConnectionError( int clientKey, std::string error, s { if (!isClosing()) { + // send an alert to the GCS if context disconnect in transitionning states + switch(SPELLexecutor::instance().getStatus()) + { + case STATUS_FINISHED: + case STATUS_PAUSED: + case STATUS_ABORTED: + case STATUS_ERROR: + case STATUS_PROMPT: + case STATUS_INTERRUPTED: + break; + default: + SPELLexecutor::instance().getScheduler().raiseWarningEventToGCS("Procedure lost SPELL Context, check and resume: "+ getProcId()); + } + m_ready = false; LOG_ERROR("IPC error: " + error + ": " + reason); SPELLexecutor::instance().pause(); @@ -1578,7 +1694,7 @@ void SPELLserverCif::waitPromptAnswer() // Finish the prompt state SPELLexecutor::instance().getScheduler().finishPrompt(); - + //SPELLexecutor::instance().getScheduler().wait(); m_promptMessage = VOID_MESSAGE; } @@ -1800,4 +1916,42 @@ void SPELLserverCif::removeSharedDataScopes() { THROW_EXCEPTION(response.get(MessageField::FIELD_ERROR),response.get(MessageField::FIELD_REASON), SPELL_ERROR_ENVIRONMENT); } + +} +SPELLipcMessage SPELLserverCif::getPingMessage() +{ + if (SPELLexecutor::instance().hasController()) { + SPELLcontrollerIF& controller = + SPELLexecutor::instance().getController(); + + SPELLstatusInfo st(controller.getStatus()); + st.condition = controller.getCondition(); + st.actionLabel = SPELLexecutor::instance().getUserAction().getLabel(); + st.actionEnabled = + SPELLexecutor::instance().getUserAction().isEnabled(); + + m_stMessage.set(MessageField::FIELD_EXEC_STATUS, + SPELLexecutorUtils::statusToString(st.status)); + completeMessage(m_stMessage); + + // Set message Condition information + if (st.condition.size() > 0) { + m_stMessage.set(MessageField::FIELD_CONDITION, st.condition); + } + + // Set message Action information + if (st.actionLabel != "") { + m_stMessage.set(MessageField::FIELD_ACTION_LABEL, st.actionLabel); + m_stMessage.set(MessageField::FIELD_ACTION_ENABLED, + st.actionEnabled ? + MessageValue::DATA_TRUE : MessageValue::DATA_FALSE); + } + + return m_stMessage; + } + else + { + return SPELLipcMessage(); + } + } diff --git a/lib/SPELL_CIFS/src/SPELLvariableBuffer.C b/lib/SPELL_CIFS/src/SPELLvariableBuffer.C index a0ff793..2517521 100644 --- a/lib/SPELL_CIFS/src/SPELLvariableBuffer.C +++ b/lib/SPELL_CIFS/src/SPELLvariableBuffer.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the WOV messages buffer // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CTX/Makefile.am b/lib/SPELL_CTX/Makefile.am index 9272c3c..8bb4801 100644 --- a/lib/SPELL_CTX/Makefile.am +++ b/lib/SPELL_CTX/Makefile.am @@ -1,51 +1,72 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -#libSPELL_EXC_la_CPPFLAGS = ${AM_CPPFLAGS} -DWITH_DEBUG - -lib_LTLIBRARIES= libSPELL_CTX.la -libSPELL_CTX_la_LDFLAGS = -version-info 3:0:0 -libSPELL_CTX_la_SOURCES = \ - src/SPELLclient.C \ - src/SPELLclientInterestList.C \ - src/SPELLclientIPC.C \ - src/SPELLclientManager.C \ - src/SPELLclientNotifier.C \ - src/SPELLcontext.C \ - src/SPELLdataHelper.C \ - src/SPELLexecutor.C \ - src/SPELLexecutorIPC.C \ - src/SPELLexecutorOperation.C \ - src/SPELLexecutorManager.C \ - src/SPELLexecutorModel.C \ - src/SPELLlistenerIPC.C \ - src/SPELLpersistencyTable.C \ - src/SPELLsharedData.C \ - src/SPELLdataTable.C - - \ No newline at end of file +lib_LTLIBRARIES = libSPELL_CTX_v2.la libSPELL_CTX_v3.la + +libSPELL_CTX_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_CTX_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_CTX_v2_la_SOURCES = src/SPELLclient.C \ + src/SPELLclientInterestList.C \ + src/SPELLclientIPC.C \ + src/SPELLclientManager.C \ + src/SPELLclientNotifier.C \ + src/SPELLcontext.C \ + src/SPELLdataHelper.C \ + src/SPELLexecutor.C \ + src/SPELLexecutorIPC.C \ + src/SPELLexecutorOperation.C \ + src/SPELLexecutorManager.C \ + src/SPELLexecutorModel.C \ + src/SPELLservice.C \ + src/SPELLserviceManager.C \ + src/SPELLlistenerIPC.C \ + src/SPELLpersistencyTable.C \ + src/SPELLsharedData.C \ + src/SPELLdataTable.C + + +libSPELL_CTX_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_CTX_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_CTX_v3_la_SOURCES = src/SPELLclient.C \ + src/SPELLclientInterestList.C \ + src/SPELLclientIPC.C \ + src/SPELLclientManager.C \ + src/SPELLclientNotifier.C \ + src/SPELLcontext.C \ + src/SPELLdataHelper.C \ + src/SPELLexecutor.C \ + src/SPELLexecutorIPC.C \ + src/SPELLexecutorOperation.C \ + src/SPELLexecutorManager.C \ + src/SPELLexecutorModel.C \ + src/SPELLservice.C \ + src/SPELLserviceManager.C \ + src/SPELLlistenerIPC.C \ + src/SPELLpersistencyTable.C \ + src/SPELLsharedData.C \ + src/SPELLdataTable.C + \ No newline at end of file diff --git a/lib/SPELL_CTX/src/SPELLclient.C b/lib/SPELL_CTX/src/SPELLclient.C index e77382b..5b24327 100644 --- a/lib/SPELL_CTX/src/SPELLclient.C +++ b/lib/SPELL_CTX/src/SPELLclient.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the client // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -179,6 +179,10 @@ SPELLipcMessage SPELLclient::processRequestFromClient( const SPELLipcMessage& ms { resp = SPELLcontext::instance().getExecutorList( msg ); } + else if (msg.getId() == ContextMessages::REQ_SERVICE_LIST) + { + resp = SPELLcontext::instance().getServiceList( msg ); + } else if (msg.getId() == ContextMessages::REQ_PROC_PROP) { resp = SPELLcontext::instance().getProcedureProperties( msg ); @@ -191,6 +195,10 @@ SPELLipcMessage SPELLclient::processRequestFromClient( const SPELLipcMessage& ms { resp = SPELLcontext::instance().getExecutorInfo( msg ); } + else if (msg.getId() == ContextMessages::REQ_SERVICE_INFO) + { + resp = SPELLcontext::instance().getServiceInfo( msg ); + } else if (msg.getId() == ContextMessages::REQ_CLIENT_INFO) { resp = SPELLcontext::instance().getClientInfo( msg ); diff --git a/lib/SPELL_CTX/src/SPELLclientIPC.C b/lib/SPELL_CTX/src/SPELLclientIPC.C index 4095686..56ba56f 100644 --- a/lib/SPELL_CTX/src/SPELLclientIPC.C +++ b/lib/SPELL_CTX/src/SPELLclientIPC.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the client IPC interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -47,8 +47,11 @@ //============================================================================= SPELLclientIPC::SPELLclientIPC() : SPELLipcInterfaceListener(), +#ifdef WITH_KUBERNETES + m_ipc("CTX-TO-GUI", 888, 19888) +#else m_ipc("CTX-TO-GUI", 888, 0) - +#endif { SPELLclientManager::instance().setIPC(this); } @@ -133,6 +136,7 @@ void SPELLclientIPC::unregisterInterest( int clientKey, SPELLclientListener* lis //============================================================================= void SPELLclientIPC::processMessage( const SPELLipcMessage& msg ) { + LOG_INFO("[CLTRCV] Received message from client " + ISTR(msg.getKey()) + ": " + msg.getId() + " for ("+msg.get("User")+")"); // Get the peer key int clientKey = msg.getKey(); @@ -148,8 +152,9 @@ void SPELLclientIPC::processMessage( const SPELLipcMessage& msg ) //============================================================================= SPELLipcMessage SPELLclientIPC::processRequest( const SPELLipcMessage& msg ) { + if (msg.getId() != ContextMessages::REQ_CURRENT_TIME) + LOG_INFO("[CLTRCV] Received request from client " + ISTR(msg.getKey()) + ": " + msg.getId() + " for ("+msg.get("User")+")"); int clientKey = msg.getKey(); - DEBUG("[CLTRCV] Received request from client: " + msg.getId()); SPELLipcMessage resp = VOID_MESSAGE; SPELLclientInterestList* list = getClientInterestList(clientKey); // If the message is a login message, redirect it to the client manager @@ -193,7 +198,12 @@ SPELLipcMessage SPELLclientIPC::processRequest( const SPELLipcMessage& msg ) } else { - LOG_ERROR("No listeners for client " + ISTR(clientKey) + " to distribute request: " + msg.getId()); + if (msg.getId() == ContextMessages::REQ_CURRENT_TIME) { + resp = SPELLcontext::instance().getCurrentTime(msg); + return resp; + } else { + LOG_ERROR("No listeners for client " + ISTR(clientKey) + " to distribute request: " + msg.getId()); + } } return resp; } diff --git a/lib/SPELL_CTX/src/SPELLclientInterestList.C b/lib/SPELL_CTX/src/SPELLclientInterestList.C index 0d8ba7f..df78699 100644 --- a/lib/SPELL_CTX/src/SPELLclientInterestList.C +++ b/lib/SPELL_CTX/src/SPELLclientInterestList.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the client interest list // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CTX/src/SPELLclientManager.C b/lib/SPELL_CTX/src/SPELLclientManager.C index f5fd20c..48ca00d 100644 --- a/lib/SPELL_CTX/src/SPELLclientManager.C +++ b/lib/SPELL_CTX/src/SPELLclientManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Client manager implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -29,12 +29,15 @@ #include "SPELL_CTX/SPELLclientManager.H" #include "SPELL_CTX/SPELLclient.H" #include "SPELL_CTX/SPELLdataHelper.H" +#include "SPELL_CTX/SPELLexecutorManager.H" // Project includes -------------------------------------------------------- #include "SPELL_SYN/SPELLmonitor.H" #include "SPELL_UTIL/SPELLlog.H" #include "SPELL_UTIL/SPELLutils.H" #include "SPELL_IPC/SPELLipc_Context.H" #include "SPELL_IPC/SPELLipc_Executor.H" +#include "SPELL_UTIL/SPELLerror.H" +#include "SPELL_PRD/SPELLprocedureManager.H" // System includes --------------------------------------------------------- @@ -46,7 +49,7 @@ SPELLclientManager* SPELLclientManager::s_instance = NULL; //============================================================================= SPELLclientManager::SPELLclientManager() { - + m_ipc = NULL; } //============================================================================= @@ -98,7 +101,7 @@ void SPELLclientManager::clientLogin( int clientKey, const std::string& host ) SPELLclient* client = new SPELLclient( clientKey, host, *m_ipc ); m_clientMap.insert( std::make_pair( clientKey, client )); - DEBUG("[CMGR] Client logged in: " + ISTR(clientKey)); + LOG_INFO("[CMGR] Client logged in: " + ISTR(clientKey) + " running on " + host); notifyClientOperation( clientKey, host, CLIENT_OP_LOGIN ); } @@ -118,6 +121,29 @@ void SPELLclientManager::clientLogout( int clientKey ) if (it != m_clientMap.end()) { SPELLclient* client = it->second; + + SPELLclient::ProcedureList procs = client->getProcedures(); + SPELLclient::ProcedureList::iterator pit; + for ( pit = procs.begin(); pit != procs.end(); pit++ ) + { + try + { + SPELLexecutor* exec = SPELLexecutorManager::instance().getExecutor( pit->first ); + if (pit->second == CLIENT_MODE_CONTROL ) + { + SPELLclientManager::instance().removeExecutorController(client, exec, true, true); + LOG_WARN("Client " + ISTR(clientKey) + " stop controlling executor " + exec->getModel().getInstanceId()); + } + else + { + SPELLclientManager::instance().stopMonitorExecutor(client, exec); + LOG_INFO("Client " + ISTR(clientKey) + " stop monitoring executor " + exec->getModel().getInstanceId()); + } + } + catch(SPELLcoreException& ex){}; + } + DEBUG("Client " + ISTR(clientKey) + " logged out, stopping procedures done"); + std::string host = client->getClientHost(); LOG_INFO("Client logged out: " + ISTR(clientKey) + ":" + host); delete client; @@ -141,6 +167,27 @@ void SPELLclientManager::clientLost( int clientKey ) if (it != m_clientMap.end()) { SPELLclient* client = it->second; + SPELLclient::ProcedureList procs = client->getProcedures(); + SPELLclient::ProcedureList::iterator pit; + for ( pit = procs.begin(); pit != procs.end(); pit++ ) + { + try + { + SPELLexecutor* exec = SPELLexecutorManager::instance().getExecutor( pit->first ); + if (pit->second == CLIENT_MODE_CONTROL ) + { + SPELLclientManager::instance().removeExecutorController(client, exec, true, true); + LOG_WARN("Client lost:" + ISTR(clientKey) + " stop controlling executor " + exec->getModel().getInstanceId()); + } + else + { + SPELLclientManager::instance().stopMonitorExecutor(client, exec); + LOG_INFO("Client lost:" + ISTR(clientKey) + " stop monitoring executor " + exec->getModel().getInstanceId()); + } + } + catch(SPELLcoreException& ex){}; + } + std::string host = client->getClientHost(); LOG_WARN("Client lost: " + ISTR(clientKey) + ":" + host); delete client; @@ -244,6 +291,8 @@ void SPELLclientManager::stopMonitorExecutor( SPELLclient* client, SPELLexecutor } } + + //============================================================================= // METHOD: SPELLclientManager:: //============================================================================= @@ -262,6 +311,11 @@ std::list SPELLclientManager::getMonitoringClients( const std::stri monitoring.push_back(id); } } + if (monitoring.empty() && procId.find("#") != std::string::npos) + { + std::string newId = noInstanceId(procId); + return getMonitoringClients(newId); + } return monitoring; } @@ -282,6 +336,11 @@ std::list SPELLclientManager::getMonitoringClientsKeys( const std::string& keys.push_back(notifier->getClientKey()); } } + if (keys.empty() && procId.find("#") != std::string::npos) + { + std::string newId = noInstanceId(procId); + return getMonitoringClientsKeys(newId); + } return keys; } @@ -316,9 +375,9 @@ void SPELLclientManager::notifyClientOperation( int clientKey, notification.set( MessageField::FIELD_GUI_KEY, ISTR(clientKey)); notification.set( MessageField::FIELD_CLOP, SPELLdataHelper::clientOperationToString(operation) ); notification.set( MessageField::FIELD_HOST, host ); - ClientMap::const_iterator it; - for( it = m_clientMap.begin(); it != m_clientMap.end(); it++ ) + ClientMap clientMap = m_clientMap; + for( it = clientMap.begin(); it != clientMap.end(); it++ ) { SPELLclient* client = it->second; if (client->getClientKey() == clientKey) continue; @@ -336,7 +395,8 @@ void SPELLclientManager::notifyClients( const SPELLipcMessage& msg ) SPELLmonitor m(m_clientLock); DEBUG("Notify clients IN"); ClientMap::const_iterator it; - for( it = m_clientMap.begin(); it != m_clientMap.end(); it++ ) + ClientMap clientMap = m_clientMap; + for( it = clientMap.begin(); it != clientMap.end(); it++ ) { SPELLclient* client = it->second; DEBUG(" - client " + ISTR(it->first)); @@ -355,7 +415,8 @@ void SPELLclientManager::notifyMonitoringClients( const SPELLipcMessage& msg ) DEBUG("Notify monitoring clients IN"); ClientMap::const_iterator it; int controllingKey = msg.getKey(); - for( it = m_clientMap.begin(); it != m_clientMap.end(); it++ ) + ClientMap clientMap = m_clientMap; + for( it = clientMap.begin(); it != clientMap.end(); it++ ) { SPELLclient* client = it->second; if (it->first == controllingKey) continue; diff --git a/lib/SPELL_CTX/src/SPELLclientNotifier.C b/lib/SPELL_CTX/src/SPELLclientNotifier.C index e783c75..af069c0 100644 --- a/lib/SPELL_CTX/src/SPELLclientNotifier.C +++ b/lib/SPELL_CTX/src/SPELLclientNotifier.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the client notifier // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CTX/src/SPELLcontext.C b/lib/SPELL_CTX/src/SPELLcontext.C index 4383e40..966945c 100644 --- a/lib/SPELL_CTX/src/SPELLcontext.C +++ b/lib/SPELL_CTX/src/SPELLcontext.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the context main class // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -26,11 +26,12 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // Local includes ---------------------------------------------------------- +#include "SPELL_PRD/SPELLservicePython.H" #include "SPELL_CTX/SPELLcontext.H" #include "SPELL_CTX/SPELLclientManager.H" #include "SPELL_CTX/SPELLexecutorManager.H" +#include "SPELL_CTX/SPELLserviceManager.H" #include "SPELL_CTX/SPELLdataHelper.H" -// Project includes -------------------------------------------------------- #include "SPELL_SYN/SPELLmonitor.H" #include "SPELL_WRP/SPELLconstants.H" #include "SPELL_WRP/SPELLdriverManager.H" @@ -46,9 +47,9 @@ #include "SPELL_IPC/SPELLipc_Context.H" #include "SPELL_IPC/SPELLipc_Listener.H" #include "SPELL_SDB/SPELLdatabaseFactory.H" +#include "SPELL_WRP/SPELLdatabaseManager.H" // System includes --------------------------------------------------------- - // DEFINES ///////////////////////////////////////////////////////////////// #define NAME std::string("[CTX] ") @@ -56,7 +57,26 @@ SPELLcontext* SPELLcontext::s_instance = NULL; const std::string USE_DRIVER_TIME = "UseDriverTime"; - +extern "C" { + void checkService(std::string& serviceId) { + SPELLsafeThreadOperations ops("checkService"); + SPELLserviceManager::ServiceList runningServices = SPELLserviceManager::instance().getServiceList(); + for(SPELLserviceManager::ServiceList::iterator it = runningServices.begin();it != runningServices.end();++it) + { + //LOG_INFO("Service " + (*it) + " found!"); + if (*it == serviceId) + { + SPELLservice* service = SPELLserviceManager::instance().getService(serviceId); + if (service && service->isActive()) + { + return; + } + } + } + + SPELLcontext::instance().openService(serviceId); + } +} //============================================================================= // CONSTRUCTOR: SPELLcontext::SPELLcontext() //============================================================================= @@ -100,6 +120,7 @@ void SPELLcontext::start( const SPELLcontextConfiguration& config ) // Setup the configuration SPELLconfiguration::instance().loadConfig(m_config.configFile); + // Configure the time format if defined in configuration std::string format = SPELLconfiguration::instance().getCommonParameter("TdsTimeFormat"); if (format.length()!=0) @@ -126,8 +147,11 @@ void SPELLcontext::start( const SPELLcontextConfiguration& config ) m_executorDefaults.setPromptWarningDelay( STRI(srvConfig.getExecutorParameter(ExecutorConstants::PromptDelay)) ); m_executorDefaults.setByStep( srvConfig.getExecutorParameter(ExecutorConstants::ByStep) == ExecutorConstants::TRUE_VALUE ); m_executorDefaults.setForceTcConfirm( srvConfig.getExecutorParameter(ExecutorConstants::ForceTcConfirm) == ExecutorConstants::TRUE_VALUE ); + m_executorDefaults.setForcePromptConfirm( srvConfig.getExecutorParameter(ExecutorConstants::ForcePromptConfirm) == ExecutorConstants::TRUE_VALUE ); m_executorDefaults.setSaveStateMode( srvConfig.getExecutorParameter(ExecutorConstants::SaveStateMode) ); m_executorDefaults.setWatchVariables( srvConfig.getExecutorParameter(ExecutorConstants::WatchVariables) == ExecutorConstants::ENABLED ); + m_executorDefaults.setResourcesDisplay( srvConfig.getExecutorParameter(ExecutorConstants::ResourcesDisplay) == ExecutorConstants::ENABLED ); + m_executorDefaults.setLocalResourcesSupported( srvConfig.getExecutorParameter(ExecutorConstants::LocalResourcesSupported) == ExecutorConstants::TRUE_VALUE ); m_executorDefaults.setMaxVerbosity( STRI(srvConfig.getExecutorParameter(ExecutorConstants::MaxVerbosity)) ); m_executorDefaults.setBrowsableLibStr( srvConfig.getExecutorParameter(ExecutorConstants::BrowsableLib) ); @@ -141,11 +165,17 @@ void SPELLcontext::start( const SPELLcontextConfiguration& config ) if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ByStep) != "" ) m_executorDefaults.setByStep( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ByStep) == ExecutorConstants::TRUE_VALUE ); if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ForceTcConfirm) != "" ) - m_executorDefaults.setForceTcConfirm( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ForceTcConfirm) == ExecutorConstants::TRUE_VALUE ); + m_executorDefaults.setForceTcConfirm( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ForceTcConfirm) == ExecutorConstants::TRUE_VALUE ); + if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ForcePromptConfirm) != "" ) + m_executorDefaults.setForcePromptConfirm( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ForcePromptConfirm) == ExecutorConstants::TRUE_VALUE ); if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::SaveStateMode) != "" ) m_executorDefaults.setSaveStateMode( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::SaveStateMode) ); if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::SaveStateMode) != "" ) m_executorDefaults.setWatchVariables( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::WatchVariables) == ExecutorConstants::ENABLED ); + if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ResourcesDisplay) != "" ) + m_executorDefaults.setResourcesDisplay( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::ResourcesDisplay) == ExecutorConstants::ENABLED ); + if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::LocalResourcesSupported) != "" ) + m_executorDefaults.setLocalResourcesSupported( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::LocalResourcesSupported) == ExecutorConstants::TRUE_VALUE ); if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::MaxVerbosity) != "" ) m_executorDefaults.setMaxVerbosity( STRI(srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::MaxVerbosity)) ); if( srvConfig.getContext(getContextName()).getExecutorParameter(ExecutorConstants::BrowsableLib) != "" ) @@ -160,30 +190,52 @@ void SPELLcontext::start( const SPELLcontextConfiguration& config ) // Initialize Python support (some client requests require Python API) SPELLpythonHelper::instance().initialize(); + SPELLsafePythonOperations ops("Context Init"); + SpellService_Install(); + + SPELLconfiguration::instance().loadPythonConfig(m_config.configFile); + if (SPELLconfiguration::instance().getContextParameter(USE_DRIVER_TIME) == "true") + { + // Setup the TIME driver for getCurrentTime (need to load Python config first) + SPELLdriverManager::instance().setup(getContextName(), "TIME"); + } + else + { + SPELLdriverManager::instance().setup(getContextName(), "CONFIG"); + } + + SPELLpythonHelper::instance().loadFramework(); + SPELLdatabaseManager::instance().loadBuiltinDatabases(); // Setup the procedure manager SPELLprocedureManager::instance().setup( getContextName() ); - // Setup the executor manager. This may take time to come back, if - // there are executors to reconnect to. The listener login message will be sent after this stage. - SPELLexecutorManager::instance().setup( getContextName() ); - - if (SPELLconfiguration::instance().getContextParameter(USE_DRIVER_TIME) == "true") - { - // Setup the TIME driver for getCurrentTime (need to load Python config first) - SPELLconfiguration::instance().loadPythonConfig(m_config.configFile); - SPELLdriverManager::instance().setup(getContextName(), "TIME"); - } // Setup the IPC interfaces to clients and listener. The listener one logs in. m_clientIPC.setup(); - m_listenerIPC.setup(); + m_listenerIPC.setup(); + + // Setup the executor manager. This may take time to come back, if + // there are executors to reconnect to. The listener login message will be sent after this stage. + SPELLexecutorManager::instance().setup(getContextName()); + SPELLserviceManager::instance().setup(getContextName()); - LOG_INFO("Context " + getContextName() + " ready" ); + LOG_INFO("Context " + getContextName() + " ready" ); // SPELLpythonHelper::initialize has acquired the GIL. Release it so // that operations in other threads (messages) can use it too. - PyEval_ReleaseLock(); + + + std::list serviceList = SPELLprocedureManager::instance().getServiceList(); + std::list::iterator it = serviceList.begin(); + for (; it != serviceList.end(); ++it) + { + LOG_INFO("Starting Service " + (*it) + "." ); + //openService(*it); + SPELLpyService& exec = SPELLprocedureManager::instance().getService(*it); + exec.startWatchDog(&checkService); + } + } //============================================================================= @@ -291,8 +343,9 @@ SPELLipcMessage SPELLcontext::openExecutor( const SPELLipcMessage& msg, SPELLcli std::string theOriginId = "GUI"; if (msg.hasField( MessageField::FIELD_ORIGIN_ID )) { - theOriginId = msg.get( MessageField::FIELD_ORIGIN_ID ); + theOriginId = msg.get( MessageField::FIELD_ORIGIN_ID ) ; } + theOriginId = theOriginId + " ("+msg.get("User")+")"; SPELLexecutorStartupParams config(theInstanceId, SPELLutils::fileTimestamp() ); @@ -322,6 +375,18 @@ SPELLipcMessage SPELLcontext::openExecutor( const SPELLipcMessage& msg, SPELLcli try { + SPELLprocedureManager::ProcList services = SPELLprocedureManager::instance().getServiceList(); + SPELLprocedureManager::ProcList::const_iterator it = services.begin(); + for (;it != services.end();++it) + { + if (config.getProcId() == *it) + { + resp = SPELLipcHelper::createErrorResponse( ContextMessages::RSP_OPEN_EXEC, msg ); + resp.set( MessageField::FIELD_ERROR, "Cannot open selected service"); + resp.set( MessageField::FIELD_REASON, "The select entry is a service and can't be started manually." ); + return resp; + } + } SPELLexecutorManager::instance().startExecutor( config, controllingClient ); resp = SPELLipcHelper::createResponse( ContextMessages::RSP_OPEN_EXEC, msg ); @@ -367,6 +432,86 @@ SPELLipcMessage SPELLcontext::openExecutor( const SPELLipcMessage& msg, SPELLcli return resp; } +bool SPELLcontext::openService( const std::string serviceName ) +{ + LOG_INFO( "Requested opening new service" ); + + // Get the information about procedure instance identifiers. Note that if the procedure + // has been started by another one, there will be a parent instance identifier in the request message. + // + // - The identifier of the child (subprocedure) will be in SPROC_ID in this case, + // - The identifier of the parent will be in the PROC_ID. + std::string theInstanceId = ""; + std::string theParentInstanceId = ""; + // Note that the group id matches the instance id for standalone procedures, but it is + // the id of the original main procedure for all the children down a dependency tree. + std::string theGroupId = serviceName; + + // The following condition is for the case when we have a subprocedure being started. + int callingLine = 0; + theInstanceId = serviceName; + + // Origin ID is just informative for SPELL and it is optional + std::string theOriginId = "SERVICE"; + + SPELLserviceStartupParams config(theInstanceId, SPELLutils::fileTimestamp() ); + + config.setArguments(""); + config.setCondition(""); + std::string cMode = MessageValue::DATA_GUI_MODE_B; + config.setClientMode(SPELLdataHelper::clientModeFromString(cMode)); + std::string oMode = "1"; + config.setOpenMode(SPELLdataHelper::openModeFromString(oMode)); + config.setParentInstanceId(theParentInstanceId); + config.setParentCallingLine(callingLine); + config.setGroupId( theGroupId ); + config.setOriginId( theOriginId ); + + LOG_INFO(" - Instance Id : " + config.getInstanceId()); + LOG_INFO(" - Client mode : " + cMode); + LOG_INFO(" - Group Id : " + config.getGroupId()); + LOG_INFO(" - Origin Id : " + config.getOriginId()); + LOG_INFO(" - Calling line: " + ISTR(config.getParentCallingLine()) ); + LOG_INFO(" - Time Id : " + config.getTimeId()); + LOG_INFO(" - Proc Id : " + config.getProcId()); + LOG_INFO(" - Instance num: " + ISTR(config.getInstanceNum())); + LOG_INFO(" - Arguments : " + config.getArguments()); + LOG_INFO(" - Condition : " + config.getCondition()); + LOG_INFO(" - Open mode : " + oMode); + LOG_INFO(" - Parent : " + config.getParentInstanceId()); + + try + { + SPELLserviceManager::instance().startService( config ); + + SPELLservice* exec = SPELLserviceManager::instance().getService(config.getInstanceId()); + SPELLexecutorStatus initialStatus = exec->getStatus(); + + config.setClientMode(CLIENT_MODE_BACKGROUND); + + // Notify other clients + SPELLexecutorOperation op; + op.instanceId = config.getInstanceId(); + op.parentId = theParentInstanceId; + op.groupId = config.getGroupId(); + op.originId = config.getOriginId(); + op.clientKey = -1; + op.clientMode = config.getClientMode(); + op.status = initialStatus; + op.condition = config.getCondition(); + op.type = SPELLexecutorOperation::EXEC_OP_OPEN; + notifyExecutorOperation( op ); + + } + catch( SPELLcoreException& err ) + { + err.loggit(); + return false; + } + return true; +} + + //============================================================================= // METHOD: SPELLcontext:: //============================================================================= @@ -643,6 +788,8 @@ SPELLipcMessage SPELLcontext::getProcedureList( const SPELLipcMessage& msg ) SPELLprocedureManager::instance().refresh(); } } + + SPELLprocedureManager::ProcList list = SPELLprocedureManager::instance().getProcList(); std::string listStr = ""; @@ -652,6 +799,15 @@ SPELLipcMessage SPELLcontext::getProcedureList( const SPELLipcMessage& msg ) if (listStr != "") listStr += LIST_SEPARATOR; listStr += *it; } + + list = SPELLprocedureManager::instance().getServiceList(); + + for( it = list.begin(); it != list.end(); it++) + { + if (listStr != "") listStr += LIST_SEPARATOR; + listStr += *it + "|" + *it; + } + resp = SPELLipcHelper::createResponse( ContextMessages::RSP_PROC_LIST, msg ); resp.set( MessageField::FIELD_PROC_LIST, listStr ); return resp; @@ -750,13 +906,18 @@ SPELLipcMessage SPELLcontext::listFiles( const SPELLipcMessage& msg ) if (fileList.size()>0) fileList += LIST_SEPARATOR; fileList += "+" + *it; } + resp = SPELLipcHelper::createResponse( ContextMessages::RSP_LIST_FILES, msg ); + resp.set( MessageField::FIELD_FILE_LIST, fileList ); } else { LOG_ERROR("Path does not exist, cannot list files: '" + dir + "'"); + resp = SPELLipcHelper::createErrorResponse( ContextMessages::RSP_LIST_FILES, msg ); + resp.set( MessageField::FIELD_FILE_LIST, "" ); + resp.set( MessageField::FIELD_ERROR, "Cannot list files"); + resp.set( MessageField::FIELD_REASON, "Path does not exist: '" + dir + "'"); + resp.set( MessageField::FIELD_FATAL, "false"); } - resp = SPELLipcHelper::createResponse( ContextMessages::RSP_LIST_FILES, msg ); - resp.set( MessageField::FIELD_FILE_LIST, fileList ); return resp; } @@ -860,11 +1021,38 @@ SPELLipcMessage SPELLcontext::getExecutorList( const SPELLipcMessage& msg ) DEBUG(" found " + *it); listStr += *it; } + + SPELLserviceManager::ServiceList serviceList = SPELLserviceManager::instance().getServiceList(); + + SPELLserviceManager::ServiceList::const_iterator sit; + for( sit = serviceList.begin(); sit != serviceList.end(); sit++) + { + if (listStr != "") listStr += LIST_SEPARATOR; + DEBUG(" found " + *sit); + listStr += *sit + "#0"; + } + + resp = SPELLipcHelper::createResponse( ContextMessages::RSP_EXEC_LIST, msg ); resp.set( MessageField::FIELD_EXEC_LIST, listStr ); return resp; } +//============================================================================= +// METHOD: SPELLcontext:: +//============================================================================= +SPELLipcMessage SPELLcontext::getServiceList( const SPELLipcMessage& msg ) +{ + DEBUG( NAME + "Requested list of services"); + SPELLipcMessage resp = VOID_MESSAGE; + SPELLserviceManager::ServiceList list = SPELLserviceManager::instance().getServiceList(); + std::string listStr = ""; + + resp = SPELLipcHelper::createResponse( ContextMessages::RSP_SERVICE_LIST, msg ); + resp.set( MessageField::FIELD_EXEC_LIST, listStr ); + return resp; +} + //============================================================================= // METHOD: SPELLcontext:: //============================================================================= @@ -899,6 +1087,19 @@ SPELLipcMessage SPELLcontext::getExecutorInfo( const SPELLipcMessage& msg ) return resp; } +//============================================================================= +// METHOD: SPELLcontext:: +//============================================================================= +SPELLipcMessage SPELLcontext::getServiceInfo( const SPELLipcMessage& msg ) +{ + std::string instanceId = msg.get( MessageField::FIELD_PROC_ID ); + SPELLipcMessage resp = SPELLipcHelper::createResponse( ContextMessages::RSP_EXEC_INFO, msg); + DEBUG( NAME + "Requested service information: " + instanceId); + SPELLserviceManager::instance().buildServiceInfo( instanceId, resp ); + + return resp; +} + //============================================================================= // METHOD: SPELLcontext::getExecutorDefaults @@ -953,7 +1154,11 @@ SPELLipcMessage SPELLcontext::setExecutorDefaults( const SPELLipcMessage& msg ) LOG_INFO(" - TC confirm : " + msg.get(MessageField::FIELD_FORCE_TC_CONFIRM) ); m_executorDefaults.setForceTcConfirm( msg.get(MessageField::FIELD_FORCE_TC_CONFIRM) == ExecutorConstants::TRUE_VALUE ); } - + if( msg.hasField(MessageField::FIELD_FORCE_PROMPT_CONFIRM) ) + { + LOG_INFO(" - Prompt confirm: " + msg.get(MessageField::FIELD_FORCE_PROMPT_CONFIRM) ); + m_executorDefaults.setForcePromptConfirm( msg.get(MessageField::FIELD_FORCE_PROMPT_CONFIRM) == ExecutorConstants::TRUE_VALUE ); + } if( msg.hasField(MessageField::FIELD_SAVE_STATE_MODE) ) { LOG_INFO(" - Save state : " + msg.get(MessageField::FIELD_SAVE_STATE_MODE) ); @@ -966,6 +1171,18 @@ SPELLipcMessage SPELLcontext::setExecutorDefaults( const SPELLipcMessage& msg ) m_executorDefaults.setWatchVariables( msg.get(MessageField::FIELD_WATCH_VARIABLES) == ExecutorConstants::TRUE_VALUE ); } + if( msg.hasField(MessageField::FIELD_RESOURCES_DISPLAY) ) + { + LOG_INFO(" - Resource disp: " + msg.get(MessageField::FIELD_RESOURCES_DISPLAY) ); + m_executorDefaults.setResourcesDisplay( msg.get(MessageField::FIELD_RESOURCES_DISPLAY) == ExecutorConstants::TRUE_VALUE ); + } + + if( msg.hasField(MessageField::FIELD_LOCAL_RESOURCE_SUPPORTED) ) + { + LOG_INFO(" - Local resource support: " + msg.get(MessageField::FIELD_LOCAL_RESOURCE_SUPPORTED) ); + m_executorDefaults.setLocalResourcesSupported( msg.get(MessageField::FIELD_LOCAL_RESOURCE_SUPPORTED) == ExecutorConstants::TRUE_VALUE ); + } + if( msg.hasField(MessageField::FIELD_MAX_VERBOSITY) ) { LOG_INFO(" - Verbosity : " + msg.get(MessageField::FIELD_MAX_VERBOSITY) ); @@ -993,8 +1210,11 @@ void SPELLcontext::fillExecutorDefaults( SPELLipcMessage& msg ) msg.set( MessageField::FIELD_PROMPT_DELAY, ISTR(m_executorDefaults.getPromptWarningDelay()) ); msg.set( MessageField::FIELD_BY_STEP, (m_executorDefaults.isByStep() ? ExecutorConstants::TRUE_VALUE : ExecutorConstants::FALSE_VALUE) ); msg.set( MessageField::FIELD_FORCE_TC_CONFIRM, (m_executorDefaults.isForceTcConfirm() ? ExecutorConstants::TRUE_VALUE : ExecutorConstants::FALSE_VALUE) ); + msg.set( MessageField::FIELD_FORCE_PROMPT_CONFIRM, (m_executorDefaults.isForcePromptConfirm() ? ExecutorConstants::TRUE_VALUE : ExecutorConstants::FALSE_VALUE) ); msg.set( MessageField::FIELD_SAVE_STATE_MODE, m_executorDefaults.getSaveStateMode() ); msg.set( MessageField::FIELD_WATCH_VARIABLES, (m_executorDefaults.isWatchVariables() ? ExecutorConstants::TRUE_VALUE : ExecutorConstants::FALSE_VALUE) ); + msg.set( MessageField::FIELD_RESOURCES_DISPLAY, (m_executorDefaults.isResourcesDisplay() ? ExecutorConstants::TRUE_VALUE : ExecutorConstants::FALSE_VALUE) ); + msg.set( MessageField::FIELD_LOCAL_RESOURCE_SUPPORTED, (m_executorDefaults.isLocalResourcesSupported() ? ExecutorConstants::TRUE_VALUE : ExecutorConstants::FALSE_VALUE) ); msg.set( MessageField::FIELD_MAX_VERBOSITY, ISTR(m_executorDefaults.getMaxVerbosity()) ); msg.set( MessageField::FIELD_BROWSABLE_LIB, m_executorDefaults.getBrowsableLibStr() ); @@ -1083,11 +1303,58 @@ SPELLipcMessage SPELLcontext::attachExecutor( const SPELLipcMessage& msg, SPELLc } catch( SPELLcoreException& ex ) { - LOG_ERROR("Cannot attach: " + std::string(ex.what()) ); - resp = SPELLipcHelper::createErrorResponse( ContextMessages::RSP_ATTACH_EXEC, msg ); - resp.set( MessageField::FIELD_ERROR, "Cannot attach to executor"); - resp.set( MessageField::FIELD_REASON, ex.what()); - resp.set( MessageField::FIELD_FATAL, PythonConstants::True ); + + try + { + SPELLservice* exec = SPELLserviceManager::instance().getService(instanceId); + std::string groupId = exec->getModel().getGroupId(); + std::string originId = exec->getModel().getOriginId(); + + if (mode == CLIENT_MODE_CONTROL) + { + LOG_ERROR("Cannot take control of Service! Not supported for service: " + instanceId ); + resp = SPELLipcHelper::createErrorResponse( ContextMessages::RSP_ATTACH_EXEC, msg ); + resp.set( MessageField::FIELD_ERROR, "Cannot attach to service in controlling mode"); + resp.set( MessageField::FIELD_REASON, "Not supported for service" ); + resp.set( MessageField::FIELD_FATAL, PythonConstants::True ); + } + else + { + // Set the procedure controlling client on the client model + client->addProcedure(instanceId, CLIENT_MODE_MONITOR); + + LOG_INFO("Client " + ISTR(client->getClientKey()) + " monitoring service " + instanceId); + + resp = SPELLipcHelper::createResponse( ContextMessages::RSP_ATTACH_EXEC, msg ); + // Add the executor information + SPELLexecutorManager::instance().buildExecutorInfo( instanceId, resp ); + // Add the GUI list + SPELLclientManager::instance().completeMonitoringInfo( instanceId, resp ); + // Monitor the executor + SPELLclientManager::instance().startMonitorExecutor(client, exec ); + // Notify other clients + SPELLexecutorOperation op; + op.instanceId = instanceId; + op.parentId = exec->getParentInstanceId(); + op.groupId = groupId; + op.originId = originId; + op.clientKey = msg.getKey(); + op.clientMode = CLIENT_MODE_MONITOR; + op.status = exec->getStatus(); + op.type = SPELLexecutorOperation::EXEC_OP_ATTACH; + notifyExecutorOperation( op ); + } + } + catch( SPELLcoreException& ex ) + { + + + LOG_ERROR("Cannot attach: " + std::string(ex.what()) ); + resp = SPELLipcHelper::createErrorResponse( ContextMessages::RSP_ATTACH_EXEC, msg ); + resp.set( MessageField::FIELD_ERROR, "Cannot attach to executor"); + resp.set( MessageField::FIELD_REASON, ex.what()); + resp.set( MessageField::FIELD_FATAL, PythonConstants::True ); + } } return resp; } @@ -1183,6 +1450,45 @@ void SPELLcontext::executorLost( const std::string& instanceId ) catch(SPELLcoreException& ex){}; } +//============================================================================= +// METHOD: SPELLcontext:: +//============================================================================= +void SPELLcontext::serviceLost( const std::string& instanceId ) +{ + try + { + LOG_ERROR("Service lost: " + instanceId); + SPELLservice* exec = SPELLserviceManager::instance().getService( instanceId ); + std::string groupId = exec->getModel().getGroupId(); + std::string originId = exec->getModel().getOriginId(); + std::string parentId = exec->getModel().getParentInstanceId(); + std::string name = exec->getModel().getProcId(); + LOG_ERROR(" - Group ID : " + groupId); + LOG_ERROR(" - Origin ID: " + originId); + LOG_ERROR(" - Parent ID: " + parentId); + + LOG_ERROR("Notify clients about executor lost"); + + // Notify all GUIs in the system + SPELLexecutorOperation op; + op.instanceId = instanceId; + op.status = STATUS_ERROR; + op.groupId = groupId; + op.originId = originId; + op.parentId = parentId; + op.type = SPELLexecutorOperation::EXEC_OP_CRASH; + notifyExecutorOperation( op ); + + // Mark the model to be removed (cannot delete now, since this operation is + // triggered from the process model itself + SPELLserviceManager::instance().closeService( instanceId ); + +// openService(instanceId); + + } + catch(SPELLcoreException& ex){}; +} + //============================================================================= // METHOD: SPELLcontext:: //============================================================================= @@ -1228,12 +1534,39 @@ SPELLipcMessage SPELLcontext::detachExecutor( const SPELLipcMessage& msg, SPELLc } catch( SPELLcoreException& ex ) { - LOG_ERROR("Cannot detach: " + std::string(ex.what()) ); - resp = SPELLipcHelper::createErrorResponse( ContextMessages::RSP_DETACH_EXEC, msg ); - resp.set( MessageField::FIELD_ERROR, "Cannot detach from executor"); - resp.set( MessageField::FIELD_REASON, ex.what() ); - resp.set( MessageField::FIELD_FATAL, PythonConstants::True ); - } + try + { + SPELLservice* exec = SPELLserviceManager::instance().getService(instanceId); + std::string groupId = exec->getModel().getGroupId(); + std::string originId = exec->getModel().getOriginId(); + + LOG_INFO("Client " + ISTR(client->getClientKey()) + " detached from service " + instanceId); + + // To notify to other clients + SPELLexecutorOperation op; + op.instanceId = instanceId; + op.clientKey = msg.getKey(); + op.groupId = groupId; + op.originId = originId; + op.status = exec->getStatus(); + op.type = SPELLexecutorOperation::EXEC_OP_DETACH; + + resp = SPELLipcHelper::createResponse(ContextMessages::RSP_DETACH_EXEC, msg); + + SPELLclientManager::instance().stopMonitorExecutor(client, exec); + op.clientMode = CLIENT_MODE_MONITOR; + + + notifyExecutorOperation(op); + } catch (SPELLcoreException& ex) + { + LOG_ERROR("Cannot detach: " + std::string(ex.what())); + resp = SPELLipcHelper::createErrorResponse(ContextMessages::RSP_DETACH_EXEC, msg); + resp.set(MessageField::FIELD_ERROR, "Cannot detach from executor"); + resp.set(MessageField::FIELD_REASON, ex.what()); + resp.set(MessageField::FIELD_FATAL, PythonConstants::True); + } + } return resp; } diff --git a/lib/SPELL_CTX/src/SPELLdataHelper.C b/lib/SPELL_CTX/src/SPELLdataHelper.C index 33b7a5b..706aca8 100644 --- a/lib/SPELL_CTX/src/SPELLdataHelper.C +++ b/lib/SPELL_CTX/src/SPELLdataHelper.C @@ -5,7 +5,7 @@ // DESCRIPTION: Data handling utilities // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CTX/src/SPELLdataTable.C b/lib/SPELL_CTX/src/SPELLdataTable.C index c8d54a0..e09e8b8 100644 --- a/lib/SPELL_CTX/src/SPELLdataTable.C +++ b/lib/SPELL_CTX/src/SPELLdataTable.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the shared data tables // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CTX/src/SPELLexecutor.C b/lib/SPELL_CTX/src/SPELLexecutor.C index b2c7986..7a54a20 100644 --- a/lib/SPELL_CTX/src/SPELLexecutor.C +++ b/lib/SPELL_CTX/src/SPELLexecutor.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -42,53 +42,42 @@ #include "SPELL_UTIL/SPELLtime.H" #include "SPELL_IPC/SPELLipc_Executor.H" #include "SPELL_IPC/SPELLipc_Context.H" +#include "SPELL_PRD/SPELLprocedureManager.H" // System includes --------------------------------------------------------- -class LoginMonitor : public SPELLthread -{ -public: - LoginMonitor( SPELLexecutor& exec ) - : SPELLthread("login-" + exec.getModel().getInstanceId() ), - m_exec( exec ) - {} - void run() - { - SPELLtime checkStart; - for(;;) - { - SPELLtime now; - SPELLtime delta = now - checkStart; - if (delta.getSeconds()>120) - { - SPELLexecutorManager::instance().callback_executorNotReconnected( m_exec.getModel().getInstanceId() ); - return; - } - else - { - if (m_exec.isLoggedIn()) - { - SPELLexecutorManager::instance().callback_executorReconnected( m_exec.getModel().getInstanceId() ); - return; - } - else - { - usleep(30000); - } - } - } - } - -private: - SPELLexecutor& m_exec; -}; // DEFINES ///////////////////////////////////////////////////////////////// #define DEFAULT_START_TIMEOUT 20 #define DEFAULT_LOGIN_TIMEOUT 60 - +void LoginMonitor::run() +{ + SPELLtime checkStart; + for (;;) + { + SPELLtime now; + SPELLtime delta = now - checkStart; + if (delta.getSeconds() > 120) + { + SPELLexecutorManager::instance().callback_executorNotReconnected(m_exec.getModel().getInstanceId()); + return; + } + else + { + if (m_exec.isLoggedIn()) + { + SPELLexecutorManager::instance().callback_executorReconnected(m_exec.getModel().getInstanceId()); + return; + } + else + { + usleep(30000); + } + } + } +} //============================================================================= // CONSTRUCTOR: SPELLexecutor::SPELLexecutor() //============================================================================= @@ -112,6 +101,10 @@ SPELLexecutor::SPELLexecutor( const SPELLexecutorStartupParams& config, SPELLcli m_reconnecting = config.isReconnecting(); m_loggedIn = false; m_loginMonitor = NULL; + m_notAckNotifications = 0; + m_status = STATUS_UNINIT; + m_processStatus = PSTATUS_INITIAL; + } //============================================================================= @@ -150,6 +143,15 @@ void SPELLexecutor::start() THROW_EXCEPTION("Cannot launch executor", "No executor command defined in configuration", SPELL_ERROR_CONFIG); } + std::string pyVersion = SPELLprocedureManager::instance().getProperty(m_model.getProcId(),"PYTHON VERSION"); + if (pyVersion.empty()) + pyVersion = "2"; + LOG_INFO("Requested executor for python version " + pyVersion); + if (pyVersion.find("3") == 0) + { + command += "_v3"; + } + int startTimeout = -1; int loginTimeout = -1; std::string startTimeoutStr = SPELLconfiguration::instance().getContextParameter( SPELLcontextConfig::ExecutorStartTimeout ); @@ -177,7 +179,8 @@ void SPELLexecutor::start() { command += " -r " + m_model.getWsFilename(); } - + if (SPELLprocessManager::instance().checkProcessExist(m_model.getInstanceId() )) + SPELLprocessManager::instance().killProcess( m_model.getInstanceId() ); SPELLprocessManager::instance().clearProcess( m_model.getInstanceId() ); SPELLprocessManager::instance().startProcess( m_model.getInstanceId(), command ); @@ -355,7 +358,12 @@ SPELLipcMessage SPELLexecutor::sendRequestToExecutor( const SPELLipcMessage& msg SPELLipcMessage response = VOID_MESSAGE; if (processOk()) { - response = m_ipc.sendRequest( m_model.getInstanceId(), msg, 5000 ); + int timeout = 3000; + if (msg.getId() == ExecutorMessages::REQ_WVARIABLES_ENABLED) + { + timeout = 500; + } + response = m_ipc.sendRequest( m_model.getInstanceId(), msg, timeout ); } else { @@ -496,7 +504,7 @@ SPELLipcMessage SPELLexecutor::processRequestFromExecutor( SPELLipcMessage msg ) //============================================================================= // METHOD: SPELLexecutor:: //============================================================================= -void SPELLexecutor::processStarted( const std::string& identifier ) +void SPELLexecutor::processStarted( const std::string identifier ) { DEBUG("Callback - Executor process started: " + identifier); m_processStatus = PSTATUS_RUNNING; @@ -506,7 +514,7 @@ void SPELLexecutor::processStarted( const std::string& identifier ) //============================================================================= // METHOD: SPELLexecutor:: //============================================================================= -void SPELLexecutor::processFinished( const std::string& identifier, const int& retValue ) +void SPELLexecutor::processFinished( const std::string identifier, const int& retValue ) { LOG_INFO("Executor process finished: " + identifier); m_processStatus = PSTATUS_FINISHED; @@ -517,7 +525,7 @@ void SPELLexecutor::processFinished( const std::string& identifier, const int& r //============================================================================= // METHOD: SPELLexecutor:: //============================================================================= -void SPELLexecutor::processKilled( const std::string& identifier ) +void SPELLexecutor::processKilled( const std::string identifier ) { LOG_ERROR("Executor process killed: " + identifier); m_model.setStatus(STATUS_ERROR); @@ -547,7 +555,7 @@ void SPELLexecutor::processKilled( const std::string& identifier ) //============================================================================= // METHOD: SPELLexecutor:: //============================================================================= -void SPELLexecutor::processFailed( const std::string& identifier ) +void SPELLexecutor::processFailed( const std::string identifier ) { LOG_ERROR("Executor process failed startup: " + identifier); m_model.setStatus(STATUS_ABORTED); @@ -654,6 +662,11 @@ bool SPELLexecutor::isActive() DEBUG("Executor " + m_model.getInstanceId() + " active: " + SPELLdataHelper::executorStatusToString(getStatus())); return true; } + if (m_reconnecting) + { + DEBUG("Executor " + m_model.getInstanceId() + " is reconnecting: " + SPELLdataHelper::executorStatusToString(getStatus())); + return true; + } return false; } @@ -723,6 +736,9 @@ void SPELLexecutor::removeControllingClient( bool clientLost ) removeClient.set( MessageField::FIELD_PROC_ID, m_model.getInstanceId() ); removeClient.set( MessageField::FIELD_GUI_CONTROL, ISTR(cKey)); removeClient.set( MessageField::FIELD_GUI_CONTROL_HOST, cHost); + if (clientLost) { + removeClient.set( MessageField::FIELD_TEXT, "Procedure GUI disconnected from SPELL Context: " + m_model.getInstanceId()); + } sendMessageToExecutor(removeClient); LOG_INFO("Controlling client removed"); @@ -752,6 +768,8 @@ void SPELLexecutor::forcePause() } else if (getStatus() == STATUS_RUNNING) { + if (m_executorStatusEvent.isClear()) + return; cmd.id = CMD_PAUSE; LOG_WARN("Pausing procedure (force stop)"); command(cmd); @@ -974,12 +992,16 @@ SPELLipcMessage SPELLexecutor::executorLogin( const SPELLipcMessage& msg ) DEBUG("Received executor login: " + m_model.getInstanceId() ); m_loggedIn = true; + m_model.setStatus(STATUS_UNINIT); // Store the information given by the executor + LOG_INFO("Executor login provided: " + m_model.getInstanceId()); m_model.setIpcKey(msg.getKey()); - m_model.setStatus(SPELLdataHelper::stringToExecutorStatus( msg.get( MessageField::FIELD_EXEC_STATUS ))); + LOG_INFO(" IPC key: " + msg.getKey()); m_model.setAsRunFilename( msg.get( MessageField::FIELD_ASRUN_NAME )); + LOG_INFO(" ASRUN : " + msg.get( MessageField::FIELD_ASRUN_NAME )); m_model.setLogFilename( msg.get( MessageField::FIELD_LOG_NAME )); + LOG_INFO(" LOG : " + msg.get( MessageField::FIELD_LOG_NAME )); //TODO //m_model.wsFileName = msg.get( MessageField::FIELD_WS_NAME ); @@ -1110,7 +1132,7 @@ void SPELLexecutor::executorNotification( const SPELLipcMessage& nmsg ) // Force an additional status notification upon line notifications where // the stage title changes - if (getModel().getStageId() != id) + if (getModel().getStageId() != id || getModel().getStageTitle() != tl) { getModel().setStage(id,tl); SPELLexecutorOperation op; diff --git a/lib/SPELL_CTX/src/SPELLexecutorIPC.C b/lib/SPELL_CTX/src/SPELLexecutorIPC.C index 5543ca3..07d289c 100644 --- a/lib/SPELL_CTX/src/SPELLexecutorIPC.C +++ b/lib/SPELL_CTX/src/SPELLexecutorIPC.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor IPC interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -59,10 +59,10 @@ SPELLexecutorIPC::SPELLexecutorIPC( SPELLexecutor& controller, const SPELLexecut //============================================================================= SPELLexecutorIPC::~SPELLexecutorIPC() { - DEBUG("[EXCIPC] Destroying"); + //DEBUG("[EXCIPC] Destroying"); m_ipc.removeListener(); m_ipc.join(); - DEBUG("[EXCIPC] Destroyed"); + //DEBUG("[EXCIPC] Destroyed"); } //============================================================================= @@ -70,7 +70,7 @@ SPELLexecutorIPC::~SPELLexecutorIPC() //============================================================================= void SPELLexecutorIPC::setup() { - DEBUG("[EXCIPC] Setup"); + //DEBUG("[EXCIPC] Setup"); m_ipc.initialize(&*this); m_ipc.connect(); m_ipc.start(); @@ -83,13 +83,13 @@ void SPELLexecutorIPC::setup() void SPELLexecutorIPC::cleanup() { SPELLmonitor m(m_notifyLock); - DEBUG("[EXCIPC] Cleanup"); + //DEBUG("[EXCIPC] Cleanup"); m_connected = false; - DEBUG("[EXCIPC] Close executor connection"); + //DEBUG("[EXCIPC] Close executor connection"); m_ipc.removeListener(); - DEBUG("[EXCIPC] Perform disconnection"); + //DEBUG("[EXCIPC] Perform disconnection"); m_ipc.disconnect(); - DEBUG("[EXCIPC] Close executor connection done"); + //DEBUG("[EXCIPC] Close executor connection done"); } //============================================================================= @@ -138,21 +138,21 @@ SPELLipcMessage SPELLexecutorIPC::sendRequest( const std::string& executorId, co void SPELLexecutorIPC::processMessage( const SPELLipcMessage& msg ) { TICK_IN; - DEBUG("[EXCIPC] Received message from executor: " + msg.dataStr()); + //DEBUG("[EXCIPC] Received message from executor: " + msg.dataStr()); // Certain messages are for monitoring clients only if ((msg.getId() != MessageId::MSG_ID_PROMPT_START)&&(msg.getId() != MessageId::MSG_ID_PROMPT_END)) { - DEBUG("[EXCIPC] Forward message to controller"); + //DEBUG("[EXCIPC] Forward message to controller"); m_controller.processMessageFromExecutor(msg); } if (msg.getId() != ExecutorMessages::MSG_NOTIF_EXEC_CLOSE) { - DEBUG("[EXCIPC] Forward message to monitoring clients"); + //DEBUG("[EXCIPC] Forward message to monitoring clients"); notifyMessage(msg); } - DEBUG("[EXCIPC] Message processed"); + //DEBUG("[EXCIPC] Message processed"); TICK_OUT; } @@ -163,21 +163,21 @@ SPELLipcMessage SPELLexecutorIPC::processRequest( const SPELLipcMessage& msg ) { TICK_IN; - DEBUG("[EXCIPC] Received request from executor: " + msg.dataStr()); + //DEBUG("[EXCIPC] Received request from executor: " + msg.dataStr()); SPELLipcMessage resp = VOID_MESSAGE; - DEBUG("[EXCIPC] Forward request to controller"); + //DEBUG("[EXCIPC] Forward request to controller"); resp = m_controller.processRequestFromExecutor(msg); // Certain requests are to be sent to controlling clients only if ( shouldForwardToMonitoring(msg) ) { - DEBUG("[EXCIPC] Forward to monitoring clients"); + //DEBUG("[EXCIPC] Forward to monitoring clients"); // Notify request notifyRequest(msg); } - DEBUG("[EXCIPC] Request processed"); + //DEBUG("[EXCIPC] Request processed"); TICK_OUT; return resp; } @@ -205,6 +205,10 @@ bool SPELLexecutorIPC::shouldForwardToMonitoring( const SPELLipcMessage& msg ) { return false; } + else if ( requestId == ContextMessages::REQ_SERVICE_INFO ) + { + return false; + } else if ( requestId == ContextMessages::REQ_DEL_SHARED_DATA ) { return false; @@ -257,9 +261,9 @@ void SPELLexecutorIPC::notifyMessage( const SPELLipcMessage& msg ) { if (m_connected) { + SPELLmonitor m(m_notifyLock); for( NotifierMap::iterator it = m_notifiers.begin(); it != m_notifiers.end(); it++) { - SPELLmonitor m(m_notifyLock); if (it->second!=NULL) it->second->processMessageFromExecutor(msg); } } @@ -272,9 +276,9 @@ void SPELLexecutorIPC::notifyRequest( const SPELLipcMessage& msg ) { if (m_connected) { + SPELLmonitor m(m_notifyLock); for( NotifierMap::iterator it = m_notifiers.begin(); it != m_notifiers.end(); it++) { - SPELLmonitor m(m_notifyLock); if (it->second!=NULL) it->second->processRequestFromExecutor(msg); } } @@ -288,7 +292,7 @@ void SPELLexecutorIPC::registerExecutorNotifier( std::string id, SPELLexecutorLi SPELLmonitor m(m_notifyLock); LOG_INFO("[EXCIPC] #### Register notifier listener: " + id); NotifierMap::iterator it = m_notifiers.find(id); - if (it == m_notifiers.end()) + if (it == m_notifiers.end() || it->second != notifier) { m_notifiers[id] = notifier; } @@ -304,6 +308,8 @@ void SPELLexecutorIPC::deregisterExecutorNotifier( std::string id ) NotifierMap::iterator it = m_notifiers.find(id); if (it != m_notifiers.end()) { - m_notifiers.erase(it); + // Don't invalidate iterators by just setting the entry to NULL + //m_notifiers.erase(it); + m_notifiers[id] = NULL; } } diff --git a/lib/SPELL_CTX/src/SPELLexecutorManager.C b/lib/SPELL_CTX/src/SPELLexecutorManager.C index a8a194b..5e10bf3 100644 --- a/lib/SPELL_CTX/src/SPELLexecutorManager.C +++ b/lib/SPELL_CTX/src/SPELLexecutorManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -40,6 +40,7 @@ #include "SPELL_IPC/SPELLipc_Context.H" #include "SPELL_IPC/SPELLipc_Executor.H" #include "SPELL_WRP/SPELLconstants.H" +#include "SPELL_CTX/SPELLserviceManager.H" // System includes --------------------------------------------------------- @@ -79,7 +80,7 @@ SPELLexecutorManager& SPELLexecutorManager::instance() void SPELLexecutorManager::setup( const std::string& ctxName ) { m_contextName = ctxName; - m_persisTable = new SPELLpersistencyTable(ctxName); + m_persisTable = new SPELLpersistencyTable(ctxName + "_executors"); if (m_persisTable->load()) { reconnectExecutors(); @@ -199,6 +200,8 @@ void SPELLexecutorManager::callback_executorReconnected( const std::string& inst SPELLexecutor* exec = it->second; addInstanceNumber( exec->getModel().getProcId(), exec->getModel().getInstanceNum() ); addExecutorModel( exec ); + SPELLprocessManager::instance().attachProcess( exec->getModel().getInstanceId(), exec->getModel().getPID() ); + m_pendingLogin.erase(it); SPELLexecutorOperation op; @@ -318,7 +321,13 @@ void SPELLexecutorManager::deleteExecutorFiles( const SPELLexecutorStartupParams //============================================================================= void SPELLexecutorManager::addExecutorModel( SPELLexecutor* exec ) { - m_executors.insert( std::make_pair( exec->getModel().getInstanceId(), exec )); + const std::string id = exec->getModel().getInstanceId(); + if (m_executors.count(id) > 0) + { + delete m_executors[id]; + m_executors.erase(id); + } + m_executors.insert( std::make_pair(id, exec )); DEBUG("[EMGR] Added executor: '" + exec->getModel().getInstanceId() + "'"); m_persisTable->registerExecutor(exec->getModel().getInstanceId(), exec->getModel().getTimeId(), @@ -337,13 +346,17 @@ void SPELLexecutorManager::removeExecutorModel( const std::string& instanceId ) { DEBUG("[EMGR] Removing executor model: '" + instanceId + "'"); m_persisTable->deregisterExecutor(it->second->getModel().getInstanceId()); + m_lock.unlock(); delete it->second; + m_lock.lock(); + m_executors.erase(it); DEBUG("[EMGR] Removed executor: '" + instanceId + "'"); } else { - LOG_ERROR("[EMGR]: No executor to remove: '" + instanceId + "'"); + SPELLserviceManager::instance().removeServiceModel(instanceId); + //LOG_ERROR("[EMGR]: No executor to remove: '" + instanceId + "'"); } } @@ -353,6 +366,15 @@ void SPELLexecutorManager::removeExecutorModel( const std::string& instanceId ) std::string SPELLexecutorManager::getInstanceId( const std::string& procId ) { SPELLmonitor m(m_lock); + + // We never remove instance numbers but we can live with it - memory consumption + // is not big and do not need to be concerned with race conditions between add and get + // due to simultaneous requests from *different* clients (e.g .GUI and Parent Exec). + + // Nevertheless attempt to clean now, protected by the lock, before getting an instance id. + // Just clean if no executors running. + if (m_executors.size()==0) m_instances.clear(); + std::string instanceId = ""; InstanceMap::iterator it = m_instances.find(procId); if ( it == m_instances.end() ) @@ -363,9 +385,10 @@ std::string SPELLexecutorManager::getInstanceId( const std::string& procId ) { int instanceNum = 0; while(hasInstanceNumber(procId, instanceNum)) instanceNum++; + addInstanceNumber(procId, instanceNum); instanceId = procId + "#" + ISTR(instanceNum); } - DEBUG("[EMGR] Found instance id: '" + instanceId + "'"); + LOG_INFO("Assigned instance id: '" + instanceId + "'"); return instanceId; } @@ -401,43 +424,18 @@ bool SPELLexecutorManager::hasInstanceNumber( const std::string& procId, int ins return false; } -//============================================================================= -// METHOD: SPELLexecutorManager::removeInstanceNumber() -//============================================================================= -void SPELLexecutorManager::removeInstanceNumber( const std::string& procId, int instanceNum ) -{ - InstanceMap::iterator it = m_instances.find(procId); - if (it != m_instances.end()) - { - InstanceList::iterator toRemove = it->second.end(); - InstanceList::iterator lit; - for( lit = it->second.begin(); lit != it->second.end(); lit++ ) - { - if (*lit == instanceNum) - { - toRemove = lit; - break; - } - } - if (toRemove != it->second.end()) - { - it->second.erase(toRemove); - } - } -} - //============================================================================= // METHOD: SPELLexecutorManager::closeExecutor() //============================================================================= void SPELLexecutorManager::closeExecutor( const std::string& instanceId ) { SPELLmonitor m(m_lock); + try { // Will raise exception if not found SPELLexecutor* exec = getExecutor(instanceId); exec->close(); - removeInstanceNumber( exec->getModel().getProcId(), exec->getModel().getInstanceNum() ); removeExecutorModel( exec->getModel().getInstanceId() ); LOG_INFO("Executor successfully closed: " + instanceId); } @@ -466,7 +464,6 @@ void SPELLexecutorManager::killExecutor( const std::string& instanceId ) // Will raise exception if not found SPELLexecutor* exec = getExecutor(instanceId); exec->kill(); - removeInstanceNumber( exec->getModel().getProcId(), exec->getModel().getInstanceNum() ); removeExecutorModel( exec->getModel().getInstanceId() ); } catch(SPELLcoreException& ex) @@ -499,7 +496,6 @@ void SPELLexecutorManager::clearModels() try { SPELLexecutor* exec = getExecutor(*it); - removeInstanceNumber( exec->getModel().getProcId(), exec->getModel().getInstanceNum() ); removeExecutorModel( exec->getModel().getInstanceId() ); } catch(SPELLcoreException& ex) @@ -509,6 +505,9 @@ void SPELLexecutorManager::clearModels() } } m_toClear.clear(); + if (m_executors.empty()){ + m_instances.clear(); + } } } @@ -520,6 +519,8 @@ SPELLexecutor* SPELLexecutorManager::getExecutor( const std::string& instanceId //DEBUG("[EMGR] Search for executor: '" + instanceId + "'"); ExecutorMap::iterator it = m_executors.find(instanceId); if (it != m_executors.end()) return it->second; + const std::string id = noInstanceId(instanceId); + return SPELLserviceManager::instance().getService(id); THROW_EXCEPTION("Cannot get executor", "No such id: " + instanceId, SPELL_ERROR_EXECUTION ); return NULL; } @@ -591,10 +592,11 @@ void SPELLexecutorManager::buildExecutorInfo( const std::string& procId, SPELLip { LOG_INFO("Building executor information for " + procId ); - std::string procName = SPELLprocedureManager::instance().getProcName(procId); try { + std::string procName = SPELLprocedureManager::instance().getProcName(procId); + // Will raise exception if not found SPELLexecutor* exec = getExecutor(procId); @@ -675,23 +677,6 @@ void SPELLexecutorManager::buildExecutorInfo( const std::string& procId, SPELLip } catch(SPELLcoreException& ex) { - LOG_ERROR("No executor found to complete information"); - msg.set( MessageField::FIELD_PROC_ID, procId ); - msg.set( MessageField::FIELD_GROUP_ID, procId ); - msg.set( MessageField::FIELD_ORIGIN_ID, "???" ); - msg.set( MessageField::FIELD_PARENT_PROC, " " ); - msg.set( MessageField::FIELD_PARENT_PROC_LINE, " " ); - msg.set( MessageField::FIELD_PROC_NAME, procName ); - msg.set( MessageField::FIELD_ASRUN_NAME, " " ); - msg.set( MessageField::FIELD_EXEC_STATUS, SPELLdataHelper::executorStatusToString(STATUS_UNINIT) ); - msg.set( MessageField::FIELD_CONDITION, " " ); - msg.set( MessageField::FIELD_GUI_LIST, " " ); - msg.set( MessageField::FIELD_GUI_CONTROL, " " ); - msg.set( MessageField::FIELD_GUI_CONTROL_HOST, " " ); - msg.set( MessageField::FIELD_OPEN_MODE, " " ); - msg.set( MessageField::FIELD_STAGE_ID, " " ); - msg.set( MessageField::FIELD_STAGE_TL, " " ); - msg.set( MessageField::FIELD_CSP, " " ); - msg.set( MessageField::FIELD_CODE_NAME, " " ); + SPELLserviceManager::instance().buildServiceInfo( procId, msg ); } } diff --git a/lib/SPELL_CTX/src/SPELLexecutorModel.C b/lib/SPELL_CTX/src/SPELLexecutorModel.C index d0ec261..853a115 100644 --- a/lib/SPELL_CTX/src/SPELLexecutorModel.C +++ b/lib/SPELL_CTX/src/SPELLexecutorModel.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CTX/src/SPELLexecutorOperation.C b/lib/SPELL_CTX/src/SPELLexecutorOperation.C index 8e9698e..9fab2aa 100644 --- a/lib/SPELL_CTX/src/SPELLexecutorOperation.C +++ b/lib/SPELL_CTX/src/SPELLexecutorOperation.C @@ -5,7 +5,7 @@ // DESCRIPTION: Executor operation notifications // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_CTX/src/SPELLlistenerIPC.C b/lib/SPELL_CTX/src/SPELLlistenerIPC.C index 4dbc25b..ffb967c 100644 --- a/lib/SPELL_CTX/src/SPELLlistenerIPC.C +++ b/lib/SPELL_CTX/src/SPELLlistenerIPC.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the listener IPC interface // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -48,6 +48,7 @@ //============================================================================= SPELLlistenerIPC::SPELLlistenerIPC() { + m_ipc = NULL; } //============================================================================= diff --git a/lib/SPELL_CTX/src/SPELLpersistencyTable.C b/lib/SPELL_CTX/src/SPELLpersistencyTable.C index 63abf94..5e9c7fe 100644 --- a/lib/SPELL_CTX/src/SPELLpersistencyTable.C +++ b/lib/SPELL_CTX/src/SPELLpersistencyTable.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor persistency table // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -92,7 +92,7 @@ bool SPELLpersistencyTable::load() pers.timeId = tokens[1]; pers.parentId = parentId; pers.pid = STRI(tokens[4]); - m_data.insert( std::make_pair(tokens[0], pers) ); + m_data[tokens[0]] = pers; } else { @@ -145,7 +145,7 @@ void SPELLpersistencyTable::registerExecutor( const std::string& instanceId, con pers.timeId = timeId; pers.parentId = parentId; pers.pid = pid; - m_data.insert( std::make_pair(instanceId, pers) ); + m_data[instanceId] = pers; dumpToFile(); } } diff --git a/lib/SPELL_CTX/src/SPELLservice.C b/lib/SPELL_CTX/src/SPELLservice.C new file mode 100644 index 0000000..10f0edb --- /dev/null +++ b/lib/SPELL_CTX/src/SPELLservice.C @@ -0,0 +1,207 @@ +// ################################################################################ +// FILE : SPELLexecutor.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the executor manager +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +#include "SPELL_CTX/SPELLservice.H" +#include "SPELL_CTX/SPELLserviceManager.H" +#include "SPELL_CTX/SPELLdataHelper.H" +#include "SPELL_CTX/SPELLcontext.H" +#include "SPELL_PRD/SPELLprocedureManager.H" +// Project includes -------------------------------------------------------- +#include "SPELL_WRP/SPELLconstants.H" +#include "SPELL_CFG/SPELLconfiguration.H" +#include "SPELL_SYN/SPELLmonitor.H" +#include "SPELL_SYN/SPELLthread.H" +#include "SPELL_IPC/SPELLipcHelper.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLutils.H" +#include "SPELL_UTIL/SPELLerror.H" +#include "SPELL_UTIL/SPELLtime.H" +#include "SPELL_IPC/SPELLipc_Executor.H" +#include "SPELL_IPC/SPELLipc_Context.H" +// System includes --------------------------------------------------------- + +// DEFINES ///////////////////////////////////////////////////////////////// + +#define DEFAULT_START_TIMEOUT 20 +#define DEFAULT_LOGIN_TIMEOUT 60 + + +//============================================================================= +// CONSTRUCTOR: SPELLservice::SPELLservice() +//============================================================================= +SPELLservice::SPELLservice( const SPELLserviceStartupParams& config ) +: SPELLexecutor(config,NULL) +{ + +} + +//============================================================================= +// DESTRUCTOR: SPELLservice::~SPELLservice() +//============================================================================= +SPELLservice::~SPELLservice() +{ + +} + +//============================================================================= +// METHOD: SPELLservice::start() +//============================================================================= +void SPELLservice::start() +{ + LOG_INFO("Starting service " + m_model.getInstanceId()); + m_ipc.setup(); + m_model.setIpcPort( m_ipc.getPort() ); + m_executorLoggedInEvent.clear(); + + if (!m_reconnecting) + { + m_processStartedEvent.clear(); + std::string command; + SPELLpyService& service = SPELLprocedureManager::instance().getService(m_model.getProcId()); + int startTimeout = -1; + int loginTimeout = -1; + if (service.useCommandOverride()) + { + std::string startTimeoutStr = SPELLconfiguration::instance().getContextParameter(SPELLcontextConfig::ExecutorStartTimeout); + std::string loginTimeoutStr = SPELLconfiguration::instance().getContextParameter(SPELLcontextConfig::ExecutorLoginTimeout); + if (startTimeoutStr != "") + { + startTimeout = STRI(startTimeoutStr); + } + if (loginTimeoutStr != "") + { + loginTimeout = STRI(loginTimeoutStr); + } + if (startTimeout == -1) + startTimeout = DEFAULT_START_TIMEOUT; + if (loginTimeout == -1) + loginTimeout = DEFAULT_LOGIN_TIMEOUT; + + LOG_INFO("Using start timeout: " + ISTR(startTimeout) + " seconds"); + LOG_INFO("Using login timeout: " + ISTR(loginTimeout) + " seconds"); + + std::string config = m_model.getConfigFile(); + std::string contextName = m_model.getContextName(); + int ipcPort = m_ipc.getPort(); + std::string instanceId = m_model.getInstanceId(); + std::string warmstartFile = ""; + if (m_model.getWsFilename() != "") + { + warmstartFile = m_model.getWsFilename(); + } + command = service.getCommandOverride(startTimeout,loginTimeout,config,contextName,ipcPort,instanceId,warmstartFile); + } + else + { + command = SPELLconfiguration::instance().getContextParameter( SPELLcontextConfig::ServiceProgram ); + + if (command == "") + { + THROW_EXCEPTION("Cannot launch service", "No service command defined in configuration", SPELL_ERROR_CONFIG); + } + + + std::string startTimeoutStr = SPELLconfiguration::instance().getContextParameter( SPELLcontextConfig::ExecutorStartTimeout ); + std::string loginTimeoutStr = SPELLconfiguration::instance().getContextParameter( SPELLcontextConfig::ExecutorLoginTimeout ); + if (startTimeoutStr != "") + { + startTimeout = STRI(startTimeoutStr); + } + if (loginTimeoutStr != "") + { + loginTimeout = STRI(loginTimeoutStr); + } + if (startTimeout == -1) startTimeout = DEFAULT_START_TIMEOUT; + if (loginTimeout == -1) loginTimeout = DEFAULT_LOGIN_TIMEOUT; + + LOG_INFO("Using start timeout: " + ISTR(startTimeout) + " seconds"); + LOG_INFO("Using login timeout: " + ISTR(loginTimeout) + " seconds"); + + command += " -c " + m_model.getConfigFile(); + command += " -n " + m_model.getContextName(); + command += " -s " + ISTR(m_ipc.getPort()); + command += " -p " + m_model.getInstanceId(); // Parent id + command += " -w "; + if (m_model.getWsFilename() != "") + { + command += " -r " + m_model.getWsFilename(); + } + } + if (SPELLprocessManager::instance().checkProcessExist(m_model.getInstanceId() )) + SPELLprocessManager::instance().killProcess( m_model.getInstanceId() ); + SPELLprocessManager::instance().clearProcess( m_model.getInstanceId() ); + SPELLprocessManager::instance().startProcess( m_model.getInstanceId(), command ); + + DEBUG("Waiting executor process to begin"); + bool timedOut = m_processStartedEvent.wait( startTimeout * 1000 ); // milliseconds + if (timedOut) + { + THROW_EXCEPTION("Cannot launch executor", "Executor process did not begin in time", SPELL_ERROR_PROCESS); + } + DEBUG("Start event received"); + + m_processStatus = SPELLprocessManager::instance().getProcessStatus( m_model.getInstanceId() ); + if (m_processStatus == PSTATUS_RUNNING) + { + if (!m_loggedIn) + { + DEBUG("Executor process started, waiting for login"); + bool timeout = m_executorLoggedInEvent.wait( loginTimeout * 1000 ); // milliseconds + if (timeout) + { + THROW_EXCEPTION("Cannot launch executor", "Executor did not login in time", SPELL_ERROR_PROCESS); + } + } + DEBUG("Executor process logged in"); + m_model.setPID( SPELLprocessManager::instance().getProcessId( m_model.getInstanceId() )); + LOG_INFO("Executor started: " + m_model.getInstanceId() + " with pid " + ISTR(m_model.getPID())); + } + else if (m_processStatus == PSTATUS_FINISHED ) + { + THROW_EXCEPTION("Cannot launch executor", "Executor process finished too quickly", SPELL_ERROR_PROCESS); + } + else if (m_processStatus == PSTATUS_FAILED ) + { + THROW_EXCEPTION("Cannot launch executor", "Executor process failed to start", SPELL_ERROR_PROCESS); + } + else if (m_processStatus == PSTATUS_KILLED ) + { + THROW_EXCEPTION("Cannot launch executor", "Executor process crashed or was killed", SPELL_ERROR_PROCESS); + } + else + { + THROW_EXCEPTION("Cannot launch executor", "Executor process in unexpected state: " + SPELLprocessUtils::processStatusToString(m_processStatus), SPELL_ERROR_PROCESS); + } + } + else + { + m_loginMonitor = new LoginMonitor(*this); + m_loginMonitor->start(); + } +} + diff --git a/lib/SPELL_CTX/src/SPELLserviceManager.C b/lib/SPELL_CTX/src/SPELLserviceManager.C new file mode 100644 index 0000000..fbee805 --- /dev/null +++ b/lib/SPELL_CTX/src/SPELLserviceManager.C @@ -0,0 +1,676 @@ +// ################################################################################ +// FILE : SPELLserviceManager.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the executor manager +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +#include "SPELL_CTX/SPELLserviceManager.H" +#include "SPELL_CTX/SPELLservice.H" +#include "SPELL_CTX/SPELLclientManager.H" +#include "SPELL_CTX/SPELLdataHelper.H" +#include "SPELL_CTX/SPELLcontext.H" +// Project includes -------------------------------------------------------- +#include "SPELL_PRD/SPELLprocedureManager.H" +#include "SPELL_SYN/SPELLmonitor.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLutils.H" +#include "SPELL_UTIL/SPELLerror.H" +#include "SPELL_CFG/SPELLconfiguration.H" +#include "SPELL_IPC/SPELLipc_Context.H" +#include "SPELL_IPC/SPELLipc_Executor.H" +#include "SPELL_WRP/SPELLconstants.H" +// System includes --------------------------------------------------------- + + +// DEFINES ///////////////////////////////////////////////////////////////// +SPELLserviceManager* SPELLserviceManager::s_instance = NULL; + +//============================================================================= +// CONSTRUCTOR: SPELLexecutorManager::SPELLexecutorManager() +//============================================================================= +SPELLserviceManager::SPELLserviceManager() +{ + m_persisTable = NULL; +} + +//============================================================================= +// DESTRUCTOR: SPELLexecutorManager::~SPELLexecutorManager() +//============================================================================= +SPELLserviceManager::~SPELLserviceManager() +{ +} + +//============================================================================= +// STATIC: SPELLexecutorManager::instance() +//============================================================================= +SPELLserviceManager& SPELLserviceManager::instance() +{ + if (s_instance == NULL) + { + s_instance = new SPELLserviceManager(); + } + return *s_instance; +} + +//============================================================================= +// METHOD: SPELLexecutorManager::setup() +//============================================================================= +void SPELLserviceManager::setup( const std::string& ctxName ) +{ + m_contextName = ctxName; + m_persisTable = new SPELLpersistencyTable(ctxName + "_services"); + if (m_persisTable->load()) + { + reconnectServices(); + } +} + +//============================================================================= +// METHOD: SPELLexecutorManager::setup() +//============================================================================= +void SPELLserviceManager::cleanup() +{ + if (m_persisTable) delete m_persisTable; + for( ServiceMap::iterator it = m_pendingLogin.begin(); it != m_pendingLogin.end(); it++ ) + { + delete it->second; + } + m_pendingLogin.clear(); +} + +//============================================================================= +// METHOD: SPELLexecutorManager::startExecutor() +//============================================================================= +void SPELLserviceManager::startService( SPELLserviceStartupParams& config) +{ + DEBUG( "[SMGR] Start service TRY-IN"); + + SPELLmonitor m(m_lock); + DEBUG( "[SMGR] Start executor IN"); + config.setContextName(m_contextName); + config.setConfigFile(SPELLconfiguration::instance().getFile()); + + // Create and start the executor with the given configuration + + // If the open mode does not include VISIBLE, do not pass the controlling client + // This way we force the GUI to attach to a proc when it is started in non-visible + SPELLservice* exec = NULL; + + DEBUG( "[SMGR] Start executor in background"); + exec = new SPELLservice(config); + + try + { + LOG_INFO("Starting service " + exec->getModel().getInstanceId() ); + exec->start(); + + addInstanceNumber( exec->getModel().getProcId(), exec->getModel().getInstanceNum() ); + addServiceModel( exec ); + + LOG_INFO("Service ready"); + } + catch(SPELLcoreException& ex) + { + // Delete recovery files just created for the new executor file + deleteServiceFiles(config); + DEBUG( "[SMGR] Start service OUT"); + THROW_EXCEPTION("Failed to start service", ex.what(), SPELL_ERROR_PROCESS); + } + DEBUG( "[SMGR] Start executor OUT"); +} + +//============================================================================= +// METHOD: SPELLexecutorManager::reconnectExecutors() +//============================================================================= +void SPELLserviceManager::reconnectServices() +{ + DEBUG( "[SMGR] Reconnecting orphan services"); + SPELLmonitor m(m_lock); + std::vector ids = m_persisTable->getRegisteredExecutors(); + m_pendingLogin.clear(); + for(std::vector::iterator it = ids.begin(); it != ids.end(); it++) + { + std::string instanceId = *it; + SPELLexecutorPersistency pers = m_persisTable->getExecutorPersistency(instanceId); + + if (getpgid(pers.pid) < 0) + { + DEBUG( "[SMGR] PID " + ISTR(pers.pid) + " for service "+instanceId+" not found"); + continue; + } + + SPELLserviceStartupParams config( instanceId, pers.timeId, true ); + config.setParentInstanceId(pers.parentId); + config.setIpcPort(pers.ipcPort); + config.setPID(pers.pid); + + SPELLservice* exec = new SPELLservice(config); + try + { + LOG_WARN("Reconnecting service " + instanceId + " listening on port " + ISTR(pers.ipcPort) ); + exec->start(); + m_services.insert( std::make_pair( exec->getModel().getInstanceId(), exec )); + m_pendingLogin.insert( std::make_pair( instanceId, exec ) ); + } + catch(SPELLcoreException& ex) + { + deleteServiceFiles(config); + } + } + DEBUG( "[SMGR] Reconnect services done"); +} + +//============================================================================= +// METHOD: SPELLexecutorManager::callback_executorReconnected() +//============================================================================= +void SPELLserviceManager::callback_serviceReconnected( const std::string& instanceId ) +{ + LOG_WARN("#######################################"); + LOG_WARN("Service " + instanceId + " reconnected"); + LOG_WARN("#######################################"); + SPELLmonitor m(m_lock); + ServiceMap::iterator it = m_pendingLogin.find(instanceId); + if (it != m_pendingLogin.end()) + { + SPELLservice* exec = it->second; + addInstanceNumber( exec->getModel().getProcId(), exec->getModel().getInstanceNum() ); + addServiceModel( exec ); + m_pendingLogin.erase(it); + + SPELLexecutorOperation op; + op.instanceId = exec->getModel().getInstanceId(); + op.parentId = exec->getModel().getParentInstanceId(); + op.groupId = exec->getModel().getGroupId(); + op.originId = exec->getModel().getOriginId(); + op.status = exec->getModel().getStatus(); + op.type = SPELLexecutorOperation::EXEC_OP_OPEN; + + SPELLcontext::instance().notifyExecutorOperation( op ); + } +} + +//============================================================================= +// METHOD: SPELLexecutorManager::callback_executorNotReconnected() +//============================================================================= +void SPELLserviceManager::callback_serviceNotReconnected( const std::string& instanceId ) +{ + LOG_ERROR("#######################################"); + LOG_ERROR("Service " + instanceId + " failed to reconnect"); + LOG_ERROR("#######################################"); + SPELLmonitor m(m_lock); + ServiceMap::iterator it = m_pendingLogin.find(instanceId); + if (it != m_pendingLogin.end()) + { + m_persisTable->deregisterExecutor(it->second->getModel().getInstanceId()); + delete it->second; + m_pendingLogin.erase(it); + } +} + +////============================================================================= +//// METHOD: SPELLexecutorManager::recoverExecutor() +////============================================================================= +//void SPELLserviceManager::recoverExecutor( SPELLexecutorStartupParams& config, SPELLclient* controllingClient ) +//{ +// //TODO check maximum number of executors +// SPELLmonitor m(m_lock); +// config.setContextName(m_contextName); +// config.setConfigFile(SPELLconfiguration::instance().getFile()); +// +// // Create and start the executor with the given configuration +// +// // If the open mode does not include VISIBLE, do not pass the controlling client +// // This way we force the GUI to attach to a proc when it is started in non-visible +// SPELLexecutor* exec = new SPELLexecutor(config, controllingClient); +// try +// { +// LOG_INFO("Recovering executor " + exec->getModel().getInstanceId() ); +// exec->start(); +// +// addInstanceNumber( exec->getModel().getProcId(), exec->getModel().getInstanceNum() ); +// addExecutorModel( exec ); +// +// LOG_INFO("Executor ready"); +// } +// catch(SPELLcoreException& ex) +// { +// // Delete recovery files just created for the new executor file +// deleteExecutorFiles(config); +// THROW_EXCEPTION("Failed to recover executor", ex.what(), SPELL_ERROR_PROCESS); +// } +//} + +//============================================================================= +// METHOD: SPELLexecutorManager::deleteExecutorFiles() +//============================================================================= +void SPELLserviceManager::deleteServiceFiles( const SPELLserviceStartupParams& config ) +{ + SPELLcontextConfig& ctxConfig = SPELLconfiguration::instance().getContext(m_contextName); + + std::string fileName = config.getTimeId() + "_Service_" + config.getInstanceId(); + + LOG_INFO("Deleting service files: " + fileName ); + + // Delete warmstart files + std::string wsDataDir = SPELLutils::getSPELL_DATA() + PATH_SEPARATOR + ctxConfig.getLocationPath("ws"); + std::list files = SPELLutils::getFilesInDir(wsDataDir); + + for(std::list::const_iterator it = files.begin(); it != files.end(); it++) + { + std::string completeFilename = *it; + if (completeFilename.find(fileName) != std::string::npos) + { + std::string path = wsDataDir + PATH_SEPARATOR + completeFilename; + if (SPELLutils::pathExists(path)) + { + SPELLutils::deleteFile(path); + LOG_INFO("Delete " + path ); + } + } + } + + // Delete Asrun files + std::string arDataDir = SPELLutils::getSPELL_DATA() + PATH_SEPARATOR + ctxConfig.getLocationPath("ar"); + + files = SPELLutils::getFilesInDir(arDataDir); + + for(std::list::const_iterator it = files.begin(); it != files.end(); it++) + { + std::string completeFilename = *it; + if (completeFilename.find(fileName) != std::string::npos) + { + std::string path = arDataDir + PATH_SEPARATOR + completeFilename; + if (SPELLutils::pathExists(path)) + { + SPELLutils::deleteFile(path); + LOG_INFO("Delete " + path ); + } + } + } +} + +//============================================================================= +// METHOD: SPELLexecutorManager::addExecutorModel() +//============================================================================= +void SPELLserviceManager::addServiceModel( SPELLservice* exec ) +{ + const std::string id = exec->getModel().getInstanceId(); + if (m_services.count(id) > 0) + { + delete m_services[id]; + m_services.erase(id); + } + m_services.insert( std::make_pair( exec->getModel().getInstanceId(), exec )); + DEBUG("[SMGR] Added executor: '" + exec->getModel().getInstanceId() + "'"); + m_persisTable->registerExecutor(exec->getModel().getInstanceId(), + exec->getModel().getTimeId(), + exec->getModel().getParentInstanceId(), + exec->getModel().getIpcPort(), + exec->getModel().getPID() ); +} + +//============================================================================= +// METHOD: SPELLexecutorManager::removeExecutorModel() +//============================================================================= +void SPELLserviceManager::removeServiceModel( const std::string& instanceId ) +{ + ServiceMap::iterator it = m_services.find(instanceId); + if (it != m_services.end()) + { + DEBUG("[SMGR] Removing service model: '" + instanceId + "'"); + m_persisTable->deregisterExecutor(it->second->getModel().getInstanceId()); + delete it->second; + m_services.erase(it); + DEBUG("[SMGR] Removed service: '" + instanceId + "'"); + } + else + { + LOG_ERROR("[SMGR]: No service to remove: '" + instanceId + "'"); + } +} + +//============================================================================= +// METHOD: SPELLexecutorManager::getInstanceId() +//============================================================================= +std::string SPELLserviceManager::getInstanceId( const std::string& procId ) +{ + SPELLmonitor m(m_lock); + + // We never remove instance numbers but we can live with it - memory consumption + // is not big and do not need to be concerned with race conditions between add and get + // due to simultaneous requests from *different* clients (e.g .GUI and Parent Exec). + + // Nevertheless attempt to clean now, protected by the lock, before getting an instance id. + // Just clean if no executors running. + if (m_services.size()==0) m_instances.clear(); + + std::string instanceId = ""; + InstanceMap::iterator it = m_instances.find(procId); + if ( it == m_instances.end() ) + { + instanceId = procId; + } + else + { + int instanceNum = 0; + while(hasInstanceNumber(procId, instanceNum)) instanceNum++; + addInstanceNumber(procId, instanceNum); + instanceId = procId; + } + LOG_INFO("Assigned instance id: '" + instanceId + "'"); + return instanceId; +} + +//============================================================================= +// METHOD: SPELLexecutorManager::addInstanceNumber() +//============================================================================= +void SPELLserviceManager::addInstanceNumber( const std::string& procId, int instanceNum ) +{ + InstanceMap::iterator it = m_instances.find(procId); + if (it == m_instances.end()) + { + InstanceList list; + list.push_back(instanceNum); + m_instances.insert( std::make_pair( procId, list ) ); + } + else + { + it->second.push_back(instanceNum); + } +} + +//============================================================================= +// METHOD: SPELLexecutorManager::hasInstanceNumber() +//============================================================================= +bool SPELLserviceManager::hasInstanceNumber( const std::string& procId, int instanceNum ) +{ + InstanceMap::iterator it = m_instances.find(procId); + InstanceList::iterator lit; + for( lit = it->second.begin(); lit != it->second.end(); lit++ ) + { + if (*lit == instanceNum) return true; + } + return false; +} + +//============================================================================= +// METHOD: SPELLexecutorManager::closeExecutor() +//============================================================================= +void SPELLserviceManager::closeService( const std::string& instanceId ) +{ + SPELLmonitor m(m_lock); + try + { + // Will raise exception if not found + SPELLservice* exec = getService(instanceId); + exec->close(); + removeServiceModel( exec->getModel().getInstanceId() ); + LOG_INFO("Service successfully closed: " + instanceId); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("Unable to find service '" + instanceId + "', cannot close"); + } +} + +//============================================================================= +// METHOD: SPELLexecutorManager::executorLost() +//============================================================================= +void SPELLserviceManager::serviceLost( SPELLservice& service ) +{ + LOG_WARN( "[SMGR] Service lost: " + service.getModel().getInstanceId()); + clearService(service.getModel().getInstanceId()); +} + +//============================================================================= +// METHOD: SPELLexecutorManager::killExecutor() +//============================================================================= +void SPELLserviceManager::killService( const std::string& instanceId ) +{ + try + { + SPELLmonitor m(m_lock); + // Will raise exception if not found + SPELLservice* exec = getService(instanceId); + exec->kill(); + removeServiceModel( exec->getModel().getInstanceId() ); + } + catch(SPELLcoreException& ex) + { + THROW_EXCEPTION("Failed to kill Service '" + instanceId + "' ", ex.what(), SPELL_ERROR_PROCESS); + } +} + +//============================================================================= +// METHOD: SPELLexecutorManager::clearExecutor() +//============================================================================= +void SPELLserviceManager::clearService( const std::string& instanceId ) +{ + SPELLmonitor m(m_lock); + m_toClear.push_back(instanceId); +} + +//============================================================================= +// METHOD: SPELLexecutorManager::clearModels +//============================================================================= +void SPELLserviceManager::clearModels() +{ + if (m_toClear.size()>0) + { + IdentifierList::iterator it; + for( it = m_toClear.begin(); it != m_toClear.end(); it++) + { + ServiceMap::iterator eit = m_services.find(*it); + if (eit != m_services.end()) + { + try + { + SPELLservice* exec = getService(*it); + removeServiceModel( exec->getModel().getInstanceId() ); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("Failed to remove executor model"); + }; + } + } + m_toClear.clear(); + if (m_services.empty()){ + m_instances.clear(); + } + } +} + +//============================================================================= +// METHOD: SPELLexecutorManager::getExecutor() +//============================================================================= +SPELLservice* SPELLserviceManager::getService( const std::string& instanceId ) +{ + DEBUG("[SMGR] Search for service: '" + instanceId + "'"); + int idx = instanceId.find("#"); + std::string id = instanceId; + if (idx >= 0) + id = id.substr(0,idx); + ServiceMap::iterator it = m_services.find(id); + if (it != m_services.end()) return it->second; + THROW_EXCEPTION("Cannot get service", "No such id: " + instanceId, SPELL_ERROR_EXECUTION ); +} + +//============================================================================= +// METHOD: SPELLexecutorManager::killAll() +//============================================================================= +void SPELLserviceManager::killAll() +{ + DEBUG( "[SMGR] Kill all Services"); + ServiceList list = getServiceList(); + ServiceList::iterator lit; + for( lit = list.begin(); lit != list.end(); lit++) + { + try + { + killService(*lit); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("Could not kill service: " + ex.what()); + } + } + DEBUG( "[SMGR] Kill all services done"); +} + +//============================================================================= +// METHOD: SPELLexecutorManager::getNumActiveExecutors() +//============================================================================= +unsigned int SPELLserviceManager::getNumActiveServices() +{ + DEBUG( "[SMGR] Get number of active services TRY-IN"); + SPELLmonitor m(m_lock); + DEBUG( "[SMGR] Get number of active services IN"); + clearModels(); + ServiceMap::iterator it; + unsigned int numActive = 0; + for( it = m_services.begin(); it != m_services.end(); it++) + { + if (it->second->isActive()) numActive++; + } + DEBUG( "[SMGR] Get number of active services OUT"); + return numActive; +} + +//============================================================================= +// METHOD: SPELLexecutorManager::getExecutorList() +//============================================================================= +SPELLserviceManager::ServiceList SPELLserviceManager::getServiceList() +{ + SPELLmonitor m(m_lock); + clearModels(); + ServiceList ids; + ServiceMap::iterator it; + for( it = m_services.begin(); it != m_services.end(); it++) + { + ids.push_back(it->first); + } + //DEBUG( "[SMGR] Get service list OUT"); + return ids; +} + +//============================================================================= +// METHOD: SPELLexecutorManager::buildExecutorInfo() +//============================================================================= +void SPELLserviceManager::buildServiceInfo( const std::string& serviceId, SPELLipcMessage& msg ) +{ + LOG_INFO("Building service information for " + serviceId ); + + try + { + // Will raise exception if not found + std::string procId; + std::string::size_type idx = serviceId.find("#"); + if (idx != std::string::npos) + procId = serviceId.substr(0,idx); + else + procId = serviceId; + SPELLservice* exec = getService(procId); + SPELLpyService& service = SPELLprocedureManager::instance().getService(procId); + + msg.set( MessageField::FIELD_PROC_ID, procId); + msg.set( MessageField::FIELD_PARENT_PROC, exec->getModel().getParentInstanceId() ); + msg.set( MessageField::FIELD_PARENT_PROC_LINE, ISTR(exec->getModel().getParentCallingLine()) ); + + // The group id of a main (without parent) procedure is the same as proc id. + std::string groupId = exec->getModel().getGroupId(); + if (groupId == "") groupId = procId; + msg.set( MessageField::FIELD_GROUP_ID, groupId ); + // The origin id is optional + msg.set( MessageField::FIELD_ORIGIN_ID, exec->getModel().getOriginId() ); + msg.set( MessageField::FIELD_PROC_NAME, service.getName() + "#0"); + msg.set( MessageField::FIELD_ASRUN_NAME, exec->getModel().getAsRunFilename() ); + msg.set( MessageField::FIELD_LOG_NAME, exec->getModel().getLogFilename() ); + SPELLexecutorStatus st = exec->getStatus(); + msg.set( MessageField::FIELD_EXEC_STATUS, SPELLdataHelper::executorStatusToString(st) ); + msg.set( MessageField::FIELD_CONDITION, exec->getModel().getCondition() ); + msg.set( MessageField::FIELD_STAGE_ID, exec->getModel().getStageId() ); + msg.set( MessageField::FIELD_STAGE_TL, exec->getModel().getStageTitle() ); + msg.set( MessageField::FIELD_CSP, exec->getModel().getStack() ); + msg.set( MessageField::FIELD_CODE_NAME, exec->getModel().getCode() ); + + if (exec->getModel().getUserAction().isSet()) + { + std::string action = exec->getModel().getUserAction().getLabel(); + LOG_INFO("User action: " + action); + msg.set( MessageField::FIELD_ACTION_LABEL, action ); + LOG_INFO("User action enabled: " + BSTR(exec->getModel().getUserAction().isEnabled())); + msg.set( MessageField::FIELD_ACTION_ENABLED, exec->getModel().getUserAction().isEnabled() ? "true" : "false" ); + switch(exec->getModel().getUserAction().getSeverity()) + { + case LanguageConstants::INFORMATION: + msg.set( MessageField::FIELD_ACTION_SEVERITY, MessageValue::DATA_SEVERITY_INFO ); + break; + case LanguageConstants::WARNING: + msg.set( MessageField::FIELD_ACTION_SEVERITY, MessageValue::DATA_SEVERITY_WARN ); + break; + case LanguageConstants::ERROR: + msg.set( MessageField::FIELD_ACTION_SEVERITY, MessageValue::DATA_SEVERITY_ERROR ); + break; + default: + msg.set( MessageField::FIELD_ACTION_SEVERITY, MessageValue::DATA_SEVERITY_INFO ); + break; + } + } + + LOG_INFO("[SMGR] Parent procedure : " + exec->getModel().getParentInstanceId()); + LOG_INFO("[SMGR] Status : " + SPELLdataHelper::executorStatusToString(st)); + LOG_INFO("[SMGR] ASRUN file : " + exec->getModel().getAsRunFilename()); + + + LOG_INFO("[SMGR] Procedure in background"); + msg.set( MessageField::FIELD_GUI_CONTROL, "" ); + msg.set( MessageField::FIELD_GUI_CONTROL_HOST, "" ); + + msg.set( MessageField::FIELD_OPEN_MODE, SPELLdataHelper::openModeToString(exec->getModel().getOpenMode()) ); + LOG_INFO("[SMGR] Open mode : " + SPELLdataHelper::openModeToString(exec->getModel().getOpenMode())); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("No service found to complete information"); + msg.set( MessageField::FIELD_PROC_ID, serviceId ); + msg.set( MessageField::FIELD_GROUP_ID, serviceId ); + msg.set( MessageField::FIELD_ORIGIN_ID, "???" ); + msg.set( MessageField::FIELD_PARENT_PROC, " " ); + msg.set( MessageField::FIELD_PARENT_PROC_LINE, " " ); + msg.set( MessageField::FIELD_PROC_NAME, "" ); + msg.set( MessageField::FIELD_ASRUN_NAME, " " ); + msg.set( MessageField::FIELD_EXEC_STATUS, SPELLdataHelper::executorStatusToString(STATUS_UNINIT) ); + msg.set( MessageField::FIELD_CONDITION, " " ); + msg.set( MessageField::FIELD_GUI_LIST, " " ); + msg.set( MessageField::FIELD_GUI_CONTROL, " " ); + msg.set( MessageField::FIELD_GUI_CONTROL_HOST, " " ); + msg.set( MessageField::FIELD_OPEN_MODE, " " ); + msg.set( MessageField::FIELD_STAGE_ID, " " ); + msg.set( MessageField::FIELD_STAGE_TL, " " ); + msg.set( MessageField::FIELD_CSP, " " ); + msg.set( MessageField::FIELD_CODE_NAME, " " ); + } +} diff --git a/lib/SPELL_CTX/src/SPELLsharedData.C b/lib/SPELL_CTX/src/SPELLsharedData.C index 7f27809..41567b4 100644 --- a/lib/SPELL_CTX/src/SPELLsharedData.C +++ b/lib/SPELL_CTX/src/SPELLsharedData.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the shared data tables container // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_DTA/Makefile.am b/lib/SPELL_DTA/Makefile.am index 5bc6b10..b831272 100644 --- a/lib/SPELL_DTA/Makefile.am +++ b/lib/SPELL_DTA/Makefile.am @@ -1,41 +1,46 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk +lib_LTLIBRARIES = libSPELL_DTA_v2.la libSPELL_DTA_v3.la -lib_LTLIBRARIES= libSPELL_DTA.la +libSPELL_DTA_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} -libSPELL_DTA_la_CPPFLAGS = ${AM_CPPFLAGS} -DWITH_DEBUG -libSPELL_DTA_la_LDFLAGS = -version-info 1:0:0 +libSPELL_DTA_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} -libSPELL_DTA_la_SOURCES = \ - src/SPELLdtaContainerObjectMethods.C \ - src/SPELLdtaVariableObjectMethods.C \ - src/SPELLdtaVariable.C \ - src/SPELLdtaContainer.C \ - src/SPELLdtaModule.C - +libSPELL_DTA_v2_la_SOURCES = src/SPELLdtaContainerObjectMethods.C \ + src/SPELLdtaVariableObjectMethods.C \ + src/SPELLdtaVariable.C \ + src/SPELLdtaContainer.C \ + src/SPELLdtaModule.C + + +libSPELL_DTA_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_DTA_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_DTA_v3_la_SOURCES = src/SPELLdtaContainerObjectMethods.C \ + src/SPELLdtaVariableObjectMethods.C \ + src/SPELLdtaVariable.C \ + src/SPELLdtaContainer.C \ + src/SPELLdtaModule.C + \ No newline at end of file diff --git a/lib/SPELL_DTA/src/SPELLdtaContainer.C b/lib/SPELL_DTA/src/SPELLdtaContainer.C index 35927bc..bd0031c 100644 --- a/lib/SPELL_DTA/src/SPELLdtaContainer.C +++ b/lib/SPELL_DTA/src/SPELLdtaContainer.C @@ -5,7 +5,7 @@ // DESCRIPTION: Data variable container implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -181,7 +181,7 @@ void SPELLdtaContainer::setValue( PyObject* key, PyObject* value ) { DEBUG("[DTAC] Creating a new variable to contain the value"); - PyObject* varClass = SPELLpythonHelper::instance().getObject("libSPELL_DTA", "Var"); + PyObject* varClass = SPELLpythonHelper::instance().getObject(MOD_NAME(libSPELL_DTA), "Var"); Py_INCREF(varClass); assert(varClass != NULL); PyObject* dict = PyDict_New(); @@ -233,7 +233,7 @@ PyObject* SPELLdtaContainer::getValue( PyObject* key ) if (!PyDict_Contains(m_dict, key)) { DEBUG("[DTAC] Variable is missing, asking for a value"); - PyObject* varClass = SPELLpythonHelper::instance().getObject("libSPELL_DTA", "Var"); + PyObject* varClass = SPELLpythonHelper::instance().getObject(MOD_NAME(libSPELL_DTA), "Var"); Py_XINCREF(varClass); PyObject* instance = SPELLpythonHelper::instance().newInstance(varClass, NULL, NULL); Py_XINCREF(instance); diff --git a/lib/SPELL_DTA/src/SPELLdtaContainerObjectMethods.C b/lib/SPELL_DTA/src/SPELLdtaContainerObjectMethods.C index 5e3a775..0a95e3f 100644 --- a/lib/SPELL_DTA/src/SPELLdtaContainerObjectMethods.C +++ b/lib/SPELL_DTA/src/SPELLdtaContainerObjectMethods.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of data container // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -65,7 +65,7 @@ int SPELLdtaContainerObject_Init( SPELLdtaContainerObject* self, PyObject* args, void SPELLdtaContainerObject_Dealloc( SPELLdtaContainerObject* self ) { delete self->container; - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ @@ -326,8 +326,7 @@ static PyMethodDef dictiter_methods[] = { PyTypeObject SPELLdtaContainerIterator_Type = { - PyObject_HEAD_INIT(&PyType_Type) - 0, /* ob_size */ + PyVarObject_HEAD_INIT(&PyType_Type,0) "container-keyiterator", /* tp_name */ sizeof(SPELLdtaContainerIteratorObject),/* tp_basicsize */ 0, /* tp_itemsize */ @@ -380,51 +379,42 @@ PyObject* SPELLdtaContainerObject_NewIterator( SPELLdtaContainerObject* containe //============================================================================ void SPELLdtaContainerObject_DeallocIterator( SPELLdtaContainerIteratorObject* di ) { - di->ob_type->tp_free( (PyObject*)di ); + Py_TYPE(di)->tp_free( (PyObject*)di ); } //============================================================================ // FUNCTION : SPELLdtaContainerObject_NextKey //============================================================================ -PyObject* SPELLdtaContainerObject_NextKey( SPELLdtaContainerIteratorObject* di ) +PyObject* SPELLdtaContainerObject_NextKey(SPELLdtaContainerIteratorObject* di) { - PyObject *key; - register Py_ssize_t i, mask; - register PyDictEntry *ep; + PyObject *key, *value; + Py_ssize_t i; + int result; + DEBUG("[DTAPY] Next iterator key on " + PYCREPR(di)); - DEBUG("[DTAPY] Next iterator key on " + PYCREPR(di)); + PyObject* d = (PyObject*) di->di_dict; - PyDictObject* d = di->di_dict; + if (d == NULL) + return NULL; + assert(PyDict_Check(d)); - DEBUG("[DTAPY] Dictionary: " + PYCREPR(d)); + i = di->di_pos; + if (i < 0) + goto fail; - if (d == NULL) return NULL; + result = PyDict_Next(d, &di->di_pos, &key, &value); - if (di->di_used != d->ma_used) - { - PyErr_SetString(PyExc_RuntimeError, "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ - LOG_ERROR("Dictionary changed size during iteration!!!"); - return NULL; - } - - i = di->di_pos; - if (i < 0) goto fail; - ep = d->ma_table; - mask = d->ma_mask; - while (i <= mask && ep[i].me_value == NULL) i++; - di->di_pos = i+1; - if (i > mask) goto fail; - di->len--; - key = ep[i].me_key; - if (key == NULL) goto fail; - Py_INCREF(key); - return key; -fail: - DEBUG("[DTAPY] No key found to iterate on"); - di->di_dict = NULL; - PyErr_Clear(); - return NULL; + if (!result) + goto fail; + + di->len--; + + Py_INCREF(key); + return key; +fail: DEBUG("[DTAPY] No key found to iterate on"); + di->di_dict = NULL; + PyErr_Clear(); + return NULL; } //============================================================================ diff --git a/lib/SPELL_DTA/src/SPELLdtaModule.C b/lib/SPELL_DTA/src/SPELLdtaModule.C index 1da117e..db7a5d2 100644 --- a/lib/SPELL_DTA/src/SPELLdtaModule.C +++ b/lib/SPELL_DTA/src/SPELLdtaModule.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of DTA module // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -54,7 +54,7 @@ static PyMethodDef SPELLdtaContainerObject_Methods[] = {"get", SPELLdtaContainerObject_Get, METH_VARARGS, "Get a value"}, {"has_key", SPELLdtaContainerObject_HasKey, METH_VARARGS, "Check if the key exists"}, {"keys", SPELLdtaContainerObject_Keys, METH_NOARGS, "Obtain the keys"}, - {"values", SPELLdtaContainerObject_Keys, METH_NOARGS, "Obtain the values"}, + {"values", SPELLdtaContainerObject_Values, METH_NOARGS, "Obtain the values"}, {"enableNotifications", SPELLdtaContainerObject_EnableNotifications, METH_NOARGS, "Enable value notifications"}, {"disableNotifications", SPELLdtaContainerObject_DisableNotifications, METH_NOARGS, "Disable value notifications"}, {NULL, NULL, 0, NULL} /* Sentinel */ @@ -91,8 +91,7 @@ static PyMappingMethods SPELLdtaContainerObject_MappingMethods = //============================================================================ static PyTypeObject SPELLdtaContainerObject_Type = { - PyObject_HEAD_INIT(NULL) /* basic object head */ - 0, /* var objects */ + PyVarObject_HEAD_INIT(NULL,0) /* basic object head */ "spell.lib.adapter.data.DataContainer", /* type name */ sizeof(SPELLdtaContainerObject), /* type size */ 0, /* item size */ @@ -145,8 +144,7 @@ static PyTypeObject SPELLdtaContainerObject_Type = //============================================================================ static PyTypeObject SPELLdtaVariableObject_Type = { - PyObject_HEAD_INIT(NULL) /* basic object head */ - 0, /* var objects */ + PyVarObject_HEAD_INIT(NULL,0) /* basic object head */ "spell.lib.adapter.data.Var", /* type name */ sizeof(SPELLdtaVariableObject), /* type size */ 0, /* item size */ @@ -197,8 +195,8 @@ static PyTypeObject SPELLdtaVariableObject_Type = ////////////////////////////////////////////////////////////////////////////// // PYTHON MODULE INITIALIZATION ////////////////////////////////////////////////////////////////////////////// -PyMODINIT_FUNC -initlibSPELL_DTA(void) + +MOD_INIT(libSPELL_DTA) { // Will hold the Python module PyObject* module; @@ -210,7 +208,7 @@ initlibSPELL_DTA(void) std::string err = "Unable to load type " + std::string(SPELLdtaContainerObject_Type.tp_name); PyErr_SetString( PyExc_RuntimeError, err.c_str()); std::cerr << "ERROR: " << err << std::endl; - return; + return MOD_ERROR_VAL; } // Allocate the new type for SPELL variable containers @@ -220,15 +218,17 @@ initlibSPELL_DTA(void) std::string err = "Unable to load type " + std::string(SPELLdtaVariableObject_Type.tp_name); PyErr_SetString( PyExc_RuntimeError, err.c_str()); std::cerr << "ERROR: " << err << std::endl; - return; + return MOD_ERROR_VAL; } // Initialize the module - module = Py_InitModule3("libSPELL_DTA", NULL, "Module for SPELL data containers"); + MOD_DEF(module,MOD_NAME(libSPELL_DTA),"Module for SPELL data containers",NULL); Py_INCREF(&SPELLdtaContainerObject_Type); PyModule_AddObject(module, "DataContainer", (PyObject *)&SPELLdtaContainerObject_Type); Py_INCREF(&SPELLdtaVariableObject_Type); PyModule_AddObject(module, "Var", (PyObject *)&SPELLdtaVariableObject_Type); + + return MOD_SUCCESS_VAL(module); } diff --git a/lib/SPELL_DTA/src/SPELLdtaVariable.C b/lib/SPELL_DTA/src/SPELLdtaVariable.C index 4b0e372..66f5f19 100644 --- a/lib/SPELL_DTA/src/SPELLdtaVariable.C +++ b/lib/SPELL_DTA/src/SPELLdtaVariable.C @@ -5,7 +5,7 @@ // DESCRIPTION: Data variable implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -955,10 +955,14 @@ bool SPELLdtaVariable::promptForValue( std::string& message ) for(unsigned int idx = 0; idx < m_expected.size(); idx++) { std::string option = ""; - if (m_type == LanguageConstants::STRING || m_type == LanguageConstants::DATETIME || m_type == LanguageConstants::RELTIME ) + if (m_type == LanguageConstants::STRING) { option = "'" + m_expected[idx].str() + "'"; } + else if (m_type == LanguageConstants::DATETIME || m_type == LanguageConstants::RELTIME ) + { + option = m_expected[idx].str(); + } else { option = m_expected[idx].str(); diff --git a/lib/SPELL_DTA/src/SPELLdtaVariableObjectMethods.C b/lib/SPELL_DTA/src/SPELLdtaVariableObjectMethods.C index ebbbb89..580343a 100644 --- a/lib/SPELL_DTA/src/SPELLdtaVariableObjectMethods.C +++ b/lib/SPELL_DTA/src/SPELLdtaVariableObjectMethods.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of variable container // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -70,7 +70,7 @@ int SPELLdtaVariableObject_Init( SPELLdtaVariableObject* self, PyObject* args, P void SPELLdtaVariableObject_Dealloc( SPELLdtaVariableObject* self ) { delete self->var; - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ diff --git a/lib/SPELL_EXC/Makefile.am b/lib/SPELL_EXC/Makefile.am index 9a4fab4..733d639 100644 --- a/lib/SPELL_EXC/Makefile.am +++ b/lib/SPELL_EXC/Makefile.am @@ -1,62 +1,98 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -#libSPELL_EXC_la_CPPFLAGS = ${AM_CPPFLAGS} -DWITH_DEBUG - -lib_LTLIBRARIES= libSPELL_EXC.la -libSPELL_EXC_la_LDFLAGS = -version-info 3:0:0 -libSPELL_EXC_la_SOURCES = \ - src/SPELLgoto.C \ - src/SPELLbreakpoint.C \ - src/SPELLbreakpointType.C \ - src/SPELLbytecode.C \ - src/SPELLastAnalyzer.C \ - src/SPELLexecutor.C \ - src/SPELLexecutorImpl.C \ - src/SPELLimportChecker.C \ - src/SPELLexecutorPython.C \ - src/SPELLcallstack.C \ - src/SPELLexecutorUtils.C \ - src/SPELLinterpreter.C \ - src/SPELLgotoPython.C \ - src/SPELLstepPython.C \ - src/SPELLscheduler.C \ - src/SPELLchildManager.C \ - src/SPELLframeManager.C \ - src/SPELLlnotab.C \ - src/SPELLcontroller.C \ - src/SPELLcodeTreeNode.C \ - src/SPELLcodeTreeLine.C \ - src/SPELLexecutorConfiguration.C \ - src/SPELLexecutionModel.C \ - src/SPELLexecutionTrace.C \ - src/SPELLvariableMonitor.C \ - src/SPELLvariableManager.C \ - src/SPELLcommandMailbox.C - - +lib_LTLIBRARIES = libSPELL_EXC_v2.la libSPELL_EXC_v3.la + +libSPELL_EXC_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_EXC_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_EXC_v2_la_SOURCES = src/SPELLgoto.C \ + src/SPELLbreakpoint.C \ + src/SPELLbreakpointType.C \ + src/SPELLbytecode.C \ + src/SPELLastAnalyzer.C \ + src/SPELLexecutor.C \ + src/SPELLexecutorImpl.C \ + src/SPELLserviceImpl.C \ + src/SPELLimportChecker.C \ + src/SPELLexecutorPython.C \ + src/SPELLcallstack.C \ + src/SPELLexecutorUtils.C \ + src/SPELLinterpreter.C \ + src/SPELLserviceInterpreter.C \ + src/SPELLgotoPython.C \ + src/SPELLstepPython.C \ + src/SPELLscheduler.C \ + src/SPELLchildManager.C \ + src/SPELLframeManager.C \ + src/SPELLlnotab.C \ + src/SPELLcontroller.C \ + src/SPELLcodeTreeNode.C \ + src/SPELLcodeTreeLine.C \ + src/SPELLexecutorConfiguration.C \ + src/SPELLexecutionModel.C \ + src/SPELLexecutionTrace.C \ + src/SPELLvariableMonitor.C \ + src/SPELLvariableManager.C \ + src/SPELLresourceMonitor.C \ + src/SPELLresourceManager.C \ + src/SPELLcommandMailbox.C + + +libSPELL_EXC_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_EXC_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_EXC_v3_la_SOURCES = src/SPELLgoto.C \ + src/SPELLbreakpoint.C \ + src/SPELLbreakpointType.C \ + src/SPELLbytecode.C \ + src/SPELLastAnalyzer.C \ + src/SPELLexecutor.C \ + src/SPELLexecutorImpl.C \ + src/SPELLserviceImpl.C \ + src/SPELLimportChecker.C \ + src/SPELLexecutorPython.C \ + src/SPELLcallstack.C \ + src/SPELLexecutorUtils.C \ + src/SPELLinterpreter.C \ + src/SPELLserviceInterpreter.C \ + src/SPELLgotoPython.C \ + src/SPELLstepPython.C \ + src/SPELLscheduler.C \ + src/SPELLchildManager.C \ + src/SPELLframeManager.C \ + src/SPELLlnotab.C \ + src/SPELLcontroller.C \ + src/SPELLcodeTreeNode.C \ + src/SPELLcodeTreeLine.C \ + src/SPELLexecutorConfiguration.C \ + src/SPELLexecutionModel.C \ + src/SPELLexecutionTrace.C \ + src/SPELLvariableMonitor.C \ + src/SPELLvariableManager.C \ + src/SPELLresourceMonitor.C \ + src/SPELLresourceManager.C \ + src/SPELLcommandMailbox.C + \ No newline at end of file diff --git a/lib/SPELL_EXC/src/SPELLastAnalyzer.C b/lib/SPELL_EXC/src/SPELLastAnalyzer.C index 54dd55a..e00abbc 100644 --- a/lib/SPELL_EXC/src/SPELLastAnalyzer.C +++ b/lib/SPELL_EXC/src/SPELLastAnalyzer.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the AST analyzer // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -43,6 +43,8 @@ //============================================================================ SPELLastAnalyzer::SPELLastAnalyzer() { + m_lastLineType = NONE; + m_openLineNo = 0; DEBUG("[ASTN] AST analyzer created"); }; diff --git a/lib/SPELL_EXC/src/SPELLbreakpoint.C b/lib/SPELL_EXC/src/SPELLbreakpoint.C index 6521b82..6227245 100644 --- a/lib/SPELL_EXC/src/SPELLbreakpoint.C +++ b/lib/SPELL_EXC/src/SPELLbreakpoint.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the breakpoint manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLbreakpointType.C b/lib/SPELL_EXC/src/SPELLbreakpointType.C index d001050..2997ec8 100644 --- a/lib/SPELL_EXC/src/SPELLbreakpointType.C +++ b/lib/SPELL_EXC/src/SPELLbreakpointType.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the breakpoint types // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLbytecode.C b/lib/SPELL_EXC/src/SPELLbytecode.C index 11cb834..e4afc6c 100644 --- a/lib/SPELL_EXC/src/SPELLbytecode.C +++ b/lib/SPELL_EXC/src/SPELLbytecode.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the bytecode analyzer // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -36,9 +36,20 @@ // Macros for accessing opcodes +#if PY_MAJOR_VERSION >= 3 +#define INSTR_OFFSET() \ + (sizeof(_Py_CODEUNIT) * (int)(next_instr - first_instr)) +#define NEXTOPARG() do { \ + _Py_CODEUNIT word = *next_instr; \ + opcode = _Py_OPCODE(word); \ + oparg = _Py_OPARG(word); \ + next_instr++; \ + } while (0) +#else #define NEXTOP() (*next_instr++) #define NEXTARG() (next_instr += 2, (next_instr[-1]<<8) + next_instr[-2]) #define INSTR_OFFSET() ((int)(next_instr - first_instr)) +#endif // Debugging macro #define DUMP DEBUG("[" + ISTR(info->lineno) +\ ":" + ISTR(info->offset) + "]: " + OPCODES[info->opcode] \ @@ -225,9 +236,13 @@ void SPELLbytecode::analyze() { assert( m_code != NULL ); // Pointer to initial instruction (used by macros) - unsigned char* first_instr = (unsigned char*) PyString_AS_STRING(m_code->co_code); +#if PY_MAJOR_VERSION >= 3 + _Py_CODEUNIT* first_instr = (_Py_CODEUNIT*) PyBytes_AsString(m_code->co_code); +#else + _Py_CODEUNIT* first_instr = (_Py_CODEUNIT*) PyString_AsString(m_code->co_code); +#endif // Pointer to current instruction (used by macros) - register unsigned char* next_instr = first_instr; + register _Py_CODEUNIT* next_instr = first_instr; // Opcode argument unsigned int oparg; // Stores the previous line @@ -235,10 +250,9 @@ void SPELLbytecode::analyze() // Stores the last opcode unsigned int prevOpCode = 0; // Opcode count in line - unsigned short opcodeCount = 0; + unsigned int opcodeCount = 0; // Will be true when there is no more bytecode to process - bool finished = false; unsigned int callDepth = 0; // Try block structure TryBlock tb; @@ -250,8 +264,12 @@ void SPELLbytecode::analyze() // Holds the bytecode offset for except and finally statements unsigned int except_offset = 0; unsigned int finally_offset = 0; + // Holds the current opcode +#if PY_MAJOR_VERSION >= 3 + unsigned int opcode; +#endif - while(not finished) + while(true) { // Create one BLine info structure per bytecode instruction BLine info; @@ -260,7 +278,20 @@ void SPELLbytecode::analyze() // Get the corresponding script line info.lineno = PyCode_Addr2Line(m_code, info.offset); // Obtain the opcode + +#if PY_MAJOR_VERSION >= 3 + NEXTOPARG(); + info.opcode = opcode; + +#else info.opcode = NEXTOP(); +#endif + // Track the end of the code + if (info.opcode == 0/*STOP_CODE*/) + { + m_lastAddr = info.offset; + break; + } // Track the number of opcodes per line in the lnotab. if ((prevLine>0) && (info.lineno != prevLine)) @@ -302,11 +333,11 @@ void SPELLbytecode::analyze() // We will ignore this, for the moment +#if PY_MAJOR_VERSION < 3 oparg = 0; - // To decide wether store the bline information or not - bool storeit = false; - if (HAS_ARG(info.opcode)) oparg = NEXTARG(); + if (HAS_ARG(info.opcode)) oparg = NEXTARG(); +#endif ////////////////////////////////////////////////////////////////////////////////// // PHASE 2 - BUILD AND STORE NEXT BLINE INFORMATION ////////////////////////////////////////////////////////////////////////////////// @@ -366,15 +397,12 @@ void SPELLbytecode::analyze() ////////////////////////////////////////////////////////////////////////////////// switch(info.opcode) { - case RETURN_VALUE: - m_lastAddr = info.offset; - finished = true; - break; case SETUP_EXCEPT: tb.try_lineno = info.lineno; except_offset = info.offset + oparg + 3; // This is the real destination offset break; case SETUP_FINALLY: + tb.try_lineno = info.lineno; finally_offset = info.offset + oparg + 3; // This is the 'finally' destination offset break; case END_FINALLY: diff --git a/lib/SPELL_EXC/src/SPELLcallstack.C b/lib/SPELL_EXC/src/SPELLcallstack.C index 3703a47..3daf9e7 100644 --- a/lib/SPELL_EXC/src/SPELLcallstack.C +++ b/lib/SPELL_EXC/src/SPELLcallstack.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the callstack model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -89,7 +89,7 @@ void SPELLcallstack::reset() //============================================================================= // METHOD : SPELLcallstack::getStack() //============================================================================= -const std::string& SPELLcallstack::getStack() +const std::string SPELLcallstack::getStack() { SPELLmonitor mon(m_lock); @@ -108,7 +108,7 @@ const std::string SPELLcallstack::getFullStack() //============================================================================= // METHOD : SPELLcallstack::getCodeName() //============================================================================= -const std::string& SPELLcallstack::getCodeName() +const std::string SPELLcallstack::getCodeName() { SPELLmonitor mon(m_lock); @@ -319,7 +319,8 @@ void SPELLcallstack::callbackEventReturn( PyFrameObject* frame, const std::strin // When moving the view node, we shall notify the client. // Do not remove the code on top. Just move the current node pointer. - m_currentNode = m_currentNode->getParent()->getParentCode(); + if (m_currentNode->getParent() != NULL) + m_currentNode = m_currentNode->getParent()->getParentCode(); // Store current line m_currentLine = line; diff --git a/lib/SPELL_EXC/src/SPELLchildManager.C b/lib/SPELL_EXC/src/SPELLchildManager.C index f5c3882..d3d7e7b 100644 --- a/lib/SPELL_EXC/src/SPELLchildManager.C +++ b/lib/SPELL_EXC/src/SPELLchildManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the child procedure manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLcodeTreeLine.C b/lib/SPELL_EXC/src/SPELLcodeTreeLine.C index feaa9a5..ed3e4fa 100644 --- a/lib/SPELL_EXC/src/SPELLcodeTreeLine.C +++ b/lib/SPELL_EXC/src/SPELLcodeTreeLine.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the execution line model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLcodeTreeNode.C b/lib/SPELL_EXC/src/SPELLcodeTreeNode.C index e36a4e8..33dbd55 100644 --- a/lib/SPELL_EXC/src/SPELLcodeTreeNode.C +++ b/lib/SPELL_EXC/src/SPELLcodeTreeNode.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the execution node model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLcommandMailbox.C b/lib/SPELL_EXC/src/SPELLcommandMailbox.C index 3572171..0048933 100644 --- a/lib/SPELL_EXC/src/SPELLcommandMailbox.C +++ b/lib/SPELL_EXC/src/SPELLcommandMailbox.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the command mailbox // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLcontroller.C b/lib/SPELL_EXC/src/SPELLcontroller.C index c605b89..058faf1 100644 --- a/lib/SPELL_EXC/src/SPELLcontroller.C +++ b/lib/SPELL_EXC/src/SPELLcontroller.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor controller // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -34,6 +34,7 @@ #include "SPELL_WRP/SPELLconstants.H" #include "SPELL_WRP/SPELLregistry.H" #include "SPELL_WRP/SPELLdriverManager.H" +#include "SPELL_WRP/SPELLpyHandle.H" // Local includes ---------------------------------------------------------- #include "SPELL_EXC/SPELLexecutor.H" #include "SPELL_EXC/SPELLcontroller.H" @@ -112,6 +113,15 @@ void SPELLcontroller::command( const ExecutorCommand& cmd, const bool queueIt, c executeCommand(cmd); } } +void SPELLcontroller::refreshStatus() { + LOG_INFO("Procedure status: " + SPELLexecutorUtils::statusToString(m_status)); + SPELLstatusInfo info(m_status); + info.condition = getCondition(); + info.actionLabel = SPELLexecutor::instance().getUserAction().getLabel(); + info.actionEnabled = SPELLexecutor::instance().getUserAction().isEnabled(); + + SPELLexecutor::instance().getCIF().notifyStatus( info ); +} //============================================================================= // METHOD : SPELLcontroller::setStatus @@ -119,6 +129,19 @@ void SPELLcontroller::command( const ExecutorCommand& cmd, const bool queueIt, c void SPELLcontroller::setStatus( const SPELLexecutorStatus& st ) { DEBUG("[C] Attempt to set status " + SPELLexecutorUtils::statusToString(st)); + + // Do not allow a status change (this may be manually invoked) in a final status + switch(m_status) + { + case STATUS_FINISHED: + case STATUS_ERROR: + case STATUS_ABORTED: + DEBUG("[C] In final status status " + SPELLexecutorUtils::statusToString(m_status) + " do not change status now. Check code."); + return; + default: + break; + } + bool newStatus = (st != m_status); if (newStatus) notifyBeforeStatusChange(m_status); @@ -311,8 +334,6 @@ void SPELLcontroller::executeCommand( const ExecutorCommand& cmd ) startCommandProcessing(); - notifyCommandToCore( cmd.id ); - if (cmd.id == CMD_ABORT) { doAbort(); @@ -390,10 +411,16 @@ void SPELLcontroller::executeCommand( const ExecutorCommand& cmd ) { doRecover(); } + else if (cmd.id == CMD_RESOURCES) + { + SPELLexecutor::instance().getResourceManager().callResourceMonitorFromClient( cmd.arg ); + } else { LOG_ERROR("[C] UNRECOGNISED COMMAND: " + cmd.id); } + notifyCommandToCore( cmd.id ); + m_mailbox.commandProcessed(); // The command has finished, release the dispatcher @@ -761,7 +788,11 @@ void SPELLcontroller::doSkip() /** \todo Should try to go to upper frame instead of stepping. // This is the issue that obligues us to put a return statement // at the end of each function. */ - doStep(false); + //doStep(false); + // For now just send a warning + SPELLexecutor::instance().getCIF().warning("Cannot execute 'Skip' on this line. Use 'Step' or 'Goto line number' instead." + "(This may happen for 'return' and 'try' block).", + LanguageConstants::SCOPE_SYS ); } } } @@ -1234,3 +1265,12 @@ void SPELLcontroller::notifyAfterDriverOperation() (*it)->callbackAfterDriverOperation(); } } + + +//============================================================================= +// METHOD : SPELLscheduler::raiseInformationEventToGCS() +//============================================================================= +void SPELLcontroller::raiseInformationEventToGCS( const std::string& message ) { + SPELLsafePythonOperations ops("SPELLcontroller::raiseInformationEventToGCS()"); + raiseEvent(message); +} \ No newline at end of file diff --git a/lib/SPELL_EXC/src/SPELLexecutionModel.C b/lib/SPELL_EXC/src/SPELLexecutionModel.C index 07fdcaf..7c1c345 100644 --- a/lib/SPELL_EXC/src/SPELLexecutionModel.C +++ b/lib/SPELL_EXC/src/SPELLexecutionModel.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the execution model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLexecutionTrace.C b/lib/SPELL_EXC/src/SPELLexecutionTrace.C index c184a45..1c36fb4 100644 --- a/lib/SPELL_EXC/src/SPELLexecutionTrace.C +++ b/lib/SPELL_EXC/src/SPELLexecutionTrace.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the execution trace model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLexecutor.C b/lib/SPELL_EXC/src/SPELLexecutor.C index 92ff4d9..d345e34 100644 --- a/lib/SPELL_EXC/src/SPELLexecutor.C +++ b/lib/SPELL_EXC/src/SPELLexecutor.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor singleton access // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLexecutorConfiguration.C b/lib/SPELL_EXC/src/SPELLexecutorConfiguration.C index 0d165b0..58c14ff 100644 --- a/lib/SPELL_EXC/src/SPELLexecutorConfiguration.C +++ b/lib/SPELL_EXC/src/SPELLexecutorConfiguration.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor configuration // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -70,7 +70,10 @@ SPELLexecutorConfig::SPELLexecutorConfig( const SPELLexecutorConfig& refConfig ) m_blocking = refConfig.isBlocking(); m_saveStateMode = refConfig.getSaveStateMode(); m_watchVariables = refConfig.isWatchEnabled(); + m_resourcesDisplay = refConfig.isResourcesDisplayEnabled(); + m_localResourceSupport = refConfig.isLocalResourcesSupported(); m_forceTcConfirm = refConfig.isForceTcConfirm(); + m_forcePromptConfirm = refConfig.isForcePromptConfirm(); m_controlClient = refConfig.getControlClient(); m_controlHost = refConfig.getControlHost(); m_headless = refConfig.isHeadless(); @@ -97,7 +100,10 @@ SPELLexecutorConfig& SPELLexecutorConfig::operator=( SPELLexecutorConfig& refCon m_blocking = refConfig.isBlocking(); m_saveStateMode = refConfig.getSaveStateMode(); m_watchVariables = refConfig.isWatchEnabled(); + m_resourcesDisplay = refConfig.isResourcesDisplayEnabled(); + m_localResourceSupport = refConfig.isLocalResourcesSupported(); m_forceTcConfirm = refConfig.isForceTcConfirm(); + m_forcePromptConfirm = refConfig.isForcePromptConfirm(); m_controlClient = refConfig.getControlClient(); m_controlHost = refConfig.getControlHost(); m_headless = refConfig.isHeadless(); @@ -129,7 +135,10 @@ void SPELLexecutorConfig::reset() m_runInto = true; m_watchVariables = false; + m_resourcesDisplay = false; + m_localResourceSupport = false; m_forceTcConfirm = false; + m_forcePromptConfirm = false; m_controlClient = ""; m_controlHost = ""; m_contextName = ""; diff --git a/lib/SPELL_EXC/src/SPELLexecutorImpl.C b/lib/SPELL_EXC/src/SPELLexecutorImpl.C index 3bf29fc..580ec4b 100644 --- a/lib/SPELL_EXC/src/SPELLexecutorImpl.C +++ b/lib/SPELL_EXC/src/SPELLexecutorImpl.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -29,6 +29,7 @@ // Local includes ---------------------------------------------------------- #include "SPELL_EXC/SPELLexecutor.H" #include "SPELL_EXC/SPELLexecutorImpl.H" +#include "SPELL_EXC/SPELLexecutorUtils.H" // Project includes -------------------------------------------------------- #include "SPELL_PRD/SPELLprocedureManager.H" #include "SPELL_SYN/SPELLmonitor.H" @@ -54,8 +55,6 @@ static int static_dispatch( PyObject* obj, PyFrameObject* frame, int what, PyObj return SPELLexecutor::instance().dispatch(obj, frame, what, args); } -static SPELLexecutorConfig EMPTY_CONFIGURATION; - //============================================================================= // FUNCTION: convert dispatch data type to string //============================================================================= @@ -104,6 +103,7 @@ SPELLexecutorImpl::SPELLexecutorImpl() m_controller = NULL; m_childMgr = NULL; m_varManager = NULL; + m_resourceManager = NULL; m_userAction.reset(); m_gotoTarget = ""; @@ -129,6 +129,15 @@ SPELLexecutorImpl::~SPELLexecutorImpl() //============================================================================= // METHOD : SPELLexecutorImpl::initialize() //============================================================================= + +void SPELLexecutorImpl::refreshStatus() +{ + if (m_controller != NULL) + { + m_controller->refreshStatus(); + } +} + void SPELLexecutorImpl::initialize( SPELLcif* cif, SPELLcontrollerIF* controller, SPELLschedulerIF* scheduler, @@ -146,6 +155,7 @@ void SPELLexecutorImpl::initialize( SPELLcif* cif, m_callstack = callstack; m_frameManager = frame; m_varManager = new SPELLvariableManager(*frame); + m_resourceManager = new SPELLresourceManager(*frame); // The order is important here! addDispatchListener(m_frameManager); @@ -156,13 +166,13 @@ void SPELLexecutorImpl::initialize( SPELLcif* cif, //============================================================================= // METHOD : SPELLexecutorImpl::getConfiguration() //============================================================================= -SPELLexecutorConfig& SPELLexecutorImpl::getConfiguration() const +SPELLexecutorConfig& SPELLexecutorImpl::getConfiguration() { if (m_cif != NULL) { return m_cif->getExecutorConfig(); } - return EMPTY_CONFIGURATION; + return m_emptyConfig; }; //============================================================================= @@ -223,8 +233,11 @@ void SPELLexecutorImpl::prepare( const std::string& instanceId, const SPELLconte LOG_INFO("[E] Blocking mode : " + BSTR( getConfiguration().isBlocking() )); LOG_INFO("[E] Browsable lib : " + getConfiguration().getBrowsableLibStr() ); LOG_INFO("[E] TC Confirm : " + BSTR( getConfiguration().isForceTcConfirm() )); + LOG_INFO("[E] Prompt Confirm : " + BSTR( getConfiguration().isForcePromptConfirm() )); LOG_INFO("[E] Save state mode : " + getConfiguration().getSaveStateMode() ); LOG_INFO("[E] Watch variables : " + BSTR(getConfiguration().isWatchEnabled()) ); + LOG_INFO("[E] Resources Display: " + BSTR(getConfiguration().isResourcesDisplayEnabled()) ); + LOG_INFO("[E] Local resources support: " + BSTR(getConfiguration().isLocalResourcesSupported()) ); LOG_INFO("[E] Input directory : " + m_inputDir ); LOG_INFO("[E] Output directory : " + m_outputDir ); LOG_INFO("[E] Run Into : " + BSTR(getConfiguration().isRunInto())); @@ -306,7 +319,7 @@ void SPELLexecutorImpl::installCallingArguments() // Initialize now the data container for Calling Arguments Py_INCREF(argDict); DEBUG("[E] Argument dictionary: " + PYREPR(argDict)); - PyObject* classObj = SPELLpythonHelper::instance().getObject("libSPELL_DTA", "DataContainer"); + PyObject* classObj = SPELLpythonHelper::instance().getObject(MOD_NAME(libSPELL_DTA), "DataContainer"); PyObject* argTuple = PyTuple_New(1); PyObject* argsName = STRPY("Calling Arguments"); PyTuple_SetItem(argTuple,0,argsName); @@ -341,7 +354,7 @@ void SPELLexecutorImpl::installCallingArguments() //============================================================================= void SPELLexecutorImpl::installInternalVariables() { - PyObject* classObj = SPELLpythonHelper::instance().getObject("libSPELL_DTA", "DataContainer"); + PyObject* classObj = SPELLpythonHelper::instance().getObject(MOD_NAME(libSPELL_DTA), "DataContainer"); PyObject* argTuple = PyTuple_New(1); PyObject* argsName = STRPY("Internal Variables"); PyTuple_SetItem(argTuple,0,argsName); @@ -403,22 +416,34 @@ void SPELLexecutorImpl::executeInternal( bool doReset ) m_controller->enableRunInto( getConfiguration().isRunInto() ); // The client will know that the executor is ready to go - m_controller->setStatus(STATUS_LOADED); + // Take into account initialization errors + SPELLexecutorStatus status = m_controller->getStatus(); + LOG_INFO("Initial status check: " + SPELLexecutorUtils::statusToString(status) ); + SPELLexecutionResult result = EXECUTION_UNKNOWN; + if (status != STATUS_ERROR) + { + m_controller->setStatus(STATUS_LOADED); - // If in automatic and backgorund mode, set the controller in play mode accordingly - // IMPORTANT If in automatic and foreground mode, it is the GUI who sends the run command - if ( getConfiguration().isHeadless() || (getConfiguration().isAutomatic() && (!getConfiguration().isVisible()) ) ) - { - DEBUG("[E] Set autorun"); - m_controller->setAutoRun(); - } + // If in automatic and backgorund mode, set the controller in play mode accordingly + // IMPORTANT If in automatic and foreground mode, it is the GUI who sends the run command + if ( getConfiguration().isHeadless() || (getConfiguration().isAutomatic() && (!getConfiguration().isVisible()) ) ) + { + DEBUG("[E] Set autorun"); + m_controller->setAutoRun(); + } - DEBUG("[E] Launching execution"); - // Execute the procedure/script. This triggers the procedure - // execution under dispatcher control. - SPELLexecutionResult result = m_frameManager->execute(); + DEBUG("[E] Launching execution"); + // Execute the procedure/script. This triggers the procedure + // execution under dispatcher control. + result = m_frameManager->execute(); - DEBUG("[E] Execution done, checking result (" + ISTR(result) + ")"); + DEBUG("[E] Execution done, checking result (" + ISTR(result) + ")"); + } + else + { + LOG_ERROR("Initialization error"); + result = EXECUTION_ERROR; + } // Check execution result and proceed accordingly. // Frame will be reset and environment unloaded if needed, depending on the case. @@ -669,9 +694,6 @@ void SPELLexecutorImpl::loadExecutionEnvironment() // Check for Python errors SPELLpythonHelper::instance().checkError(); - // Load SCDB and GDB - SPELLdatabaseManager::instance().loadBuiltinDatabases(); - // Create proc dictionary object and install it PyObject* procObj = PyDict_New(); PyObject* pname = SSTRPY(m_instanceId); @@ -689,10 +711,48 @@ void SPELLexecutorImpl::loadExecutionEnvironment() PyDict_SetItemString( procObj, DatabaseConstants::PARENT.c_str(), parentId); SPELLpythonHelper::instance().install( procObj, DatabaseConstants::PROC ); + SPELLpythonHelper::instance().checkError(); - SPELLpythonHelper::instance().importUserLibraries( m_libPath ); - + // Load SCDB and GDB + SPELLdatabaseManager::instance().loadBuiltinDatabases(); SPELLpythonHelper::instance().checkError(); + + bool success = true; + std::string importErrors = ""; + + if (m_libPath != "") + { + if (m_libPath.find(",") != std::string::npos) + { + std::vector items = SPELLutils::tokenize(m_libPath,","); + std::vector::const_iterator cit; + for(cit = items.begin(); cit != items.end(); cit++) + { + success = SPELLpythonHelper::instance().importUserLibraries( *cit, importErrors ) && success; + } + } + else + { + try + { + success = SPELLpythonHelper::instance().importUserLibraries( m_libPath, importErrors ) && success; + } + catch(SPELLcoreException& ex) + { + importErrors += "\n - Failed to import user libraries from '" + m_libPath + "': " + ex.what(); + } + } + } + else + { + LOG_INFO("No user libraries defined"); + } + + + if (!success) + { + LOG_ERROR("Errors detected in UserLib: " + importErrors); + } // Tell the execution frame that the environment has been updated m_frameManager->filterDictUpdated(); @@ -723,6 +783,11 @@ void SPELLexecutorImpl::loadDriver() setForceTcConfirmInternal(true); } + if (getConfiguration().isForcePromptConfirm()) + { + setForcePromptConfirmInternal(true); + } + // Load the driver language specifics SPELLcontextConfig& ctxConfig = SPELLconfiguration::instance().getContext( getConfiguration().getContextName() ); std::string driverName = ctxConfig.getDriverName(); @@ -1068,6 +1133,9 @@ int SPELLexecutorImpl::dispatch( PyObject* obj, PyFrameObject* frame, int what, if(!m_controller->checkAborted()) { m_frameManager->terminate(); + if (what == PyTrace_EXCEPTION) + // We haven't raised an exception in case we trace an exception therefore return 0 (no error) + return 0; return -1; } @@ -1198,6 +1266,18 @@ void SPELLexecutorImpl::setForceTcConfirm( const bool force ) } } +//============================================================================= +// METHOD : SPELLexecutorImpl::setForceTcConfirm +//============================================================================= +void SPELLexecutorImpl::setForcePromptConfirm( const bool force ) +{ + if (getConfiguration().isForcePromptConfirm() != force ) + { + LOG_INFO("[EXEC] Set force Prompt confirmation to " + BSTR(force)); + getConfiguration().setForcePromptConfirm(force); + setForcePromptConfirmInternal(force); + } +} //============================================================================= // METHOD : SPELLexecutorImpl::command //============================================================================= @@ -1210,7 +1290,18 @@ void SPELLexecutorImpl::setForceTcConfirmInternal( bool force ) SPELLpythonHelper::instance().callMethod( tc, "forceTcConfirm", arg, NULL ); } } - +//============================================================================= +// METHOD : SPELLexecutorImpl::command +//============================================================================= +void SPELLexecutorImpl::setForcePromptConfirmInternal( bool force ) +{ + PyObject* tc = SPELLregistry::instance().get("CIF"); + if (tc != NULL) + { + PyObject* arg = force ? Py_True : Py_False; + SPELLpythonHelper::instance().callMethod( tc, "forcePromptConfirm", arg, NULL ); + } +} //============================================================================= // METHOD : SPELLexecutorImpl::command //============================================================================= @@ -1292,7 +1383,8 @@ void SPELLexecutorImpl::finish( const std::string& message ) //============================================================================= void SPELLexecutorImpl::processLock() { - SPELLmonitor m(m_cmdLock); + // TODO: No reason to lock here. Remove once validated. + //SPELLmonitor m(m_cmdLock); m_controller->executionLock(); } @@ -1301,7 +1393,8 @@ void SPELLexecutorImpl::processLock() //============================================================================= void SPELLexecutorImpl::processUnlock() { - SPELLmonitor m(m_cmdLock); + // TODO: No reason to lock here. Remove once validated. + //SPELLmonitor m(m_cmdLock); m_controller->executionUnlock(); } @@ -1568,9 +1661,13 @@ void SPELLexecutorImpl::displayStage( const std::string& id, const std::string& Py_INCREF(pyId); Py_INCREF(pyDesc); + // Get the previous step value if any + PyObject* prev = PyDict_GetItemString(proc, DatabaseConstants::STEP.c_str()); + + // Set the new value PyDict_SetItemString( proc, DatabaseConstants::STEP.c_str(), list); - PyObject* prev = PyDict_GetItemString(proc, DatabaseConstants::STEP.c_str()); + // If there was a previous value, set it as PREVIOUS if (prev != NULL) { PyDict_SetItemString( proc, DatabaseConstants::PREV_STEP.c_str(), prev); diff --git a/lib/SPELL_EXC/src/SPELLexecutorPython.C b/lib/SPELL_EXC/src/SPELLexecutorPython.C index d243498..84e04c2 100644 --- a/lib/SPELL_EXC/src/SPELLexecutorPython.C +++ b/lib/SPELL_EXC/src/SPELLexecutorPython.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor Python bindings // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -179,6 +179,17 @@ static PyObject* Executor_DisableUserAction( PyObject* self, PyObject* args ); // DESCRIPTION : set current step id and description //============================================================================ static PyObject* Executor_SetStep( PyObject* self, PyObject* args ); + +//============================================================================ +// FUNCTION : Executor_SetByStep +//============================================================================ +static PyObject* Executor_SetByStep( PyObject* self, PyObject* args ); + +//============================================================================ +// FUNCTION : Executor_SetExecutionDelay +//============================================================================ +static PyObject* Executor_GetByStep( PyObject* self, PyObject* args ); + //============================================================================ // FUNCTION : Executor_Notify // DESCRIPTION : Notify arbitrary data @@ -232,8 +243,10 @@ static PyMethodDef Executor_Methods[] = {"dismissUserAction", Executor_DismissUserAction, METH_NOARGS, "Unset the user action function"}, {"enableUserAction", Executor_EnableUserAction, METH_NOARGS, "Enable the user action function"}, {"disableUserAction", Executor_DisableUserAction, METH_NOARGS, "Disable the user action function"}, - {"setStep" , Executor_SetStep, METH_VARARGS, "Set current step name"}, - {"setExecutionDelay", Executor_SetExecutionDelay, METH_VARARGS, "Set execution delay"}, + {"setStep" , Executor_SetStep, METH_VARARGS, "Set current step name"}, + {"setByStep" , Executor_SetByStep, METH_VARARGS, "Set the ByStep configuration"}, + {"getByStep" , Executor_GetByStep, METH_NOARGS, "Get the ByStep configuration"}, + {"setExecutionDelay", Executor_SetExecutionDelay, METH_VARARGS, "Set execution delay"}, {"notify", Executor_Notify, METH_VARARGS, "Notify items"}, {"getControllingHost", Executor_GetControllingHost, METH_NOARGS, "Get the controlling host"}, {NULL, NULL, 0, NULL} /* Sentinel */ @@ -244,8 +257,7 @@ static PyMethodDef Executor_Methods[] = //============================================================================ static PyTypeObject Executor_Type = { - PyObject_HEAD_INIT(NULL) - 0, //ob_size + PyVarObject_HEAD_INIT(NULL,0) "executor.Executor", //tp_name sizeof(PyExecutorObject), //tp_basicsize 0, //tp_itemsize @@ -298,7 +310,7 @@ static int Executor_Init( PyExecutorObject* self, PyObject* args, PyObject* kwds //============================================================================ static void Executor_Dealloc( PyExecutorObject* self ) { - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ @@ -725,7 +737,11 @@ static PyObject* Executor_SetUserAction( PyObject* self, PyObject* args ) DEBUG("[EXECPY] Set user action"); PyObject* funcObj = PyTuple_GetItem(args,0); std::string label = PYSTR(PyTuple_GetItem(args, 1)); +#if PY_MAJOR_VERSION >= 3 + std::string funcName = PYSTR( ((PyFunctionObject*)funcObj)->func_name ); +#else std::string funcName = PYSTR( PyObject_GetAttrString( funcObj, "func_name" ) ); +#endif PyObject* configObj = PyTuple_GetItem(args,2); unsigned int severity = LanguageConstants::INFORMATION; SPELLpyHandle pySev = SSTRPY(LanguageModifiers::Severity); @@ -808,7 +824,7 @@ static PyObject* Executor_SetStep( PyObject* self, PyObject* args ) PyObject* idObj = PyTuple_GetItem(args,0); PyObject* descObj = PyTuple_GetItem(args,1); - SPELLexecutor::instance().displayStage( PYSSTR(idObj), PYSSTR(descObj) ); + SPELLexecutor::instance().stageReached( PYSSTR(idObj), PYSSTR(descObj) ); Py_RETURN_NONE; } @@ -835,6 +851,34 @@ static PyObject* Executor_SetExecutionDelay( PyObject* self, PyObject* args ) Py_RETURN_NONE; } +//============================================================================ +// FUNCTION : Executor_SetByStep +//============================================================================ +static PyObject* Executor_SetByStep( PyObject* self, PyObject* args ) +{ + DEBUG("[EXECPY] Set ByStep configuration"); + + PyObject* byStep = PyTuple_GetItem(args,0); + SPELLexecutor::instance().setByStep(PyObject_IsTrue(byStep)); + + Py_RETURN_NONE; +} + +//============================================================================ +// FUNCTION : Executor_SetExecutionDelay +//============================================================================ +static PyObject* Executor_GetByStep( PyObject* self, PyObject* args ) +{ + DEBUG("[EXECPY] Get ByStep configuration"); + + if (SPELLexecutor::instance().getConfiguration().isByStep()){ + Py_RETURN_TRUE; + } else { + Py_RETURN_FALSE; + } + +} + //============================================================================ // FUNCTION : Executor_GetControllingHost //============================================================================ diff --git a/lib/SPELL_EXC/src/SPELLexecutorUtils.C b/lib/SPELL_EXC/src/SPELLexecutorUtils.C index 7d401df..0fc863b 100644 --- a/lib/SPELL_EXC/src/SPELLexecutorUtils.C +++ b/lib/SPELL_EXC/src/SPELLexecutorUtils.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor utilities // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLframeManager.C b/lib/SPELL_EXC/src/SPELLframeManager.C index c92a60a..a29c5e8 100644 --- a/lib/SPELL_EXC/src/SPELLframeManager.C +++ b/lib/SPELL_EXC/src/SPELLframeManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the Python frame manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -46,6 +46,7 @@ //============================================================================= SPELLframeManager::SPELLframeManager() : SPELLdispatchListener(), + m_resourceMonitor(new SPELLresourceChangeListener()), m_modelMap() { m_warmStart = NULL; @@ -56,6 +57,8 @@ SPELLframeManager::SPELLframeManager() m_model = NULL; m_filename = ""; m_definitions = NULL; + m_inError = false; + m_terminated = false; } //============================================================================= @@ -88,6 +91,7 @@ void SPELLframeManager::initialize( const std::string& scriptFile ) // Enable or disable monitoring according to configuration SPELLvariableMonitor::s_enabled = SPELLexecutor::instance().getConfiguration().isWatchEnabled(); + SPELLresourceMonitor::s_enabled = SPELLexecutor::instance().getConfiguration().isResourcesDisplayEnabled(); // Compile the script and create the Python frame // to execute bytecode @@ -242,8 +246,8 @@ void SPELLframeManager::createInitialFrame() Py_XDECREF(m_currentFrame); m_currentFrame = NULL; } - - updateCurrentFrame( m_initialFrame, true ); + m_currentFrame = m_initialFrame; + //updateCurrentFrame( m_initialFrame, true ); DEBUG("[EF] Initial frame ready"); } @@ -305,7 +309,7 @@ void SPELLframeManager::terminate() if (!m_terminated) { DEBUG("[EF] Terminating execution"); - SPELLpythonHelper::instance().acquireGIL(); + SPELLsafePythonOperations ops("SPELLframeManager::terminate"); // Note: we substract 3 bytes since the last two instructions are always // resembling a "return None" statement. We want the instruction previous // to the last one here. @@ -316,9 +320,16 @@ void SPELLframeManager::terminate() int lasti = it->second->getLastAddress() -3 ; int lineno = it->second->getLastLine(); DEBUG("[EF] Setting lasti " + ISTR(lasti) + " lineno " + ISTR(lineno) + " on " + PYCREPR(frame)); - SPELLpythonHelper::instance().setNewLine(frame,lineno,lasti); - DEBUG("Frame stack top " + PSTR(frame->f_stacktop)); - DEBUG("Frame value stack " + PSTR(frame->f_valuestack)); + try + { + SPELLpythonHelper::instance().setNewLine(frame,lineno,lasti); + DEBUG("Frame stack top " + PSTR(frame->f_stacktop)); + DEBUG("Frame value stack " + PSTR(frame->f_valuestack)); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("Unable to set lasti in frame " + PYCREPR(frame)); + } } m_terminated = true; DEBUG("[EF] Terminating execution finished"); @@ -338,6 +349,7 @@ const SPELLexecutionResult SPELLframeManager::execute() while(frame!= NULL) { DEBUG("[EF] Running frame " + PYCREPR(frame)); + SPELLpythonHelper::instance().checkError(); PyEval_EvalFrame( frame ); DEBUG("[EF] Frame finished " + PYCREPR(frame)); DEBUG("[EF] Next frame " + PYCREPR(frame)); @@ -543,7 +555,14 @@ const bool SPELLframeManager::goNextLine() else { DEBUG("[F] Go next line " + ISTR(new_lineno) + " at " + ISTR(new_lasti)); - controllerContinue = setNewLine( new_lineno, new_lasti ); + try + { + controllerContinue = setNewLine( new_lineno, new_lasti ); + } + catch (SPELLcoreException& ex) + { + controllerContinue = false; + } } } @@ -586,7 +605,24 @@ const bool SPELLframeManager::goLine( const int& new_lineno, bool report ) { SPELLsafePythonOperations ops("SPELLframeManager::goLine()"); DEBUG("[F] Go-to line " + ISTR(new_lineno) + " on model " + PSTR(m_model)); +#if PY_MAJOR_VERSION >= 3 + bool result = false; + try + { + result = setNewLine( new_lineno, 0 ); + DEBUG("[F] Go new line " + ISTR(new_lineno)); + if (report) + { + SPELLexecutor::instance().getCIF().write("Jump to line " + ISTR(new_lineno), LanguageConstants::SCOPE_SYS ); + } + } + catch(SPELLcoreException& ex) + { + SPELLexecutor::instance().getCIF().warning("Unable to go to line " + ISTR(new_lineno) + ", " + ex.what(), LanguageConstants::SCOPE_SYS ); + } + return result; +#else // We cannot jump in the middle of a block if (m_ast.isInsideBlock(new_lineno)) { @@ -610,7 +646,7 @@ const bool SPELLframeManager::goLine( const int& new_lineno, bool report ) } // We cannot jump inside blocks - bool inTry = getModel().isInTryBlock(new_lineno); + /*bool inTry = getModel().isInTryBlock(new_lineno); bool inExc = getModel().isInExceptBlock(new_lineno); bool inFin = getModel().isInFinallyBlock(new_lineno); if (inTry | inExc | inFin ) @@ -621,7 +657,7 @@ const bool SPELLframeManager::goLine( const int& new_lineno, bool report ) SPELLexecutor::instance().getCIF().warning("Unable to go to line " + ISTR(new_lineno), LanguageConstants::SCOPE_SYS ); } return false; - } + }*/ int new_lasti = getModel().offset(new_lineno); if ( new_lasti == -1 ) @@ -650,6 +686,7 @@ const bool SPELLframeManager::goLine( const int& new_lineno, bool report ) } return result; +#endif } //============================================================================= @@ -690,6 +727,8 @@ void SPELLframeManager::runScript( const std::string& script ) SPELLpythonHelper::instance().checkError(); // See model update its information getModel().update(); + // Disable local resource analyze due to performance loss + //getResourceMonitor().analyze(); DEBUG("[EF] Executing script OUT"); } catch (SPELLcoreException& ex) @@ -769,8 +808,10 @@ void SPELLframeManager::callbackEventLine( PyFrameObject* frame, const std::stri m_warmStart->notifyLine(); } } - // See model update its information (including variable monitor if enabled) + // See model update its information (including variable/resource monitor if enabled) getModel().update(); + // Disable local resource analyze due to performance loss + //getResourceMonitor().analyze(); DEBUG("[EF] Reset error mode"); m_inError = false; @@ -964,12 +1005,13 @@ void SPELLframeManager::copyDatabase( const std::string& database, PyFrameObject if (db == NULL) { // New reference - db = SPELLpythonHelper::instance().getObject("__main__", database); + db = SPELLpythonHelper::instance().getObject(BUILTINS, database); } if (db) { DEBUG("[EF] Copy database now"); Py_INCREF(db); + Py_INCREF(db); PyDict_SetItemString(toFrame->f_globals, database.c_str(), db); PyDict_SetItemString(toFrame->f_locals, database.c_str(), db); } @@ -977,6 +1019,7 @@ void SPELLframeManager::copyDatabase( const std::string& database, PyFrameObject { LOG_ERROR("Unable to copy database reference: " + database + " on " + PYCREPR(toFrame)); } + SPELLpythonHelper::instance().checkError(); } //============================================================================= @@ -1086,6 +1129,8 @@ void SPELLframeManager::createFrameModel( int callLine, PyFrameObject* frame ) // Initialize the variable monitor m_model->getVariableMonitor().initialize(); + // Initialize the resource monitor + getResourceMonitor().initialize(); m_modelMap.insert( std::make_pair( code_id, m_model )); diff --git a/lib/SPELL_EXC/src/SPELLgoto.C b/lib/SPELL_EXC/src/SPELLgoto.C index 19ef47a..7c0fae9 100644 --- a/lib/SPELL_EXC/src/SPELLgoto.C +++ b/lib/SPELL_EXC/src/SPELLgoto.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the goto mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -35,10 +35,21 @@ // Macros for accessing opcodes +#if PY_MAJOR_VERSION >= 3 +#define INSTR_OFFSET() \ + (sizeof(_Py_CODEUNIT) * (int)(next_instr - first_instr)) +#define NEXTOPARG() do { \ + _Py_CODEUNIT word = *next_instr; \ + opcode = _Py_OPCODE(word); \ + oparg = _Py_OPARG(word); \ + next_instr++; \ + } while (0) + +#else #define NEXTOP() (*next_instr++) #define NEXTARG() (next_instr += 2, (next_instr[-1]<<8) + next_instr[-2]) #define INSTR_OFFSET() ((int)(next_instr - first_instr)) - +#endif static const std::string STEP_CALL("Step"); static const std::string GOTO_CALL("Goto"); @@ -84,19 +95,25 @@ void SPELLgoto::analyze() // Assume there is no INIT step m_initLine = -1; // Pointer to initial instruction (used by macros) - unsigned char* first_instr = (unsigned char*) PyString_AS_STRING(m_code->co_code); +#if PY_MAJOR_VERSION >= 3 + _Py_CODEUNIT* first_instr = (_Py_CODEUNIT*) PyBytes_AsString(m_code->co_code); + // Size of the code to analyze + const unsigned int code_size = PyBytes_Size(m_code->co_code); +#else + _Py_CODEUNIT* first_instr = (_Py_CODEUNIT*) PyString_AsString(m_code->co_code); // Size of the code to analyze const unsigned int code_size = PyString_Size(m_code->co_code); +#endif // Pointer to current instruction (used by macros) - register unsigned char* next_instr = first_instr; + register _Py_CODEUNIT* next_instr = first_instr; // Holds the current line unsigned int lineno; // Holds the current instruction offset register unsigned int offset; // Holds the current opcode - register unsigned int opcode; + register int opcode; // Opcode argument - register unsigned int oparg; + register int oparg; // Holds the last loaded name std::string loaded_name,loaded_const = ""; @@ -105,22 +122,38 @@ void SPELLgoto::analyze() // Hold the goto information std::string step_id,step_title,goto_id = ""; register unsigned short const1 = 1; - +#if PY_MAJOR_VERSION >= 3 + int extendedArg = 0; +#endif offset = INSTR_OFFSET(); while (offset < code_size) { +#if PY_MAJOR_VERSION >= 3 + NEXTOPARG(); +#else // Obtain the opcode opcode = NEXTOP(); // Get the opcode argument oparg = 0; if (HAS_ARG(opcode)) oparg = NEXTARG(); - +#endif switch(opcode) { +#if PY_MAJOR_VERSION >= 3 + case EXTENDED_ARG: + { + extendedArg = (extendedArg << 8) + oparg; + break; + } +#endif case LOAD_NAME: case LOAD_GLOBAL: { +#if PY_MAJOR_VERSION >= 3 + oparg = oparg + (extendedArg << 8); + extendedArg = 0; +#endif loaded_name = PYSTR(PyTuple_GetItem(m_code->co_names,oparg)); if (loaded_name == STEP_CALL) { @@ -138,6 +171,10 @@ void SPELLgoto::analyze() } case LOAD_CONST: { +#if PY_MAJOR_VERSION >= 3 + oparg = oparg + (extendedArg << 8); + extendedArg = 0; +#endif PyObject* pyConst = PyTuple_GetItem(m_code->co_consts,oparg); if (pyConst != Py_None) { @@ -162,7 +199,10 @@ void SPELLgoto::analyze() } case CALL_FUNCTION: { - if (loaded_name == STEP_CALL) +#if PY_MAJOR_VERSION >= 3 + extendedArg = 0; +#endif + if (loaded_name == STEP_CALL) { // Get the corresponding script line lineno = PyCode_Addr2Line(m_code, offset); @@ -186,6 +226,13 @@ void SPELLgoto::analyze() const1 = 1; break; } + default: + { +#if PY_MAJOR_VERSION >= 3 + extendedArg = 0; +#endif + break; + } }// End switch // // Inform about errors, but ignore them at this analysis diff --git a/lib/SPELL_EXC/src/SPELLgotoPython.C b/lib/SPELL_EXC/src/SPELLgotoPython.C index 0ca7caf..eae7813 100644 --- a/lib/SPELL_EXC/src/SPELLgotoPython.C +++ b/lib/SPELL_EXC/src/SPELLgotoPython.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the goto Python bindings // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -80,8 +80,7 @@ static PyMethodDef Goto_Methods[] = //============================================================================ static PyTypeObject Goto_Type = { - PyObject_HEAD_INIT(NULL) - 0, //ob_size + PyVarObject_HEAD_INIT(NULL,0) "executor.Goto", //tp_name sizeof(PyGotoObject), //tp_basicsize 0, //tp_itemsize @@ -135,7 +134,7 @@ static int Goto_Init( PyGotoObject* self, PyObject* args, PyObject* kwds ) //============================================================================ static void Goto_Dealloc( PyGotoObject* self ) { - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ diff --git a/lib/SPELL_EXC/src/SPELLimportChecker.C b/lib/SPELL_EXC/src/SPELLimportChecker.C index 04cdb52..7f8c5ee 100644 --- a/lib/SPELL_EXC/src/SPELLimportChecker.C +++ b/lib/SPELL_EXC/src/SPELLimportChecker.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the import detector // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLinterpreter.C b/lib/SPELL_EXC/src/SPELLinterpreter.C index 27d4d36..d7f5408 100644 --- a/lib/SPELL_EXC/src/SPELLinterpreter.C +++ b/lib/SPELL_EXC/src/SPELLinterpreter.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the custom interpreter // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -161,6 +161,7 @@ void SPELLinterpreter::mainLoop() // Create and connect all execution control objects if (prepareObjects()) { + SPELLsafePythonOperations ops("Executor execute"); // Now try to initialize (compile) the execution frame. if (prepareExecution()) { @@ -346,7 +347,7 @@ const bool SPELLinterpreter::prepareObjects() { DEBUG(" Initializing Python interface"); SPELLpythonHelper::instance().initialize(); - + SPELLsafePythonOperations ops("Executor Init"); // Install log support asap Log_Install(); @@ -377,6 +378,8 @@ const bool SPELLinterpreter::prepareObjects() m_callstack = new SPELLcallstack(); // Create and initialize the execution frame m_frameManager = new SPELLframeManager(); + + DEBUG(" Initializing executor"); // Setup the CIF now try @@ -391,10 +394,10 @@ const bool SPELLinterpreter::prepareObjects() ::exit(EIO); } - DEBUG(" Initializing executor"); // Initialize the executor with the objects m_executor->initialize(m_cif, m_controller, m_scheduler, m_callstack, m_frameManager); + // Prepare and configure warm start mechanism now DEBUG(" Preparing warm start"); prepareWarmStart( ctxConfig ); diff --git a/lib/SPELL_EXC/src/SPELLlnotab.C b/lib/SPELL_EXC/src/SPELLlnotab.C index 18c6038..dec7e41 100644 --- a/lib/SPELL_EXC/src/SPELLlnotab.C +++ b/lib/SPELL_EXC/src/SPELLlnotab.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the LNotab analyzer // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLresourceManager.C b/lib/SPELL_EXC/src/SPELLresourceManager.C new file mode 100644 index 0000000..611f41a --- /dev/null +++ b/lib/SPELL_EXC/src/SPELLresourceManager.C @@ -0,0 +1,190 @@ +// ################################################################################ +// FILE : SPELLresourceManager.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the procedure resource manager +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLresourceManager.H" +#include "SPELL_EXC/SPELLexecutor.H" +#include "SPELL_EXC/SPELLexecutorUtils.H" +#include "SPELL_EXC/SPELLcommand.H" +// Project includes -------------------------------------------------------- +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLpythonHelper.H" + +// GLOBALS //////////////////////////////////////////////////////////////////// + +//============================================================================= +// CONSTRUCTOR : SPELLresourceManager::SPELLresourceManager() +//============================================================================= +SPELLresourceManager::SPELLresourceManager( SPELLframeManager& frame ) +: m_frameManager(frame), +m_changeResourceQueue(5) +{ + DEBUG("[RMGR] Created"); +} + +//============================================================================= +// DESTRUCTOR: SPELLresourceManager::~SPELLresourceManager() +//============================================================================= +SPELLresourceManager::~SPELLresourceManager() +{ + DEBUG("[RMGR] Destroyed"); +} + +//============================================================================= +// METHOD : SPELLresourceManager::setEnabled() +//============================================================================= +void SPELLresourceManager::setEnabled( bool enabled ) +{ + SPELLresourceMonitor::s_enabled = enabled; + LOG_INFO("[RMGR] Display of resources enabled: " + BSTR(enabled) ); +} + +//============================================================================= +// METHOD : SPELLresourceManager::setEnabled() +//============================================================================= +bool SPELLresourceManager::isEnabled() +{ + return SPELLresourceMonitor::s_enabled; +} + +//============================================================================= +// METHOD : SPELLresourceManager::getResources() +//============================================================================= +void SPELLresourceManager::getResources() +{ + DEBUG("[RMGR] Retrieve all resources"); + + if(isStatusValid()) + { + ExecutorCommand cmd; + cmd.id = CMD_RESOURCES; + cmd.arg = RESOURCE_MONITOR_GET_RESOURCES; + + // Call through controller + SPELLexecutor::instance().command(cmd, false); + } + else + { + SPELLexecutorStatus st = SPELLexecutor::instance().getStatus(); + std::string status = SPELLexecutorUtils::statusToString(st); + LOG_WARN("[RMGR] Status is not valid for resource analysis: " + status); + } +} + +//============================================================================= +// METHOD : SPELLresourceManager::analyze() +//============================================================================= +void SPELLresourceManager::analyze() +{ + m_frameManager.getResourceMonitor().analyze(); +} + + +//============================================================================= +// METHOD : SPELLresourceManager::changeResource() +//============================================================================= +void SPELLresourceManager::changeResource( SPELLresourceInfo& resource ) +{ + DEBUG("[RMGR] Change resource " + resource.resourceName); + + if(isStatusValid()) + { + try + { + ExecutorCommand cmd; + cmd.id = CMD_RESOURCES; + cmd.arg = RESOURCE_MONITOR_CHANGE_RESOURCE; + + m_changeResourceQueue.push(resource); + // Call through controller + SPELLexecutor::instance().command(cmd, false); + + LOG_INFO("[RMGR] Resource modification submitted: " + + resource.resourceName + ": globalValue = " + + resource.resourceGlobalValue + + ", localValue = " + resource.resourceLocalValue); + } + catch( SPELLcoreException& ex ) + { + LOG_ERROR("[RMGR] Unable to assign resource value: " + ex.what()); + } + } + else + { + SPELLexecutorStatus st = SPELLexecutor::instance().getStatus(); + std::string status = SPELLexecutorUtils::statusToString(st); + LOG_WARN("[RMGR] Status is not valid for resource analysis: " + status); + } +} + +void SPELLresourceManager::callResourceMonitorFromClient(std::string monitorTask) +{ + if (monitorTask == RESOURCE_MONITOR_CHANGE_RESOURCE) + { + SPELLresourceInfo resource = m_changeResourceQueue.pull(); + m_frameManager.getResourceMonitor().changeResource(resource); + } + else if (monitorTask == RESOURCE_MONITOR_GET_RESOURCES) + { + m_frameManager.getResourceMonitor().getResources(); + } + else + { + LOG_ERROR("[RMGR] Invalid monitorTask in resource manager"); + } +} + +//============================================================================= +// METHOD : SPELLresourceManager::isStatusValid() +//============================================================================= +bool SPELLresourceManager::isStatusValid() +{ + bool valid = false; + if (m_frameManager.isReady()) + { + SPELLexecutorStatus st = SPELLexecutor::instance().getStatus(); + switch(st) + { + case STATUS_PAUSED: + case STATUS_WAITING: + case STATUS_PROMPT: + case STATUS_FINISHED: + case STATUS_INTERRUPTED: + case STATUS_ABORTED: + valid = true; + break; + default: + break; + } + } + else + { + LOG_WARN("[RMGR] Frame manager is not ready for resource analysis"); + } + return valid; +} diff --git a/lib/SPELL_EXC/src/SPELLresourceMonitor.C b/lib/SPELL_EXC/src/SPELLresourceMonitor.C new file mode 100644 index 0000000..b84ae05 --- /dev/null +++ b/lib/SPELL_EXC/src/SPELLresourceMonitor.C @@ -0,0 +1,474 @@ +// ################################################################################ +// FILE : SPELLresourceMonitor.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the procedure resource monitor +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLresourceMonitor.H" +#include "SPELL_EXC/SPELLexecutor.H" +// Project includes -------------------------------------------------------- +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLutils.H" +#include "SPELL_WRP/SPELLconstants.H" +#include "SPELL_UTIL/SPELLpythonMonitors.H" +#include "SPELL_WRP/SPELLpyHandle.H" + +// GLOBALS //////////////////////////////////////////////////////////////////// +#define EMPTY_STRING "" + +bool SPELLresourceMonitor::s_enabled = false; + +//============================================================================= +// CONSTRUCTOR : SPELLresourceMonitor::SPELLresourceMonitor +//============================================================================= +SPELLresourceMonitor::SPELLresourceMonitor( SPELLresourceChangeListener* listener) +: m_listener(listener) +{ + DEBUG("[RD] Created resource monitor"); + m_initialized = false; +} + +//============================================================================= +// DESTRUCTOR : SPELLresourceMonitor::~SPELLresourceMonitor +//============================================================================= +SPELLresourceMonitor::~SPELLresourceMonitor() +{ + delete m_listener; + // Do not delete! borrowed reference + m_listener = NULL; + m_resources.clear(); + DEBUG("[RD] Destroyed"); +} + +//============================================================================= +// METHOD: SPELLresourceMonitor::retrieveResources() +//============================================================================= +void SPELLresourceMonitor::retrieveResources( ResourceMap &resultingMap, bool onlyLocal ) +{ + + resultingMap.clear(); + + SPELLsafePythonOperations ops("SPELLresourceMonitor::retrieveResources()"); + // Retrieve the resources + + for (long isLocal=0; isLocal<=1; ++isLocal) + { + if(isLocal) + { + if (!SPELLexecutor::instance().getConfiguration().isLocalResourcesSupported()) + { + continue; + } + } + else + { + if (onlyLocal) + { + continue; + } + } + + DEBUG("[RD] Get resources from procedure, with 'IsLocal' = " + BSTR(bool(isLocal))); + + PyObject* pyIsLocal = PyBool_FromLong(isLocal); + PyObject* dict = SPELLpythonHelper::instance().callFunction("spell.utils.resources_utils", "getResourcesUtil", pyIsLocal, NULL); + /*PyObject* args = PyTuple_New(0); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "IsLocal", pyIsLocal); + PyObject* dict = SPELLpythonHelper::instance().callSpellFunction("GetResources", args, kwargs); + Py_XDECREF(args); + Py_XDECREF(kwargs);*/ + SPELLpythonHelper::instance().checkError(); + Py_XDECREF(pyIsLocal); + + SPELLpyHandle itemList = PyDict_Keys(dict); + unsigned int numItems = PyList_Size(itemList.get()); + for( unsigned int index = 0; index" + //if (value == "") value = EMPTY_STRING; + + if (isLocal) + { + // override previously set value. If not set (when onlyLocal is true), other fields are + // initialized by the default ResourceInfo initializer (called from analyser) + resultingMap[resourceName].resourceLocalValue = value; + } + else + { + std::string gcsName; + std::map::iterator itNM = m_resourceNameMapping.find(resourceName); + if (itNM != m_resourceNameMapping.end()) + { + gcsName = itNM->second; + } + else + { + gcsName = resourceName; + } + + // get the accepted values list + std::map > >::iterator it = m_resourceAcceptedValues.find(resourceName); + // first set the local value to the empty, wil be updated in the second iteration + if (it != m_resourceAcceptedValues.end()) + { + resultingMap.insert( std::make_pair(resourceName, SPELLresourceInfo(resourceName, type, value, "", it->second, gcsName)) ); + } + else + { + resultingMap.insert( std::make_pair(resourceName, SPELLresourceInfo(resourceName, type, value, "", std::vector >(), gcsName)) ); + } + } + + Py_XDECREF(object); + } + } +} + +//============================================================================= +// METHOD: SPELLresourceMonitor::retrieveResourceValueRangeAndNameMap() +//============================================================================= +void SPELLresourceMonitor::retrieveResourceValueRangeAndNameMap() +{ + m_resourceAcceptedValues.clear(); + + SPELLsafePythonOperations ops("SPELLresourceMonitor::retrieveResourceValueRangeAndNameMap()"); + + // Retrieved accepted values lists or resources + PyObject* dict = SPELLpythonHelper::instance().callFunction("spell.utils.resources_utils","getResourceAcceptedValuesUtil",NULL); + SPELLpyHandle itemList = PyDict_Keys(dict); + unsigned int numItems = PyList_Size(itemList.get()); + for( unsigned int index = 0; index > valueVector; + for(Py_ssize_t i = 0; i < PyList_Size(object); i++) { + PyObject *tuple = PyList_GetItem(object, i); + Py_XINCREF(tuple); + PyObject *value1 = PyTuple_GetItem(tuple, 0); + PyObject *value2 = PyTuple_GetItem(tuple, 1); + valueVector.push_back( std::make_pair( PYSSTR( value1 ), PYSSTR( value2 ) ) ); + Py_XDECREF(tuple); + } + Py_XDECREF(object); + + m_resourceAcceptedValues.insert( std::make_pair(resourceName, valueVector )); + } + + m_resourceNameMapping.clear(); + + dict = SPELLpythonHelper::instance().callFunction("spell.utils.resources_utils","getResourceNameMapUtil",NULL); + itemList = PyDict_Keys(dict); + numItems = PyList_Size(itemList.get()); + for( unsigned int index = 0; index allResources; + + SPELLresourceMonitor::ResourceMap::const_iterator it; + for( it = m_resources.begin(); it != m_resources.end(); it++) + { + allResources.push_back(it->second); + } + + m_listener->resourceList( allResources ); +} + +//============================================================================= +// METHOD: SPELLresourceMonitor::changeResource() +//============================================================================= +void SPELLresourceMonitor::changeResource( SPELLresourceInfo& resource ) +{ + SPELLsafePythonOperations ops("SPELLresourceMonitor::changeResource()"); + + DEBUG("[RD] Request changing resource " + resource.resourceName); + + if (!m_initialized) initialize(); + + ResourceMap::iterator it = m_resources.find(resource.resourceName); + + for (long isLocal=0; isLocal<=1; ++isLocal) + { + std::string *resourceVal; + if (isLocal) + { + resourceVal = &(resource.resourceLocalValue); + if (it != m_resources.end() && *resourceVal == it->second.resourceLocalValue) + continue; + } + else + { + resourceVal = &(resource.resourceGlobalValue); + if (it != m_resources.end() && *resourceVal == it->second.resourceGlobalValue) + continue; + } + + // Evaluate the value for the resource + PyObject* value = NULL; + // If resourceValue is empty string, do not change value + if (*resourceVal == "") + { + continue; //value = STRPY(""); + } + else + { + // Build assignment expression. We need to check first, if there + // are double quotes, convert them to single quotes + SPELLutils::replace(*resourceVal, "\"", "'"); + DEBUG("[RD] Evaluating value expression: " + *resourceVal ); + // Check value correctness and evaluate it + value = SSTRPY((*resourceVal)); //SPELLpythonHelper::instance().eval(*resourceVal, false); + } + + *resourceVal = PYSSTR(value); + + DEBUG("[RD] Setting " + resource.resourceName + " to " + PYREPR(value) + " 'IsLocal' is " + BSTR(bool(isLocal))); + + // create args + PyObject* pyIsLocal = PyBool_FromLong(isLocal); + PyObject* pyName = SSTRPY(resource.resourceName); + PyObject* retVal = SPELLpythonHelper::instance().callFunction("spell.utils.resources_utils", "setResourceUtil", pyName, value, pyIsLocal, NULL); + /*PyObject* args = PyTuple_New(2); + PyTuple_SetItem(args, 0, pyName); + PyTuple_SetItem(args, 1, value); + PyObject* kwargs = PyDict_New(); + PyDict_SetItemString(kwargs, "IsLocal", pyIsLocal); + // Call set resource + PyObject* retVal = SPELLpythonHelper::instance().callSpellFunction("SetResource", args, kwargs); + // release + Py_XDECREF(args); + Py_XDECREF(kwargs);*/ + SPELLpythonHelper::instance().checkError(); + Py_XDECREF(pyIsLocal); + Py_XDECREF(pyName); + Py_XDECREF(value); + + if ((int) PyInt_AsLong(retVal) != 1) + { + DEBUG("[RD] Setting resource " + resource.resourceName + " to " + (*resourceVal) + " failed. 'IsLocal' is " + BSTR(bool(isLocal))); + // setting resource failed, do nothing + return; + } + + DEBUG("[RD] Setting resource " + resource.resourceName + " to " + (*resourceVal) + " success. 'IsLocal' is " + BSTR(bool(isLocal))); + //if (*resourceVal == "") *resourceVal = EMPTY_STRING; + } + + // Update the resource in the internal model and notify to listeners + if (it != m_resources.end() && (resource.resourceGlobalValue != "" || resource.resourceLocalValue != "")) + { + std::vector changed; + if (resource.resourceGlobalValue != "") + { + it->second.resourceGlobalValue = resource.resourceGlobalValue; + } + if (resource.resourceLocalValue != "") + { + it->second.resourceLocalValue = resource.resourceLocalValue; + } + LOG_INFO("[RD] resource changed by user: " + resource.resourceName + + ", current global value: " + it->second.resourceGlobalValue + + ", current local value " + it->second.resourceLocalValue ); + changed.push_back(it->second); + m_listener->resourceChanged( changed ); + } +} + +//============================================================================= +// METHOD: SPELLresourceMonitor::getResource() +//============================================================================= +void SPELLresourceMonitor::getResource( SPELLresourceInfo& resource ) +{ + if (!m_initialized) initialize(); + ResourceMap::iterator it = m_resources.find(resource.resourceName); + if (it != m_resources.end()) + { + resource.resourceGlobalValue = it->second.resourceGlobalValue; + if (resource.resourceGlobalValue == "") resource.resourceGlobalValue = EMPTY_STRING; + resource.resourceLocalValue = it->second.resourceLocalValue; + if (resource.resourceLocalValue == "") resource.resourceLocalValue = EMPTY_STRING; + resource.resourceType = it->second.resourceType; + } + else + { + resource.resourceGlobalValue = "???"; + resource.resourceLocalValue = "???"; + resource.resourceType = "???"; + } +} + +//============================================================================= +// METHOD: SPELLresourceMonitor::shouldDiscard() +//============================================================================= +bool SPELLresourceMonitor::shouldDiscard( const std::string& resourceName ) +{ + return false; +} + +//============================================================================= +// METHOD: SPELLresourceMonitor::analyze() +//============================================================================= +void SPELLresourceMonitor::analyze() +{ + if (!SPELLresourceMonitor::s_enabled) return; + + if (!m_initialized) initialize(); + + SPELLsafePythonOperations ops("SPELLresourceMonitor::analyze()"); + + DEBUG("[RD] Analyze changes"); + + std::vector changed; + + ResourceMap tempResourceMap; + + // Retrieve local resources + retrieveResources(tempResourceMap, true); + + ResourceMap::iterator it, itKeep; + for(it = tempResourceMap.begin(); it != tempResourceMap.end(); ++it) + { + std::string resourceName = it->first; + //std::cout << resourceName << " -> " << shouldDiscard(resourceName,object) << std::endl; + if (shouldDiscard(resourceName)) continue; + + itKeep = m_resources.find(resourceName); + + // The resource is new + if (itKeep == m_resources.end()) + { + LOG_INFO("[RD] Error: There should be no new global resource, but got '" + resourceName + "'"); + } + // The resource exists, compare values + else + { + bool hasChanged = false; + if (itKeep->second.resourceLocalValue != it->second.resourceLocalValue) + { + LOG_INFO("[RD] Resource local value change: " + resourceName + ", current value: " + it->second.resourceLocalValue + ", previous: " + itKeep->second.resourceLocalValue ); + itKeep->second.resourceLocalValue = it->second.resourceLocalValue; + hasChanged = true; + } + if (hasChanged) + { + changed.push_back(itKeep->second); + } + } + } + + if ( changed.size()>0 ) + { + m_listener->resourceChanged( changed ); + } +} + +//============================================================================= +// METHOD: SPELLresourceChangeListener::resourceChanged() +//============================================================================= +void SPELLresourceChangeListener::resourceChanged( const std::vector& changed ) +{ + if (SPELLresourceMonitor::s_enabled) + { + SPELLexecutor::instance().getCIF().notifyResourceChange( changed ); + } +} + +//============================================================================= +// METHOD: SPELLresourceChangeListener::resourceList() +//============================================================================= +void SPELLresourceChangeListener::resourceList( const std::vector& resources ) +{ + if (SPELLresourceMonitor::s_enabled) + { + SPELLexecutor::instance().getCIF().notifyResourceList( resources ); + } +} diff --git a/lib/SPELL_EXC/src/SPELLscheduler.C b/lib/SPELL_EXC/src/SPELLscheduler.C index 7977a76..f254d6e 100644 --- a/lib/SPELL_EXC/src/SPELLscheduler.C +++ b/lib/SPELL_EXC/src/SPELLscheduler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the executor scheduler // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -60,7 +60,9 @@ SPELLscheduler::SPELLscheduler( bool useSafeCalls ) m_result.reset(); m_silentCheck = false; m_defaultPromptWarningDelay = 30; - + m_abortTimer = false; + m_pyTM = NULL; + m_notificationPeriodIndex = 0; DEBUG("[SCH] Scheduler created"); } @@ -78,26 +80,34 @@ SPELLscheduler::~SPELLscheduler() //============================================================================= void SPELLscheduler::startWait( const SPELLscheduleCondition& condition ) { - DEBUG("[SCH] Starting wait"); + LOG_INFO("Starting wait"); + // If there is a condition already ongoing, cancel it + if (m_condition.type != SPELLscheduleCondition::SCH_NONE) + { + { + SPELLmonitor m(m_checkLock); + finishWaitInternal(false, false); + } + // Wait for the timer to finish otherwise we might cause a deadlock when starting the next timer + if (m_timer != NULL) + { + m_timer->join(); + } + } - // If there is a condition already ongoing, cancel it - if (m_condition.type != SPELLscheduleCondition::SCH_NONE) - { - finishWait(false,false); - } + SPELLmonitor m(m_checkLock); - // Store the condition data m_condition = condition; - DEBUG(" Condition type: " + ISTR(m_condition.type)); - DEBUG(" Target time : " + m_condition.targetTime.toString()); - DEBUG(" Expression : " + m_condition.expression); - DEBUG(" Message : " + m_condition.message); - DEBUG(" Period : " + m_condition.period.toString()); - DEBUG(" Timeout : " + m_condition.timeout.toString()); - DEBUG(" List : " + PYREPR(m_condition.getVerification())); - DEBUG(" Configuration : " + PYREPR(m_condition.getConfig())); - DEBUG(" Prompt user : " + BSTR(m_condition.promptUser)); + LOG_INFO(" Condition type: " + ISTR(m_condition.type)); + LOG_INFO(" Target time : " + m_condition.targetTime.toString()); + LOG_INFO(" Expression : " + m_condition.expression); + LOG_INFO(" Message : " + m_condition.message); + LOG_INFO(" Period : " + m_condition.period.toString()); + LOG_INFO(" Timeout : " + m_condition.timeout.toString()); + LOG_INFO(" List : " + PYREPR(m_condition.getVerification())); + LOG_INFO(" Configuration : " + PYREPR(m_condition.getConfig())); + LOG_INFO(" Prompt user : " + BSTR(m_condition.promptUser)); // Reset filters @@ -191,12 +201,12 @@ void SPELLscheduler::startWait( const SPELLscheduleCondition& condition ) //============================================================================= void SPELLscheduler::startPrompt( const SPELLtime& timeout, bool headless ) { - DEBUG("[SCH] Starting prompt (to: " + timeout.toString() + ")"); + LOG_INFO("Starting prompt (to: " + timeout.toString() + ")"); // If there is a condition already ongoing, cancel it if (m_condition.type != SPELLscheduleCondition::SCH_NONE) { - finishWait(false,false); + finishWaitInternal(false,false); } // Store the condition data @@ -217,6 +227,7 @@ void SPELLscheduler::startPrompt( const SPELLtime& timeout, bool headless ) DEBUG("[SCH] Timeout given from the adapter"); m_condition.timeout = timeout; } + DEBUG("[SCH] Prompt timeout: " + m_condition.timeout.toString()); // Reset filters @@ -281,7 +292,7 @@ void SPELLscheduler::startTimer() { DEBUG("Using default period of 1 second"); SPELLtime defaultPeriod(1,0,true); - m_timer = new SPELLtimer( 200, *this ); + m_timer = new SPELLtimer( 200, *dynamic_cast(this) ); } m_timer->start(); } @@ -300,35 +311,52 @@ bool SPELLscheduler::timerCallback( unsigned long elapsed ) } else { - SPELLsafePythonOperations ops("SPELLscheduler::timerCallback()"); - // Depending on the condition type, we perform one check or another - switch(m_condition.type) - { - case SPELLscheduleCondition::SCH_TIME: - case SPELLscheduleCondition::SCH_PROMPT: - { - abortCheck = checkTime(); - break; - } - case SPELLscheduleCondition::SCH_EXPRESSION: - { - abortCheck = checkExpression(); - break; - } - case SPELLscheduleCondition::SCH_CHILD: - { - abortCheck = checkChildProcedure(); - break; - } - case SPELLscheduleCondition::SCH_VERIFICATION: - { - abortCheck = checkVerification(); - break; - } - default: - break; - } + SPELLtime lockTime; + // We need the check Lock as well otherwise we might deadlock when executing timerCallback and startWait at the same time + do { + //SPELLmonitor m(m_checkLock); + bool locked = m_checkLock.trylock(); + if (!locked) { + SPELLtime currentTime; + SPELLtime diff = currentTime - lockTime; + if (diff.getSeconds() > 5 || m_abortTimer) { + return false; + } + usleep(100000); + continue; + } + + SPELLsafePythonOperations ops("SPELLscheduler::timerCallback()"); + // Depending on the condition type, we perform one check or another + switch(m_condition.type) + { + case SPELLscheduleCondition::SCH_TIME: + case SPELLscheduleCondition::SCH_PROMPT: + { + abortCheck = checkTime(); + break; + } + case SPELLscheduleCondition::SCH_EXPRESSION: + { + abortCheck = checkExpression(); + break; + } + case SPELLscheduleCondition::SCH_CHILD: + { + abortCheck = checkChildProcedure(); + break; + } + case SPELLscheduleCondition::SCH_VERIFICATION: + { + abortCheck = checkVerification(); + break; + } + default: + break; + } + m_checkLock.unlock(); + } while (false); // Periodic notifications with message and interval. We shall not be // aborting, there shall be a defined notification period, a message // to show shall be available and the time since the last notification @@ -339,6 +367,7 @@ bool SPELLscheduler::timerCallback( unsigned long elapsed ) (m_condition.message != "") && (currentTime - m_lastNotificationTime)>=m_notificationPeriod ) { + SPELLsafePythonOperations ops("SPELLscheduler::timerCallback()"); DEBUG("[SCH] Issuing user message"); SPELLexecutor::instance().getCIF().write(m_condition.message, LanguageConstants::SCOPE_PROC ); @@ -378,7 +407,9 @@ void SPELLscheduler::notifyTime( bool finished, bool success ) if (!finished) { SPELLtime currentTime; - std::string remaining = (m_checkTargetTime - currentTime).toString(); + auto diff = (m_checkTargetTime - currentTime); + diff.set(diff.getSeconds(),diff.getMilliseconds(),0); + std::string remaining = diff.toString(); ItemNotification item; item.type = NOTIFY_TIME; item.name = "COUNTDOWN"; @@ -814,7 +845,14 @@ bool SPELLscheduler::interruptWait() //============================================================================= void SPELLscheduler::finishWait( bool setStatus, bool keepLock ) { - SPELLmonitor m(m_checkLock); + finishWaitInternal(setStatus,keepLock); +} + +//============================================================================= +// METHOD : SPELLscheduler::finishWait +//============================================================================= +void SPELLscheduler::finishWaitInternal( bool setStatus, bool keepLock ) +{ if (waiting()) { DEBUG("[SCH] Finish wait status"); @@ -866,7 +904,12 @@ void SPELLscheduler::wait() // This may block the execution for a long time, so we need to allow other Python threads // to kick in SPELLsafeThreadOperations ops("SPELLscheduler::wait()"); + DEBUG("[SCH] Wait on event IN"); m_waitingEvent.wait(); +// if (m_timer != NULL){ +// m_timer->join(); +// } + DEBUG("[SCH] Wait on event OUT"); }; //============================================================================= @@ -997,7 +1040,7 @@ bool SPELLscheduler::waitCondition( std::string condition ) else if (PyDict_Contains( pycond.get(), pyUntil.get() )) { std::string until_time = PYREPR(PyDict_GetItemString( pycond.get(), Until.c_str())); - until_time = "TIME(" + until_time + ").abs()"; + until_time = "long(TIME(" + until_time + ").abs())"; // New reference SPELLpyHandle theTime = SPELLpythonHelper::instance().eval(until_time,false); @@ -1094,4 +1137,17 @@ void SPELLscheduler::setPromptWarningDelay( unsigned int secs ) { LOG_INFO("[SCH] Update prompt warning default delay to " + ISTR(secs) + " seconds"); m_defaultPromptWarningDelay = secs; + // Set Default timeout in case the current condition waits for a Prompt + if (m_condition.type == SPELLscheduleCondition::SCH_PROMPT) + { + m_condition.timeout.set(m_defaultPromptWarningDelay,0); + } +} + +//============================================================================= +// METHOD : SPELLscheduler::raiseWarningEventToGCS() +//============================================================================= +void SPELLscheduler::raiseWarningEventToGCS( const std::string& message ) { + SPELLsafePythonOperations ops("SPELLscheduler::raiseWarningEventToGCS()"); + raiseEvent(message); } diff --git a/lib/SPELL_EXC/src/SPELLserviceImpl.C b/lib/SPELL_EXC/src/SPELLserviceImpl.C new file mode 100644 index 0000000..61db8b1 --- /dev/null +++ b/lib/SPELL_EXC/src/SPELLserviceImpl.C @@ -0,0 +1,1739 @@ +// ################################################################################ +// FILE : SPELLexecutorImpl.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the executor +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLexecutor.H" +#include "SPELL_EXC/SPELLserviceImpl.H" +#include "SPELL_EXC/SPELLexecutorImpl.H" + +#include "SPELL_EXC/SPELLexecutorUtils.H" +// Project includes -------------------------------------------------------- +#include "SPELL_PRD/SPELLprocedureManager.H" +#include "SPELL_SYN/SPELLmonitor.H" +#include "SPELL_CFG/SPELLconfiguration.H" +#include "SPELL_DTA/SPELLdtaContainer.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLpythonHelper.H" +#include "SPELL_UTIL/SPELLpythonMonitors.H" +#include "SPELL_WRP/SPELLdriverManager.H" +#include "SPELL_WRP/SPELLdatabaseManager.H" +#include "SPELL_WRP/SPELLpyHandle.H" +#include "SPELL_WRP/SPELLregistry.H" +#include "SPELL_WRP/SPELLconstants.H" +#include "SPELL_CFG/SPELLbrowsableLibMode.H" + +using namespace PythonConstants; + +// GLOBALS //////////////////////////////////////////////////////////////////// + +// Bridge for C-API dispatching callback +static int static_dispatch( PyObject* obj, PyFrameObject* frame, int what, PyObject* args ) +{ + return SPELLexecutor::instance().dispatch(obj, frame, what, args); +} + +std::string dispatchDataType( int what ); + +//============================================================================= +// CONSTRUCTOR : SPELLserviceImpl::SPELLserviceImpl +//============================================================================= +SPELLserviceImpl::SPELLserviceImpl() + : SPELLexecutorIF(), + m_importChecker() +{ + m_initialized = false; + m_initStepDone = false; + m_initLines.clear(); + m_cif = NULL; + m_frameManager = NULL; + m_instanceId = ""; + m_parentId = ""; + m_childId = ""; + m_servicePath = ""; + + m_scheduler = NULL; + m_callstack = NULL; + m_controller = NULL; + m_childMgr = NULL; + m_varManager = NULL; + m_resourceManager = NULL; + + m_userAction.reset(); + m_gotoTarget = ""; + + m_childMgr = new SPELLchildManager(); + + DEBUG("[E] SPELLexecutor created"); +} + +//============================================================================= +// DESTRUCTOR : SPELLexecutorImpl::~SPELLexecutorImpl +//============================================================================= +SPELLserviceImpl::~SPELLserviceImpl() +{ + DEBUG("[E] SPELLservice destroyed"); + if (m_childMgr) + { + delete m_childMgr; + } + +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::initialize() +//============================================================================= + +void SPELLserviceImpl::refreshStatus() +{ + if (m_controller != NULL) + { + m_controller->refreshStatus(); + } +} + +void SPELLserviceImpl::initialize( SPELLcif* cif, + SPELLcontrollerIF* controller, + SPELLschedulerIF* scheduler, + SPELLcallstackIF* callstack, + SPELLframeManager* frame ) +{ + assert( cif != NULL ); + assert( controller != NULL ); + assert( scheduler != NULL ); + assert( callstack != NULL ); + assert( frame != NULL ); + m_cif = cif; + m_controller = controller; + m_scheduler = scheduler; + m_callstack = callstack; + m_frameManager = frame; + m_varManager = new SPELLvariableManager(*frame); + m_resourceManager = new SPELLresourceManager(*frame); + + // The order is important here! + addDispatchListener(m_frameManager); + addDispatchListener(m_callstack); + addDispatchListener(m_controller); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::getConfiguration() +//============================================================================= +SPELLexecutorConfig& SPELLserviceImpl::getConfiguration() +{ + if (m_cif != NULL) + { + return m_cif->getExecutorConfig(); + } + return m_emptyConfig; +}; + +//============================================================================= +// METHOD : SPELLexecutorImpl::getStatus() +//============================================================================= +const SPELLexecutorStatus SPELLserviceImpl::getStatus() const +{ + if (m_controller != NULL) + { + return m_controller->getStatus(); + } + return STATUS_UNKNOWN; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::getInstanceId +//============================================================================= +const std::string SPELLserviceImpl::getInstanceId() const +{ + return m_instanceId; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::prepare +//============================================================================= +void SPELLserviceImpl::prepare( const std::string& instanceId, const SPELLcontextConfig& ctxConfig ) +{ + assert( m_cif != NULL ); + assert( m_controller != NULL ); + assert( m_scheduler != NULL ); + assert( m_callstack != NULL ); + assert( m_frameManager != NULL ); + + LOG_INFO("[E] Preparing service execution"); + + // Check for Python errors + SPELLpythonHelper::instance().checkError(); + + // See constants.H + // We do this on preparation stage because these values shall not be reset + // on procedure reload. The user may have changed them in the meantime. + if (!m_initialized) + { + m_instanceId = instanceId; + + //Get from CTX Config + m_inputDir = SPELLutils::resolvePath( ctxConfig.getInputDirectory() ); + m_outputDir = SPELLutils::resolvePath( ctxConfig.getOutputDirectory() ); + + // Report the current configuration + LOG_INFO("EXECUTOR CONFIGURATION (" + getInstanceId() + ")"); + LOG_INFO("[E] Headless : " + BSTR(getConfiguration().isHeadless()) ); + LOG_INFO("[E] Parent : " + getConfiguration().getParentProcId() ); + LOG_INFO("[E] Arguments : " + getConfiguration().getArguments() ); + LOG_INFO("[E] Condition : " + getConfiguration().getCondition() ); + LOG_INFO("[E] Automatic mode : " + BSTR( getConfiguration().isAutomatic() )); + LOG_INFO("[E] Visible mode : " + BSTR( getConfiguration().isVisible() )); + LOG_INFO("[E] Blocking mode : " + BSTR( getConfiguration().isBlocking() )); + LOG_INFO("[E] Browsable lib : " + getConfiguration().getBrowsableLibStr() ); + LOG_INFO("[E] TC Confirm : " + BSTR( getConfiguration().isForceTcConfirm() )); + LOG_INFO("[E] Prompt Confirm : " + BSTR( getConfiguration().isForcePromptConfirm() )); + LOG_INFO("[E] Save state mode : " + getConfiguration().getSaveStateMode() ); + LOG_INFO("[E] Watch variables : " + BSTR(getConfiguration().isWatchEnabled()) ); + LOG_INFO("[E] resources display: " + BSTR(getConfiguration().isResourcesDisplayEnabled()) ); + LOG_INFO("[E] Local resource support: " + BSTR(getConfiguration().isLocalResourcesSupported()) ); + LOG_INFO("[E] Input directory : " + m_inputDir ); + LOG_INFO("[E] Output directory : " + m_outputDir ); + LOG_INFO("[E] Run Into : " + BSTR(getConfiguration().isRunInto())); + LOG_INFO("[E] Exec Delay : " + ISTR(getConfiguration().getExecDelay())); + LOG_INFO("[E] Prompt Delay : " + ISTR(getConfiguration().getPromptWarningDelay())); + LOG_INFO("[E] By Step : " + BSTR(getConfiguration().isByStep())); + LOG_INFO("[E] Max Verbosity : " + ISTR(getConfiguration().getMaxVerbosity())); + + m_scheduler->setPromptWarningDelay(getConfiguration().getPromptWarningDelay()); + + m_initialized = true; + } + + // Check that no previous errors are there + SPELLpythonHelper::instance().checkError(); + + // Setup extra environment stuff + // 1. Procedure arguments and internal variables. + // If there are arguments available we need to evaluate + // them in the Python environment and to install them in the execution environment + // via the ARGS and IVARS global objects. + installCallingArguments(); + installInternalVariables(); + + // 2. Process condition and open mode + // If there is a condition available, we set it to the controller. The controller + // will read and evaluate it and hold the execution until the condition is + // fullfilled. + if (getConfiguration().getCondition() != "") + { + m_controller->setCondition( getConfiguration().getCondition() ); + } + + // Reset any user action function previously set + m_userAction.reset(); + + // Configure the initial step over mode in the callstack + if (getConfiguration().isRunInto()==true) + { + m_callstack->stepOver( SO_ALWAYS_INTO ); + } + else + { + m_callstack->stepOver( SO_ALWAYS_OVER ); + } + + LOG_INFO("[E] Executor prepared"); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::installCallingArguments +//============================================================================= +void SPELLserviceImpl::installCallingArguments() +{ + DEBUG("[E] Installing calling arguments"); + + PyObject* argDict = NULL; + if (getConfiguration().getArguments() != "") + { + try + { + // Evaluate the argument string. It is expected to be a Python dictionary. + // No error check is done for this. + argDict = SPELLpythonHelper::instance().eval(getConfiguration().getArguments(),false); + } + catch(SPELLcoreException& ex) + { + m_cif->error( "Unable to install arguments data holder: " + ex.what(), LanguageConstants::SCOPE_SYS ); + argDict = PyDict_New(); + } + } + else + { + // In case of no arguments given, we install an empty Python dictionary. + argDict = PyDict_New(); + } + + + // Initialize now the data container for Calling Arguments + Py_INCREF(argDict); + DEBUG("[E] Argument dictionary: " + PYREPR(argDict)); + PyObject* classObj = SPELLpythonHelper::instance().getObject(MOD_NAME(libSPELL_DTA), "DataContainer"); + PyObject* argTuple = PyTuple_New(1); + PyObject* argsName = STRPY("Calling Arguments"); + PyTuple_SetItem(argTuple,0,argsName); + Py_INCREF(argsName); + Py_INCREF(argTuple); + PyObject* args = SPELLpythonHelper::instance().newInstance(classObj, argTuple, NULL); + + DEBUG("[E] Calling arguments instance: " + PYREPR(args) + " type: " + PYCREPR(PyObject_Type(args))); + + // Now assign the passed arguments. Disable notifications for DTA + SPELLdtaContainer::setGlobalNotificationsEnabled(false); + SPELLpyHandle argKeys = PyDict_Keys(argDict); + unsigned int numKeys = PyList_Size(argKeys.get()); + for(unsigned int idx = 0; idxreset(); + } + DEBUG("[E] Reset status"); + m_controller->reset(); + m_importChecker.reset(); + // Reset CIF closure lock always + m_cif->resetClose(); + + // Establish initial run-into value now (after reset) + m_controller->enableRunInto( getConfiguration().isRunInto() ); + + // The client will know that the executor is ready to go + // Take into account initialization errors + SPELLexecutorStatus status = m_controller->getStatus(); + LOG_INFO("Initial status check: " + SPELLexecutorUtils::statusToString(status) ); + SPELLexecutionResult result = EXECUTION_UNKNOWN; + if (status != STATUS_ERROR) + { + m_controller->setStatus(STATUS_LOADED); + + DEBUG("[E] Set autorun"); + m_controller->setAutoRun(); + + DEBUG("[E] Launching execution"); + // Execute the procedure/script. This triggers the procedure + // execution under dispatcher control. + //result = m_frameManager->execute(); + + SPELLpyService& service = SPELLprocedureManager::instance().getService(m_instanceId); + + service.start(); + + DEBUG("[E] Execution done, checking result (" + ISTR(result) + ")"); + } + else + { + LOG_ERROR("Initialization error"); + result = EXECUTION_ERROR; + } + + // Check execution result and proceed accordingly. + // Frame will be reset and environment unloaded if needed, depending on the case. + + switch(result) + { + case EXECUTION_ERROR: + { + DEBUG("[E] Result ERROR"); + m_initStepDone = true; + continueExecuting = executorFinishedWithErrors(); + if (continueExecuting) + { + // We will recover, so do not reset entities + resetEntities = false; + } + break; + } + case EXECUTION_SUCCESS: + case EXECUTION_TERMINATED: + { + DEBUG("[E] Result SUCCESS/TERMINATED"); + m_initStepDone = false; + continueExecuting = executorFinishedRight(); + break; + } + case EXECUTION_ABORTED: + { + DEBUG("[E] Result ABORTED"); + m_initStepDone = false; + continueExecuting = executorAborted(); + break; + } + default: + { + DEBUG("[E] Execution result unknown: " + ISTR(result)); + m_initStepDone = false; + unloadDriver(true); + continueExecuting = false; + break; + } + } + } + while(continueExecuting); + + // This may be redundant, but does not harm. + PyEval_SetTrace( NULL, NULL ); + + DEBUG("[E] Execution finished"); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::reloadOrClose +//============================================================================= +bool SPELLserviceImpl::reloadOrClose() +{ + DEBUG("[E] Waiting for CIF closure"); + // Wait the close or reload command from the user + m_cif->waitClose(); + // This tells us if user wants to reload, not close + if (m_controller->shouldReload()) + { + DEBUG("[E] Clear asrun"); + m_cif->clearAsRun(); + DEBUG("[E] Reload driver"); + // We are reloading, load again the execution environment and reset the frame + loadDriver(); + DEBUG("[E] Reset frame"); + m_frameManager->reset(); + DEBUG("[E] Reload environment"); + loadExecutionEnvironment(); + return true; + } + else + { + // Completely unload execution environment if we are not reloading + unloadDriver(true); + return false; + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::executorAborted +//============================================================================= +bool SPELLserviceImpl::executorAborted() +{ + DEBUG("[E] Execution finished due to abort"); + // Cleanup the runtime exception that lead to the abort of the interpretation + SPELLerror::instance().clearErrors(); + // Set the status aborted here, not before. Otherwise, the GUI would be notified + // before the driver actually unloads, and this may take time. + getController().setStatus(STATUS_ABORTED); + // Procedure has aborted, so unload the driver and execution environment right away + unloadDriver(false); + // Wait until the user decides to close or to reload + return reloadOrClose(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::executorFinishedRight +//============================================================================= +bool SPELLserviceImpl::executorFinishedRight() +{ + DEBUG("[E] Service Execution finished successfully"); + // Cleanup the runtime exception that lead to the termination of the interpretation + SPELLerror::instance().clearErrors(); + + // Mark the procedure as finished + m_controller->setFinished(); + + // Procedure has finished, so unload the driver and execution environment right away + unloadDriver(false); + // This call may be misleading but the behavior at this point is the same as the aborted state + return reloadOrClose(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::executorFinishedWithErrors +//============================================================================= +bool SPELLserviceImpl::executorFinishedWithErrors() +{ + DEBUG("[E] Service Execution finished with errors"); + // Will return true if the error was recoverable AND + // the user chooses to recover. + AfterError errorHandlingResult = handleExecutionError(); + bool continueExecuting = false; + + // If the user does not want to (either closing or reloading) + switch(errorHandlingResult) + { + case CANCEL_RECOVER: + { + // This tells us if user wants to reload, not close + if(m_controller->shouldReload()) + { + // If we are reloading, just dont unload the execution environment, reset the + // frame and start over + m_frameManager->reset(); + continueExecuting = true; + } + else + { + // Unload the environment if we dont recover + unloadDriver(true); + } + // Else will close and not reload/recover + break; + } + case RECOVER_SUCCESS: + { + // To re-execute. But do not reset the frame and dont unload the environment. + continueExecuting = true; + break; + } + case RECOVER_FAILED: + case CANNOT_RECOVER: + { + // Unload the environment, could not recover it + unloadDriver(false); + // We will need to wait users choice + DEBUG("[E] Waiting for CIF closure (recovery failed)"); + // Wait the close or reload command from the user + m_cif->resetClose(); + m_cif->waitClose(); + // This may be misleading, but the behavior at this point is the same as aborted + continueExecuting = reloadOrClose(); + break; + } + default: + break; + } + return continueExecuting; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::save +//============================================================================= +void SPELLserviceImpl::save() +{ + DEBUG("[E] Saving on demand"); + try + { + // Restore the state from persistent file + m_frameManager->saveState(); + } + catch(SPELLcoreException& ex) + { + std::string msg = "Save state failed: " + std::string(ex.what()); + m_cif->notifyError( msg, ex.what(), true); + LOG_ERROR(msg); + } + DEBUG("[E] Saving on demand done"); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::recover +//============================================================================= +void SPELLserviceImpl::recover() +{ + DEBUG("[E] Recovering service execution"); + + try + { + // Load the SPELL driver + loadDriver(); + + // Load the builtin databases, etc. + loadExecutionEnvironment(); + + // Restore the state from persistent file + m_frameManager->restoreState(); + + // Re-create the internal callstack model + m_frameManager->replayStack( m_callstack ); + } + catch(SPELLcoreException& ex) + { + std::string msg = "Recovery failed, could not restore state: " + std::string(ex.what()); + m_cif->notifyError( msg, ex.what(), true); + LOG_ERROR(msg); + PyEval_SetTrace( NULL, NULL ); + DEBUG("[E] Waiting for CIF closure"); + m_cif->waitClose(); + return; + } + DEBUG("[E] Execution recovery finished"); + executeInternal(false); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::finalize +//============================================================================= +void SPELLserviceImpl::finalize() +{ + DEBUG("[E] Finalizing, user request closure"); + // Release the CIF lock, the controller received the close command + // so we can proceed + m_cif->canClose(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::loadExecutionEnvironment +//============================================================================= +void SPELLserviceImpl::loadExecutionEnvironment() +{ + DEBUG("[E] Loading execution environment"); + + // Check for Python errors + SPELLpythonHelper::instance().checkError(); + + // Load SCDB and GDB + SPELLdatabaseManager::instance().loadBuiltinDatabases(); + + // Create proc dictionary object and install it + PyObject* procObj = PyDict_New(); + PyObject* pname = SSTRPY(m_instanceId); + PyObject* arguments = SSTRPY(getConfiguration().getArguments()); + PyObject* outputDataDir = SSTRPY( m_outputDir ); + PyObject* inputDataDir = SSTRPY( m_inputDir ); + PyObject* parentId = SSTRPY( getConfiguration().getParentProcId() ); + + PyDict_SetItemString( procObj, DatabaseConstants::NAME.c_str(), pname); + PyDict_SetItemString( procObj, DatabaseConstants::ARGS.c_str(), arguments); + PyDict_SetItemString( procObj, DatabaseConstants::STEP.c_str(), Py_None); + PyDict_SetItemString( procObj, DatabaseConstants::PREV_STEP.c_str(), Py_None); + PyDict_SetItemString( procObj, DatabaseConstants::OUTPUT_DATA.c_str(), outputDataDir); + PyDict_SetItemString( procObj, DatabaseConstants::INPUT_DATA.c_str(), inputDataDir); + PyDict_SetItemString( procObj, DatabaseConstants::PARENT.c_str(), parentId); + + SPELLpythonHelper::instance().install( procObj, DatabaseConstants::PROC ); + SPELLpythonHelper::instance().checkError(); + + bool success = true; + std::string importErrors = ""; + + if (m_libPath != "") + { + if (m_libPath.find(",") != std::string::npos) + { + std::vector items = SPELLutils::tokenize(m_libPath,","); + std::vector::const_iterator cit; + for(cit = items.begin(); cit != items.end(); cit++) + { + success = success && SPELLpythonHelper::instance().importUserLibraries( *cit, importErrors ); + } + } + else + { + try + { + success = success && SPELLpythonHelper::instance().importUserLibraries( m_libPath, importErrors ); + } + catch(SPELLcoreException& ex) + { + importErrors += "\n - Failed to import user libraries from '" + m_libPath + "': " + ex.what(); + } + } + } + else + { + LOG_INFO("No user libraries defined"); + } + + + if (!success) + { + THROW_EXCEPTION("Initialization failed", "\nErrors detected in UserLib: " + importErrors, SPELL_ERROR_EXECUTION); + } + + // Tell the execution frame that the environment has been updated + m_frameManager->filterDictUpdated(); + + DEBUG("[E] Loading execution environment done"); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::loadDriver +//============================================================================= +void SPELLserviceImpl::loadDriver() +{ + DEBUG("#############################################################"); + DEBUG("[E] Load driver - start"); + + // Check for Python errors + SPELLpythonHelper::instance().checkError(); + + SPELLnoCommandProcessing nc; + + LOG_INFO("Loading driver on context " + getConfiguration().getContextName()); + // Prepare and load the SPELL driver + SPELLdriverManager::instance().setup( getConfiguration().getContextName() ); + + // Enable TC confirmation if so said the config + if (getConfiguration().isForceTcConfirm()) + { + setForceTcConfirmInternal(true); + } + + if (getConfiguration().isForcePromptConfirm()) + { + setForcePromptConfirmInternal(true); + } + + // Load the driver language specifics + SPELLcontextConfig& ctxConfig = SPELLconfiguration::instance().getContext( getConfiguration().getContextName() ); + std::string driverName = ctxConfig.getDriverName(); + SPELLdriverConfig& drvConfig = SPELLconfiguration::instance().getDriver( driverName ); + + std::string path = drvConfig.getPath() + PATH_SEPARATOR + drvConfig.getIdentifier(); + + path = SPELLutils::resolvePath(path); + + DEBUG("[E] Driver files loaded in " + path); + + // Import modifiers + std::string package = ""; + std::string moduleFile = path + PATH_SEPARATOR + "modifiers.py"; + DEBUG("[E] Checking for module " + moduleFile); + if (SPELLutils::isFile( moduleFile )) + { + package = drvConfig.getIdentifier() + ".modifiers"; + DEBUG("[E] Importing driver package " + package); + SPELLpythonHelper::instance().importAllFrom( package ); + } + + // Import constants + moduleFile = path + PATH_SEPARATOR + "constants.py"; + DEBUG("[E] Checking for module " + moduleFile); + if (SPELLutils::isFile( moduleFile )) + { + package = drvConfig.getIdentifier() + ".constants"; + DEBUG("[E] Importing driver package " + package); + SPELLpythonHelper::instance().importAllFrom( package ); + } + + // Import functions + moduleFile = path + PATH_SEPARATOR + "functions.py"; + DEBUG("[E] Checking for module " + moduleFile); + if (SPELLutils::isFile( path + PATH_SEPARATOR + "functions.py" )) + { + package = drvConfig.getIdentifier() + ".functions"; + DEBUG("[E] Importing driver package " + package); + SPELLpythonHelper::instance().importAllFrom( package ); + } + SPELLpythonHelper::instance().checkError(); + + DEBUG("[E] Load driver - end"); + DEBUG("#############################################################"); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::unloadDriver +//============================================================================= +void SPELLserviceImpl::unloadDriver( bool shutdown ) +{ + try + { + DEBUG("[E] Unload driver - start"); + + SPELLnoCommandProcessing nc; + + // Just cleanup driver if reload/recover will be done + SPELLdriverManager::instance().cleanup(shutdown); + + DEBUG("[E] Unload driver - end"); + } + catch(SPELLcoreException& ex) + { + throw ex; + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::handleExecutionError +//============================================================================= +const SPELLserviceImpl::AfterError SPELLserviceImpl::handleExecutionError() +{ + AfterError result = CANNOT_RECOVER; + SPELLcoreException* exc = SPELLerror::instance().getError(); + bool canRestore = m_frameManager->haveWarmStart(); + bool fatalError = exc->isFatal() || (!canRestore); + LOG_ERROR("[E] Execution failed: " + exc->what() + " can restore: " + BSTR(canRestore) + ", fatal: " + BSTR(exc->isFatal())); + + // If the error is inside user library, we do not allow recovery + // to browsable lib ON to show where the error was. + std::string location = SPELLerror::instance().getErrorLocation(); + DEBUG("[E] Error location is " + location); + if ((location != "")&& (m_libPath != "")) + { + if (location.find(m_libPath) == 0) + { + m_controller->setError( "Error happened in user library: " + exc->getError(), exc->getReason(), true ); + return CANNOT_RECOVER; + } + } + + DEBUG("[E] Notify error to controller"); + // Otherwise notify the error normally + m_controller->setError( "Execution aborted: " + exc->getError(), exc->getReason(), fatalError ); + + // If the execution frame has warmstart information + if ( canRestore ) + { + // Wait user request to reload or abort + LOG_INFO("[E] Waiting for recover command"); + m_cif->waitClose(); + // This will tell us if the user wants recovery or not + bool doRecover = m_controller->shouldRecover(); + LOG_INFO("[E] Recovery flag: " + ( doRecover ? STR("enabled") : STR("disabled"))); + DEBUG("Do recover flag: " + BSTR(doRecover)); + if (doRecover) + { + LOG_INFO("[E] Recovering execution"); + m_cif->warning("Recovering execution from failure", LanguageConstants::SCOPE_SYS ); + try + { + try + { + // No commands processed in the meantime + SPELLnoCommandProcessing nc; + // Recover the state in the frame + m_frameManager->fixState(); + // Set the callstack in recovery mode so that + // the next module call event is not processed, and + // therefore add a spurious element to the stack + m_callstack->setRecoveryMode(); + } + catch(SPELLcoreException& ex) + { + throw ex; + } + + // Now re-invoke execution + DEBUG("[E] Re-executing"); + PyErr_Clear(); + result = RECOVER_SUCCESS; + } + catch(SPELLcoreException& ex) + { + std::string msg = "[E] Recovery failed: " + ex.what(); + LOG_ERROR(msg); + DEBUG(msg); + m_cif->notifyError("Recovery failed", ex.what(), true); + result = RECOVER_FAILED; + } + } + else + { + LOG_WARN("[E] Recovery cancelled by user"); + m_cif->warning("Recovery cancelled, procedure will be aborted", LanguageConstants::SCOPE_SYS ); + result = CANCEL_RECOVER; + } + } + else + { + LOG_ERROR("Recovery failed, warm start mechanism disabled"); + m_cif->notifyError("Recovery failed", "Warm start mechanism disabled", true); + // result is already CANNOT_RECOVER + } + return result; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::dispatch +//============================================================================= +int SPELLserviceImpl::dispatch( PyObject* obj, PyFrameObject* frame, int what, PyObject* args ) +{ + //SPELLmonitor m(m_dspLock); + + // Do not dispatch if the notified code is not a procedure + std::string file = PYSTR(frame->f_code->co_filename); + + //DEBUG("[DISPATCH] Processing file '" + file + "'") + //DEBUG("[DISPATCH] Proc path: '" + m_procPath + "'") + //DEBUG("[DISPATCH] Lib path: '" + m_libPath + "'") + + bool notProc = (file.find(m_servicePath) == std::string::npos); + bool notUserLib = true; + if (notProc) + { + notUserLib = (m_libPath == "") || (file.find(m_libPath) == std::string::npos); + if (notUserLib) return 0; + } + + // Filter importing state + std::string name = PYSTR(frame->f_code->co_name); + // Do not dispatch if the interpreter is importing a file + if (m_importChecker.isImporting(what,file,frame->f_lineno,name)) return 0; + + // Extract the rest of information + std::string path = PYSTR(frame->f_code->co_filename); + + // Will provide procedure or user library id + std::string procId = this->m_instanceId; + + int lineno = frame->f_lineno; + std::string etype = dispatchDataType(what); + + //DEBUG("[DISPATCH] START Dispatch event in " + procId + ":" + ISTR(lineno)); + //DEBUG(" (" + name + ") at " + PYCREPR(frame)); + //DEBUG(" event type " + etype); +// std::cerr << "################################################################################" << std::endl; +// std::cerr << "[DISPATCH] Dispatch event in " + procId + ":" + ISTR(lineno) + " (" + name + ") at " + PYCREPR(frame) << std::endl; +// std::cerr << "################################################################################" << std::endl; + + // Notify the frame manager to update the frame. This way, the internal models are + // created/updated at the proper time following the procedure execution. This call + // must be done before any other call to the frame manager that makes use of the + // 'procedure execution model object', because it is first created at this point. + m_frameManager->updateCurrentFrame(frame, what ); + + // INIT step feature + // IMPORTANT for this first if, there is no frame model until the first CALL dispatch + // has been processed. So we cannot use getModel() until then. That is why we have + // first the check to be PyTrace_LINE. + if (!m_initStepDone && + what == PyTrace_LINE && + m_frameManager->isAtInitialFrame() && + m_frameManager->getModel().hasInitStep()) + { + getCIF().disableNotifications(); + + if (checkInitStep(lineno)) + { + m_initLines.push_back(lineno); + return 0; + } + else + { + LOG_INFO("INIT step mode finished"); + // Use the callstack to notify the executed lines during INIT step, all together, + // to the controlling client. We do not care about dispatch and control during + // init mode, but we want to mark the appropriate lines as executed. + std::list::const_iterator it; + for( it = m_initLines.begin(); it != m_initLines.end(); it++ ) + { + getCallstack().callbackEventLine(frame, procId, (*it), name); + } + m_initLines.clear(); + + // Re-enable notifications once INIT step is reached + getCIF().enableNotifications(); + } + } + + // Browsable lib feature: if the flag is enabled, allow browsing inside the user library. + // this means that dispatch shall continue when notUserLib is false --> we make it true. + notUserLib = notUserLib | (getConfiguration().getBrowsableLib()==ON); + + // By-step feature: if enabled, pause on each Step statement + // Note that checkByStep cannot be used on the very first CALL notification + // as the current SPELLexecutionModel is not created yet, therefore there would + // be a segmentation fault. + if ( (what == PyTrace_LINE) && getConfiguration().isByStep()) checkByStep(lineno); + + // Breakpoint feature: pause on the breakpoints + checkBreakpoint(procId,lineno); + + // Hold the dispatching mechanism if there are commmands to be processed, + // and until the command finishes + { + SPELLsafeThreadOperations ops("SPELLexecutorImpl::dispatch()"); + m_controller->waitCommand(); + } + + // Repeat flag is used for certain cases of skip mechanism. + bool repeat = true; + // Will be false if the execution has been aborted (the controller knows this) + // if the execution has been aborted, dispatching wont be done in control objects below. + bool statusOk = true; + while(repeat) + { + // Check aborted state beforehand + statusOk = m_controller->checkAborted(); + repeat = false; + + // Here is the in-language goto mechanism implementation. Whenever the current line is + // a line with a programmed goto (there is a target line in the Goto model) the frame + // lineno will be changed accordingly. + if (m_gotoTarget != "") + { + try + { + repeat = repeat || m_frameManager->goLabel(m_gotoTarget, false, true); + lineno = frame->f_lineno; + m_gotoTarget = ""; + } + catch(SPELLcoreException& ex) + { + m_gotoTarget = ""; + abort(ex.what(),true); + statusOk = false; + } + } + + // If not aborted and not in User library, perform the data dispatch to each control object + if (statusOk && notUserLib) + { + //DEBUG("[DISPATCH] Will do dispatch"); + switch(what) + { + case PyTrace_EXCEPTION: + { + DEBUG("[DISPATCH] Exception " + procId + ":" + ISTR(lineno) ); + DEBUG("[DISPATCH] Args: " + PYREPR(args) ); + DEBUG("[DISPATCH] Going to error state"); + + notifyErrorEvent( frame, procId, lineno, name ); + + if ( processException( args, lineno ) ) + { + DEBUG("[DISPATCH] Exception is SPELL"); + return 0; + } + + DEBUG("[DISPATCH] Python exception"); + statusOk = false; + break; + } + case PyTrace_LINE: + { + notifyLineEvent( frame, procId, lineno, name ); + break; + } + case PyTrace_CALL: + { + // No Call/Return events for Services ar necessary + if (m_frameManager->isAtInitialFrame()) + notifyCallEvent( frame, procId, lineno, name ); + break; + } + case PyTrace_RETURN: + { + // No Call/Return events for Services ar necessary + if (m_frameManager->isAtInitialFrame()) + notifyReturnEvent( frame, procId, lineno, name ); + break; + } + default: + LOG_ERROR("[DISPATCH] Uncontrolled event " + procId + ":" + ISTR(lineno) ); + break; + } + } + lineno = frame->f_lineno; + repeat = m_controller->shallRepeat(); + } + + // If we are terminating/aborting the execution, tell the frame to move to the end of + // the bytecode. This way we guarantee nothing will be executed. + if(!m_controller->checkAborted()) + { + m_frameManager->terminate(); + return -1; + } + + return 0; +// DEBUG("[DISPATCH] EXIT dispatch event in " + procId + ":" + ISTR(frame->f_lineno)); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::processException +//============================================================================= +bool SPELLserviceImpl::processException( PyObject* data, int lineno ) +{ + // Check if it is a SPELL exception (syntax, driver, etc). If so, + // we want to KEEP the dispatching mechanism on because we still are + // executing a procedure normally. + // Handle name errors + PyObject* errValue = PyTuple_GetItem(data,1); + bool isSpell = SPELLpythonHelper::instance().isInstance( errValue, "SpellException", "spell.lib.exception" ); + + // If we are in a try block there will be no error report, + // but inform the user about the catched exception + if (m_frameManager->getModel().isInTryBlock( lineno )) + { + PyObject* errType = PyTuple_GetItem(data,0); + PyObject* errTb = PyTuple_GetItem(data,2); + SPELLcoreException* ex = SPELLerror::instance().errorToException( errType, errType, errValue, errTb ); + if (isSpell) + { + m_cif->write( "SPELL exception: " + PYREPR(errValue), LanguageConstants::SCOPE_SYS); + } + else if (ex) + { + m_cif->write( ex->what(), LanguageConstants::SCOPE_SYS ); + } + } + + return isSpell; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setRunInto +//============================================================================= +void SPELLserviceImpl::setRunInto( const bool enabled ) +{ + if (getConfiguration().isRunInto() != enabled) + { + LOG_INFO("[EXEC] Run into flag set to " + (enabled ? STR("ENABLED") : STR("DISABLED"))); + getConfiguration().setRunInto(enabled); + m_controller->enableRunInto(enabled); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setByStep +//============================================================================= +void SPELLserviceImpl::setByStep( const bool enabled ) +{ + if (getConfiguration().isByStep() != enabled) + { + LOG_INFO("[EXEC] By step flag set to " + (enabled ? STR("ENABLED") : STR("DISABLED"))); + getConfiguration().setByStep(enabled); + /** \todo configure for dispatch */ + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setBrowsableLib +//============================================================================= +void SPELLserviceImpl::setBrowsableLib( const SPELLbrowsableLibMode& browsable ) +{ + if (getConfiguration().getBrowsableLib() != browsable) + { + getConfiguration().setBrowsableLib(browsable); + LOG_INFO("[EXEC] Browsable lib flag set to " + browsableLibModeToString(browsable) ); + /** \todo configure for dispatch */ + } +} + + +//============================================================================= +// METHOD : SPELLexecutorImpl::setBrowsableLib +//============================================================================= +void SPELLserviceImpl::setBrowsableLibStr( const std::string& browsableStr ) +{ + if (getConfiguration().getBrowsableLibStr() != browsableStr) + { + getConfiguration().setBrowsableLibStr(browsableStr); + LOG_INFO("[EXEC] Browsable lib flag set to " + browsableStr); + /** \todo configure for dispatch */ + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setExecDelay +//============================================================================= +void SPELLserviceImpl::setExecDelay( const int delay ) +{ + if (getConfiguration().getExecDelay() != delay) + { + LOG_INFO("[EXEC] Execution delay set to " + ISTR(delay) + " milliseconds"); + getConfiguration().setExecDelay(delay); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setPromptWarningDelay +//============================================================================= +void SPELLserviceImpl::setPromptWarningDelay( const int delay ) +{ + if (getConfiguration().getPromptWarningDelay() != delay) + { + LOG_INFO("[EXEC] Prompt warning delay set to " + ISTR(delay)); + getConfiguration().setPromptWarningDelay(delay); + m_scheduler->setPromptWarningDelay(delay); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setForceTcConfirm +//============================================================================= +void SPELLserviceImpl::setForceTcConfirm( const bool force ) +{ + if (getConfiguration().isForceTcConfirm() != force ) + { + LOG_INFO("[EXEC] Set force TC confirmation to " + BSTR(force)); + getConfiguration().setForceTcConfirm(force); + setForceTcConfirmInternal(force); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setForceTcConfirm +//============================================================================= +void SPELLserviceImpl::setForcePromptConfirm( const bool force ) +{ + if (getConfiguration().isForcePromptConfirm() != force ) + { + LOG_INFO("[EXEC] Set force Prompt confirmation to " + BSTR(force)); + getConfiguration().setForcePromptConfirm(force); + setForcePromptConfirmInternal(force); + } +} +//============================================================================= +// METHOD : SPELLexecutorImpl::command +//============================================================================= +void SPELLserviceImpl::setForceTcConfirmInternal( bool force ) +{ + PyObject* tc = SPELLregistry::instance().get("TC"); + if (tc != NULL) + { + PyObject* arg = force ? Py_True : Py_False; + SPELLpythonHelper::instance().callMethod( tc, "forceTcConfirm", arg, NULL ); + } +} +//============================================================================= +// METHOD : SPELLexecutorImpl::command +//============================================================================= +void SPELLserviceImpl::setForcePromptConfirmInternal( bool force ) +{ + PyObject* tc = SPELLregistry::instance().get("CIF"); + if (tc != NULL) + { + PyObject* arg = force ? Py_True : Py_False; + SPELLpythonHelper::instance().callMethod( tc, "forcePromptConfirm", arg, NULL ); + } +} +//============================================================================= +// METHOD : SPELLexecutorImpl::command +//============================================================================= +void SPELLserviceImpl::command( const ExecutorCommand& cmd, const bool high_priority ) +{ + SPELLmonitor m(m_cmdLock); + DEBUG("[E] Issuing command " + cmd.id); + m_controller->command( cmd, (cmd.id != CMD_ABORT), high_priority ); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::abort +//============================================================================= +void SPELLserviceImpl::abort( const std::string& message, bool systemAborted ) +{ + SPELLmonitor m(m_cmdLock); + + // Send the finish message if any + if (message != "") + { + m_cif->warning(message, systemAborted ? LanguageConstants::SCOPE_SYS : LanguageConstants::SCOPE_PROC ); + } + + // Abort command is a special case which needs this method to process + // abort requests coming from the SPELL framework, not the user (in the + // former case abort shall be immediate, whereas in the latter the abort + // command shall wait till the language execution lock is released) + ExecutorCommand cmd_abort; + cmd_abort.id = CMD_ABORT; + m_controller->command( cmd_abort, false, true ); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::pause +//============================================================================= +void SPELLserviceImpl::pause() +{ + SPELLmonitor m(m_cmdLock); + + // Pause command is a special case which needs this method to process + // pause requests coming from the SPELL framework, not the user. + ExecutorCommand cmd_pause; + cmd_pause.id = CMD_PAUSE; + m_controller->command( cmd_pause, false, true ); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::interrupt +//============================================================================= +void SPELLserviceImpl::interrupt() +{ + SPELLmonitor m(m_cmdLock); + ExecutorCommand cmd_int; + cmd_int.id = CMD_INTERRUPT; + m_controller->command( cmd_int, false, true ); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::finish +//============================================================================= +void SPELLserviceImpl::finish( const std::string& message ) +{ + SPELLmonitor m(m_cmdLock); + + // Send the finish message if any + if (message != "") + { + m_cif->warning(message, LanguageConstants::SCOPE_PROC ); + } + // Finish command is a special case which needs this method to process + // finish requests coming from the SPELL framework, not the user. + ExecutorCommand cmd_finish; + cmd_finish.id = CMD_FINISH; + m_controller->command( cmd_finish, false, true ); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::processLock +//============================================================================= +void SPELLserviceImpl::processLock() +{ + // TODO: No reason to lock here. Remove once validated. + //SPELLmonitor m(m_cmdLock); + m_controller->executionLock(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::processUnlock +//============================================================================= +void SPELLserviceImpl::processUnlock() +{ + // TODO: No reason to lock here. Remove once validated. + //SPELLmonitor m(m_cmdLock); + m_controller->executionUnlock(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::canSkip +//============================================================================= +const bool SPELLserviceImpl::canSkip() +{ + // Allow skipping if in wait or interrupted state + SPELLexecutorStatus status = m_controller->getStatus(); + if ((status == STATUS_WAITING)||(status == STATUS_INTERRUPTED)||(status == STATUS_PROMPT)) return true; + return m_frameManager->canSkip(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::goNextLine +//============================================================================= +const bool SPELLserviceImpl::goNextLine() +{ + unsigned int currentLine = m_frameManager->getCurrentLine(); + if (m_frameManager->getAST().isBlockStart(currentLine)) + { + DEBUG("[E] Skipping entire code block"); + unsigned int nextLine = m_frameManager->getAST().getBlockEnd(currentLine); + return goLine(nextLine); + } + else + { + DEBUG("[E] Skipping single line"); + // When we skip the line, we dont want the callstack and trace model to register the current line + m_callstack->skipCurrentLine(); + return m_frameManager->goNextLine(); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::goLabel +//============================================================================= +const bool SPELLserviceImpl::goLabel( const std::string& label, bool programmed ) +{ + if (programmed) + { + m_gotoTarget = label; + return true; + } + else + { + m_gotoTarget = ""; + // It is a manual goto: we want to report + bool result = m_frameManager->goLabel(label, true, false); + if (result) + { + // When we skip the line, we dont want the callstack and trace model to register the current line + m_callstack->skipCurrentLine(); + } + else + { + m_cif->warning("Unable to go to label '" + label + "'", LanguageConstants::SCOPE_SYS ); + } + return result; + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::goLine +//============================================================================= +const bool SPELLserviceImpl::goLine( const int new_lineno ) +{ + // The frame manager will go to the given line only if possible, checks done inside + bool result = m_frameManager->goLine( new_lineno, true ); + if (result) + { + // When we skip the line, we dont want the callstack and trace model to register the current line + m_callstack->skipCurrentLine(); + } + return result; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setBreakpoint +//============================================================================= +const bool SPELLserviceImpl::setBreakpoint( const std::string& file, + const unsigned int line, + const SPELLbreakpointType type ) +{ + return m_frameManager->getBreakpoints().setBreakpoint( file, line, type ); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::checkBreakpoint +//============================================================================= +void SPELLserviceImpl::checkBreakpoint( const std::string& file, const unsigned int line ) +{ + if (m_controller->getStatus() == STATUS_RUNNING) + { + if (m_frameManager->getBreakpoints().checkBreakpoint(file,line)) + { + DEBUG("[BYSTEP] Pausing procedure on breakpoint"); + LOG_INFO("Breakpoint on line " + ISTR(line)); + pause(); + } + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::clearBreakpoints +//============================================================================= +void SPELLserviceImpl::clearBreakpoints() +{ + m_frameManager->getBreakpoints().clearBreakpoints(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::checkByStep +//============================================================================= +const bool SPELLserviceImpl::checkByStep( const int& frameLine ) +{ + // Only if we are not in background (headless) + if (getConfiguration().isHeadless()) return false; + + if (m_frameManager->getModel().isLabel(frameLine)) + { + DEBUG("[BYSTEP] Pausing procedure"); + pause(); + return true; + } + return false; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::checkInitStep +//============================================================================= +const bool SPELLserviceImpl::checkInitStep( const int& frameLine ) +{ + bool abortDispatching = false; + // Only if there is an INIT step in the code + if (m_frameManager->getModel().hasInitStep()) + { + // Abort the dispatching unless the current line is the INIT line + abortDispatching = true; + if(m_frameManager->getModel().isInitStep( frameLine )) + { + LOG_INFO("Pausing on INIT step on line " + ISTR(frameLine)); + m_initStepDone = true; + // Only if we are not in background (headless) + if (!getConfiguration().isHeadless()) pause(); + // Continue dispatching so that the + // controller holds the execution in PAUSE + abortDispatching = false; + } + } + return abortDispatching; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::runScript +//============================================================================= +const bool SPELLserviceImpl::runScript( const std::string& script ) +{ + m_cif->setManualMode(true); + bool result = true; + try + { + m_frameManager->runScript( script ); + } + catch(SPELLcoreException& ex) + { + result = false; + m_cif->warning("Failed to execute script: " + ex.what(), LanguageConstants::SCOPE_SYS ); + } + m_cif->setManualMode(false); + return result; +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::executeUserAction() +//============================================================================= +void SPELLserviceImpl::executeUserAction() +{ + if (m_userAction.isEnabled()) + { + LOG_INFO("Executing user action function '" + m_userAction.getAction() + "'"); + try + { + SPELLpythonHelper::instance().checkError(); + m_cif->warning("Running user action '" + m_userAction.getAction() + "'", LanguageConstants::SCOPE_SYS ); + std::string actionScript = m_userAction.getAction() + "()"; + m_frameManager->runScript( actionScript ); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("Failed to execute user action: " + ex.what()); + m_cif->error("Failed to execute user action: " + ex.what(), LanguageConstants::SCOPE_SYS ); + } + } + else + { + LOG_WARN("Cannot execute user action, not enabled"); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::setUserAction() +//============================================================================= +void SPELLserviceImpl::setUserAction( const SPELLuserAction& action ) +{ + m_userAction = action; + m_cif->notifyUserActionSet(action.getLabel(),action.getSeverity()); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::enableUserAction() +//============================================================================= +void SPELLserviceImpl::enableUserAction( bool enable ) +{ + m_userAction.enable(enable); + m_cif->notifyUserActionEnable(enable); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::dismissUserAction() +//============================================================================= +void SPELLserviceImpl::dismissUserAction() +{ + m_userAction.reset(); + m_cif->notifyUserActionUnset(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl::stageReached +//============================================================================= +void SPELLserviceImpl::stageReached( const std::string& id, const std::string& title ) +{ + displayStage(id,title); + // Notify the event to the excecution frame + m_frameManager->eventStage(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl:: +//============================================================================= +void SPELLserviceImpl::displayStage( const std::string& id, const std::string& title ) +{ + if (title != "") + { + m_cif->write("Step " + id + ": " + title, LanguageConstants::SCOPE_STEP ); + } + else + { + m_cif->write("Step " + id, LanguageConstants::SCOPE_STEP ); + } + // Change also in callstack + m_callstack->setStage(id,title); + + // Update the data in the procedure + PyObject* proc = getVariableManager().getVariableRef(DatabaseConstants::PROC); + if (proc != NULL) + { + SPELLsafePythonOperations ops("SPELLexecutor::displayStage()"); + PyObject* pyId = SSTRPY(id); + PyObject* pyDesc = SSTRPY(title); + PyObject* list = PyList_New(2); + PyList_SetItem(list,0,pyId); + PyList_SetItem(list,1,pyDesc); + Py_INCREF(pyId); + Py_INCREF(pyDesc); + + // Get the previous step value if any + PyObject* prev = PyDict_GetItemString(proc, DatabaseConstants::STEP.c_str()); + + // Set the new value + PyDict_SetItemString( proc, DatabaseConstants::STEP.c_str(), list); + + // If there was a previous value, set it as PREVIOUS + if (prev != NULL) + { + PyDict_SetItemString( proc, DatabaseConstants::PREV_STEP.c_str(), prev); + } + } + + // prepares the message to be sent to GUI + m_cif->resetClose(); +} + +//============================================================================= +// METHOD : SPELLexecutorImpl:: +//============================================================================= +void SPELLserviceImpl::addDispatchListener( SPELLdispatchListener* listener ) +{ + if (std::find(m_listeners.begin(), m_listeners.end(), listener) == m_listeners.end()) + { + DEBUG( "Added listener " + listener->getId() ); + m_listeners.push_back(listener); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl:: +//============================================================================= +void SPELLserviceImpl::removeDispatchListener( SPELLdispatchListener* listener ) +{ + SPELLdispatchListeners::iterator it = std::find(m_listeners.begin(), m_listeners.end(), listener); + if (it != m_listeners.end()) + { + DEBUG( "Removed listener " + listener->getId() ); + m_listeners.erase(it); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl:: +//============================================================================= +void SPELLserviceImpl::notifyLineEvent( PyFrameObject* frame, const std::string& file, const int line, const std::string& name ) +{ + //DEBUG( "Notify listeners: line event"); + SPELLdispatchListeners::iterator it; + for( it = m_listeners.begin(); it != m_listeners.end(); it++) + { + //DEBUG( " - notify dispatch listener " + (*it)->getId() ); + (*it)->callbackEventLine( frame, file, line, name ); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl:: +//============================================================================= +void SPELLserviceImpl::notifyCallEvent( PyFrameObject* frame, const std::string& file, const int line, const std::string& name ) +{ + //DEBUG( "Notify listeners: call event"); + SPELLdispatchListeners::iterator it; + for( it = m_listeners.begin(); it != m_listeners.end(); it++) + { + //DEBUG( " - notify dispatch listener " + (*it)->getId() ); + (*it)->callbackEventCall( frame, file, line, name ); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl:: +//============================================================================= +void SPELLserviceImpl::notifyReturnEvent( PyFrameObject* frame, const std::string& file, const int line, const std::string& name ) +{ + //DEBUG( "Notify listeners: return event"); + SPELLdispatchListeners::iterator it; + for( it = m_listeners.begin(); it != m_listeners.end(); it++) + { + //DEBUG( " - notify dispatch listener " + (*it)->getId() ); + (*it)->callbackEventReturn( frame, file, line, name ); + } +} + +//============================================================================= +// METHOD : SPELLexecutorImpl:: +//============================================================================= +void SPELLserviceImpl::notifyErrorEvent( PyFrameObject* frame, const std::string& file, const int line, const std::string& name ) +{ + DEBUG( "Notify listeners: error event"); + SPELLdispatchListeners::iterator it; + for( it = m_listeners.begin(); it != m_listeners.end(); it++) + { + DEBUG( " - notify dispatch listener " + (*it)->getId() ); + (*it)->callbackEventError( frame, file, line, name ); + } + DEBUG( "Notify listeners: error event done"); +} diff --git a/lib/SPELL_EXC/src/SPELLserviceInterpreter.C b/lib/SPELL_EXC/src/SPELLserviceInterpreter.C new file mode 100644 index 0000000..c099963 --- /dev/null +++ b/lib/SPELL_EXC/src/SPELLserviceInterpreter.C @@ -0,0 +1,532 @@ +// ################################################################################ +// FILE : SPELLinterpreter.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the custom interpreter +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// System includes --------------------------------------------------------- +// Local includes ---------------------------------------------------------- +#include "SPELL_EXC/SPELLserviceInterpreter.H" +#include "SPELL_EXC/SPELLgoto.H" +#include "SPELL_EXC/SPELLexecutor.H" +#include "SPELL_EXC/SPELLexecutorStatus.H" +#include "SPELL_EXC/SPELLcontroller.H" +#include "SPELL_EXC/SPELLscheduler.H" +#include "SPELL_EXC/SPELLcallstack.H" +#include "SPELL_EXC/SPELLexecutorImpl.H" +#include "SPELL_PRD/SPELLservicePython.H" +// Project includes -------------------------------------------------------- +#include "SPELL_PRD/SPELLprocedureManager.H" +#include "SPELL_UTIL/SPELLerror.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLpythonHelper.H" +#include "SPELL_UTIL/SPELLutils.H" +#include "SPELL_CFG/SPELLconfiguration.H" +#include "SPELL_WRP/SPELLregistry.H" +#include "SPELL_WRP/SPELLdriverManager.H" +#include "SPELL_WS/SPELLwsWarmStartImpl.H" +#include "SPELL_WS/SPELLwsStartupInfo.H" +#include "SPELL_CIF/SPELLcif.H" +#include "SPELL_WRP/SPELLdatabaseManager.H" + +// GLOBALS ///////////////////////////////////////////////////////////////// + +// Interpreter singleton instance +static SPELLserviceInterpreter* s_instance = NULL; + +// Warmstart location name +static const std::string WS_LOCATION = "ws"; +static const std::string ASRUN_LOCATION = "ar"; + +//============================================================================= +// CONSTRUCTOR: SPELLinterpreter::SPELLinterpreter +//============================================================================= +SPELLserviceInterpreter::SPELLserviceInterpreter() +{ + m_serviceImpl = NULL; + m_cif = NULL; + m_controller = NULL; + m_scheduler = NULL; + m_callstack = NULL; + m_frameManager = NULL; + m_warmStart = NULL; + m_servicePath = ""; + m_service = ""; +} + +//============================================================================= +// DESTRUCTOR: SPELLinterpreter::~SPELLinterpreter +//============================================================================= +SPELLserviceInterpreter::~SPELLserviceInterpreter() +{ + DEBUG("[*] Cleaning up"); + if (m_warmStart != NULL) + { + DEBUG("[*] Cleaning warm start mechanism"); + delete m_warmStart; + m_warmStart = NULL; + } + if (m_callstack != NULL) + { + delete m_callstack; + m_callstack = NULL; + } + if (m_controller != NULL) + { + delete m_controller; + m_controller = NULL; + } + if (m_scheduler != NULL) + { + delete m_scheduler; + m_scheduler = NULL; + } + if (m_serviceImpl != NULL) + { + delete m_serviceImpl; + m_serviceImpl = NULL; + } + SPELLpythonHelper::instance().finalize(); + DEBUG("[*] End"); +} + +//============================================================================= +// METHOD : SPELLinterpreter::instance +//============================================================================= +SPELLserviceInterpreter& SPELLserviceInterpreter::instance() +{ + if (s_instance == NULL) + { + s_instance = new SPELLserviceInterpreter(); + } + return *s_instance; +} + +//============================================================================= +// METHOD : SPELLinterpreter::initialize +//============================================================================= +void SPELLserviceInterpreter::initialize( const SPELLinterpreterConfig& config, SPELLcif* cif ) +{ + LOG_INFO("[*] Initializing interpreter"); + + m_cif = cif; + m_service = config.procId; + m_config = config; +} + +//============================================================================= +// METHOD : SPELLinterpreter::mainLoop +//============================================================================= +void SPELLserviceInterpreter::mainLoop() +{ + // Load the SPELL configuration (will fail with exception if there is an error) + SPELLconfiguration::instance().loadConfig(m_config.configFile); + + // Configure the time format if defined in configuration + std::string format = SPELLconfiguration::instance().getCommonParameter("TdsTimeFormat"); + if (format.length()!=0) + { + if (format.compare ("1") == 0) + { + SPELLutils::setTimeFormat(TIME_FORMAT_SLASH); + } + else if (format.compare("0")==0) + { + SPELLutils::setTimeFormat(TIME_FORMAT_DOT); + } + } + + LOG_INFO("[***] Start main loop"); + + // Create and connect all execution control objects + if (prepareObjects()) + { + SPELLsafePythonOperations ops("Service Init"); + // Now try to initialize (compile) the execution frame. + if (prepareExecution()) + { + // If we are in recovery mode: + if (m_config.recover) + { + recover(); + } + else // Or we are in running mode + { + execute(); + } + } + + // After the execution cycle, wait for the user/server to autorize closure + DEBUG("[***] Main loop waiting for CIF"); + m_cif->waitClose(); + + // Once all is finished, stop controller + DEBUG("[***] Stopping command controller"); + m_controller->stop(); + + DEBUG("[***] Cleaning CIF"); + m_cif->cleanup(false); + } + + // Cleanup all warm start files + if (m_warmStart) + { + m_warmStart->cleanup(); + } + + LOG_INFO("[***] End main loop"); +} + +//============================================================================= +// METHOD : SPELLinterpreter::prepareWarmStart +//============================================================================= +void SPELLserviceInterpreter::prepareWarmStart( const SPELLcontextConfig& ctxConfig ) +{ + // Prepare warmstart file if applicable + if (m_config.warmstart) + { + char* home = getenv("SPELL_DATA"); + if (home == NULL) + { + std::string msg = "Unable to setup persistent file, no SPELL_DATA environment variable defined"; + LOG_ERROR(" " + msg); + m_cif->error(msg, LanguageConstants::SCOPE_SYS ); + m_config.warmstart = false; + return; + } + + // Get the location of WS files + std::string wsdir = ctxConfig.getLocationPath( WS_LOCATION ); + std::string saveMode = ctxConfig.getExecutorParameter(ExecutorConstants::SaveStateMode); + + // Prepare the WS configuration + SPELLwsStartupInfo startup; + + if (saveMode == "DISABLED") + { + LOG_WARN("Warmstart disabled by configuration"); + startup.persistentFile = ""; + } + else + { + startup.persistentFile = STR(home) + PATH_SEPARATOR + wsdir + PATH_SEPARATOR; + // Check that the directory exists + if (!SPELLutils::pathExists(startup.persistentFile)) + { + std::string msg = "Unable to setup persistent file, warm-start directory not found: " + startup.persistentFile; + LOG_ERROR(" " + msg); + m_cif->error(msg, LanguageConstants::SCOPE_SYS ); + m_config.warmstart = false; + return; + } + + // Character replacements for constructing the file ids + std::string theId = m_service; + SPELLutils::replace( theId, ".py", "" ); + SPELLutils::replace( theId, "..", "" ); + SPELLutils::replace( theId, "//", "/" ); + SPELLutils::replace( theId, PATH_SEPARATOR, "__" ); + startup.persistentFile += m_config.timeId + "_Executor_" + theId; + LOG_INFO("Warm start persistent file: " + startup.persistentFile); + } + + + LOG_INFO("Warm start files location: " + wsdir); + LOG_INFO("Warm start working mode: " + saveMode); + LOG_INFO("Warm start service file: " + m_service); + + + // Set the configured working mode + startup.workingMode = StringToWorkingMode(saveMode); + + // Set the procedure file + startup.procedureFile = m_service; + + // Set the recovery file if any + if (m_config.persis != "") + { + startup.recoveryFile = STR(home) + PATH_SEPARATOR + wsdir + PATH_SEPARATOR + m_config.persis; + startup.performRecovery = true; + } + + try + { + // Create the warm start support + m_warmStart = new SPELLwsWarmStartImpl(); + // Initialize it + m_warmStart->initialize( startup ); + // Set the warm start support in the execution frame + m_frameManager->setWarmStart( m_warmStart ); + } + catch(SPELLcoreException& ex) + { + if (m_warmStart != NULL) delete m_warmStart; + m_warmStart = NULL; + throw ex; + } + } + else + { + m_cif->warning("ATTENTION: no warmstart mechanism is being used", LanguageConstants::SCOPE_SYS ); + LOG_WARN("[***] No warmstart mechanism will be used"); + } +} + +//============================================================================= +// METHOD : SPELLinterpreter::prepareCIF +//============================================================================= +void SPELLserviceInterpreter::prepareCIF( const SPELLcontextConfig& ctxConfig ) +{ + DEBUG(" Setup client interface"); + + // Create the configuration for the CIF + SPELLcifStartupInfo cifStartup; + cifStartup.procId = m_service; + cifStartup.procName = "service startups"; + cifStartup.contextName = m_config.ctxName; + cifStartup.contextPort = m_config.ctxPort; + cifStartup.timeId = m_config.timeId; + + m_cif->setup( cifStartup ); +} + +//============================================================================= +// METHOD : SPELLinterpreter::prepareObjects +//============================================================================= +void SPELLserviceInterpreter::recoverCIF( const SPELLcontextConfig& ctxConfig ) +{ + char* home = getenv("SPELL_DATA"); + if (home == NULL) + { + std::string msg = "Unable to recover CIF asrun, no SPELL_DATA environment variable defined"; + LOG_ERROR(" " + msg); + m_cif->error(msg, LanguageConstants::SCOPE_SYS ); + return; + } + + // Get the location of WS files + std::string ardir = ctxConfig.getLocationPath( ASRUN_LOCATION ); + + std::string originalAsRun = std::string(home) + PATH_SEPARATOR + ardir + PATH_SEPARATOR + m_config.persis + ".ASRUN"; + + m_cif->useAsRun( originalAsRun ); +} + +//============================================================================= +// METHOD : SPELLinterpreter::prepareObjects +//============================================================================= +const bool SPELLserviceInterpreter::prepareObjects() +{ + LOG_INFO("[***] Preparing objects"); + // Setup the client interface. The standalone one does + // just a few value initializations, the server one + // does the login in the context process + assert(m_cif != NULL); + bool result = true; + try + { + DEBUG(" Initializing Python interface"); + SPELLpythonHelper::instance().initialize(); + SPELLsafePythonOperations ops("Service Init"); + // Install log support asap + Log_Install(); + SpellService_Install(); + DEBUG(" Loading execution framework functions"); + // Setup the execution environment + SPELLpythonHelper::instance().loadFramework(); + + // Load the configuration on python side for the language and drivers + SPELLconfiguration::instance().loadPythonConfig(m_config.configFile); + // Get the context configuration + SPELLcontextConfig& ctxConfig = SPELLconfiguration::instance().getContext(m_config.ctxName); + + + // User library + m_libPath = SPELLprocedureManager::instance().getLibPath(); + + DEBUG(" Creating and linking"); + // Create the executor instance + m_serviceImpl = new SPELLserviceImpl(); + // Set the reference for the static accessor (Python layer and others) + SPELLexecutor::setInstance( m_serviceImpl ); + + m_controller = new SPELLcontroller( m_config.procId ); + m_scheduler = new SPELLscheduler(false); + m_callstack = new SPELLcallstack(); + // Create and initialize the execution frame + m_frameManager = new SPELLframeManager(); + + DEBUG(" Initializing Service"); + // Initialize the executor with the objects + m_serviceImpl->initialize(m_cif, m_controller, m_scheduler, m_callstack, m_frameManager); + + // Setup the CIF now + try + { + prepareCIF( ctxConfig ); + } + catch(SPELLcoreException& ex) + { + // If we have no CIF there is no way to communicate anything. Bail out + LOG_ERROR("[FATAL] Failed to setup CIF: cannot start executor."); + LOG_ERROR("[FATAL] Error was: " + ex.what()); + ::exit(EIO); + } + + + // Install the CIF object in the SPELL registry + DEBUG(" Installing client interface"); + ClientIF_Install(); + + // Setup procedure manager + DEBUG(" Preparing procedure manager"); + SPELLdriverManager::instance().setup(m_config.ctxName); + SPELLdatabaseManager::instance().loadBuiltinDatabases(); + SPELLprocedureManager::instance().setup(m_config.ctxName); + SPELLpyService& service = SPELLprocedureManager::instance().getService(m_service); + m_servicePath = service.getBasePath(); + + // Prepare and configure warm start mechanism now + DEBUG(" Preparing warm start"); + prepareWarmStart( ctxConfig ); + + // Let the executor prepare all the rest it needs for execution. Anything done here + // shall be independent from reloads/aborts, etc. + m_serviceImpl->prepare( m_service, ctxConfig ); + + DEBUG(" Starting controller"); + m_controller->begin(); + + // If we are in recovery mode, recover also the original asrun file + if (m_config.persis != "") + { + recoverCIF( ctxConfig ); + } + + LOG_INFO("[***] Objects ready"); + } + catch(SPELLcoreException& ex) + { + std::string msg = "[***] Failed to create objects: " + ex.what(); + LOG_ERROR(msg); + // Notify error only if we have a way to do it, that is, no CIF failure. + if (m_controller) + { + m_controller->setError( "Failed to initialize: " + ex.getError(), ex.getReason(), true); + } + result = false; + } + return result; +} + +//============================================================================= +// METHOD : SPELLinterpreter::prepareExecution +//============================================================================= +const bool SPELLserviceInterpreter::prepareExecution() +{ + LOG_INFO("[***] Preparing execution"); + try + { + DEBUG(" Installing executor binding"); + Executor_Install(); + + DEBUG(" Installing goto bindings"); + Goto_Install(); + Step_Install(); + + + + if (m_config.script) + { + // In this case the proc path is just the script + //m_procPath = m_procedure; + LOG_INFO(" Compiling script"); + + } + else + { + LOG_INFO(" Compiling procedure"); + // Compile and create initial execution frame + SPELLpyService& service = SPELLprocedureManager::instance().getService(m_service); + m_frameManager->initialize(service.getMainServiceFile()); + } + + // Set the procedures path + m_serviceImpl->setProcedurePath( m_servicePath ); + // Set the user library path + m_serviceImpl->setLibraryPath( m_libPath ); + + LOG_INFO("Executor procedure path: " + m_servicePath); + LOG_INFO("Executor library path: " + m_libPath); + + } + catch(SPELLcoreException& ex) + { + LOG_ERROR(" Catched error during preparation: " + ex.what()); + m_controller->setError( "Error during preparation: " + ex.getError(), ex.getReason(), true); + return false; + } + LOG_INFO("[***] Execution ready"); + return true; +} + +//============================================================================= +// METHOD : SPELLinterpreter::execute +//============================================================================= +void SPELLserviceInterpreter::execute() +{ + // Setup procedure manager + LOG_INFO("[*] Starting execution"); + + try + { + m_serviceImpl->execute(); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("[FATAL] Error during execution: " + ex.what()); + m_controller->setError( "Error during execution: " + ex.getError(), ex.getReason(), ex.isFatal()); + } + + LOG_INFO("[*] Execution finished"); +} + +//============================================================================= +// METHOD : SPELLinterpreter::recover +//============================================================================= +void SPELLserviceInterpreter::recover() +{ + // Setup procedure manager + LOG_INFO("[*] Recovering execution from persistent file"); + try + { + m_serviceImpl->recover(); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("[FATAL] Error during recovery: " + ex.what()); + /** \todo handle exception: send error */ + } + LOG_INFO("[*] Execution finished"); +} diff --git a/lib/SPELL_EXC/src/SPELLstepPython.C b/lib/SPELL_EXC/src/SPELLstepPython.C index 5754b94..94655fc 100644 --- a/lib/SPELL_EXC/src/SPELLstepPython.C +++ b/lib/SPELL_EXC/src/SPELLstepPython.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the Step Python bindings // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -80,8 +80,7 @@ static PyMethodDef Step_Methods[] = //============================================================================ static PyTypeObject Step_Type = { - PyObject_HEAD_INIT(NULL) - 0, //ob_size + PyVarObject_HEAD_INIT(NULL,0) "executor.Step", //tp_name sizeof(PyStepObject), //tp_basicsize 0, //tp_itemsize @@ -135,7 +134,7 @@ static int Step_Init( PyStepObject* self, PyObject* args, PyObject* kwds ) //============================================================================ static void Step_Dealloc( PyStepObject* self ) { - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ diff --git a/lib/SPELL_EXC/src/SPELLvariableManager.C b/lib/SPELL_EXC/src/SPELLvariableManager.C index 6888142..e16445b 100644 --- a/lib/SPELL_EXC/src/SPELLvariableManager.C +++ b/lib/SPELL_EXC/src/SPELLvariableManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the procedure variable manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_EXC/src/SPELLvariableMonitor.C b/lib/SPELL_EXC/src/SPELLvariableMonitor.C index c04e91e..bbbbcab 100644 --- a/lib/SPELL_EXC/src/SPELLvariableMonitor.C +++ b/lib/SPELL_EXC/src/SPELLvariableMonitor.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the procedure variable monitor // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -92,12 +92,16 @@ void SPELLvariableMonitor::retrieveGlobalVariables() std::string varName = PYSSTR(key); PyObject* object = PyDict_GetItem( dict, key ); + if (object == NULL) continue; + + Py_XINCREF(object); + if (shouldDiscard(varName,object)) continue; DEBUG("[VM] Processing " + varName); std::string type = PYSSTR( PyObject_Type(object) ); - std::string value = PYREPR( object ); + std::string value = PYCREPR( object ); DEBUG("[VM] Type : " + type); DEBUG("[VM] Value : " + value); @@ -107,6 +111,8 @@ void SPELLvariableMonitor::retrieveGlobalVariables() if (value == "") value = EMPTY_STRING; m_globalVariables.insert( std::make_pair(varName, SPELLvarInfo(varName, type, value, true)) ); + + Py_XDECREF(object); } } @@ -150,7 +156,7 @@ void SPELLvariableMonitor::retrieveLocalVariables() DEBUG("[VM] Processing " + varName); std::string type = PYSSTR( PyObject_Type(object) ); DEBUG("[VM] Type : " + type); - std::string value = PYREPR( object ); + std::string value = PYCREPR( object ); DEBUG("[VM] Value : " + value); DEBUG("[VM] Global : " + BSTR(false)); @@ -312,6 +318,12 @@ PyObject* SPELLvariableMonitor::getVariableRef( const std::string& name ) { return PyDict_GetItemString(m_frame->f_globals, name.c_str()); } + PyObject* builtins = PyEval_GetBuiltins(); + if (PyDict_Contains(builtins, pyName.get())) + { + return PyDict_GetItemString(builtins, name.c_str()); + } + return NULL; } @@ -369,13 +381,15 @@ bool SPELLvariableMonitor::shouldDiscard( const std::string& varName, PyObject* // Ignore internal flags if (varName == "__USERLIB__") return true; + if (varName.substr(0,2) == "__" ) return true; + + // Ignore callables and complex objects if (SPELLpythonHelper::instance().isInstance(value, "Database", "spell.lib.adapter.databases.database")) return true; if (PyCallable_Check(value)) return true; if (PyFunction_Check(value)) return true; if (PyClass_Check(value)) return true; if (PyModule_Check(value)) return true; - if (PyInstance_Check(value)) return true; return false; } @@ -406,7 +420,7 @@ void SPELLvariableMonitor::analyze() PyObject* key = PyList_GetItem( globalsItemList.get(), index ); PyObject* object = PyDict_GetItem( dict, key ); std::string varName = PYSTR(key); - + //std::cout << varName << " -> " << shouldDiscard(varName,object) << std::endl; if (shouldDiscard(varName,object)) continue; it = m_globalVariables.find(varName); diff --git a/lib/SPELL_IPC/Makefile.am b/lib/SPELL_IPC/Makefile.am index 27b40f2..713afe3 100644 --- a/lib/SPELL_IPC/Makefile.am +++ b/lib/SPELL_IPC/Makefile.am @@ -1,47 +1,61 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_IPC.la -CPPFLAGS += -Wno-pragmas -libSPELL_IPC_la_LDFLAGS = -version-info 3:0:0 -libSPELL_IPC_la_SOURCES = \ - src/SPELLipcDataChunk.C \ - src/SPELLipcIncoming.C \ - src/SPELLipcMessage.C \ - src/SPELLipcServerInterface.C \ - src/SPELLipcServerInterfaceSingle.C \ - src/SPELLipcChannel.C \ - src/SPELLsocket.C \ - src/SPELLipcClientInterface.C \ - src/SPELLipcHelper.C \ - src/SPELLipcInput.C \ - src/SPELLipcMessageMailbox.C \ - src/SPELLipcOutput.C \ - src/SPELLipcTrash.C - - +lib_LTLIBRARIES = libSPELL_IPC_v2.la libSPELL_IPC_v3.la + +libSPELL_IPC_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} -Wno-pragmas + +libSPELL_IPC_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_IPC_v2_la_SOURCES = src/SPELLipcDataChunk.C \ + src/SPELLipcIncoming.C \ + src/SPELLipcMessage.C \ + src/SPELLipcServerInterface.C \ + src/SPELLipcServerInterfaceSingle.C \ + src/SPELLipcChannel.C \ + src/SPELLsocket.C \ + src/SPELLipcClientInterface.C \ + src/SPELLipcHelper.C \ + src/SPELLipcInput.C \ + src/SPELLipcMessageMailbox.C \ + src/SPELLipcOutput.C \ + src/SPELLipcTrash.C + +libSPELL_IPC_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} -Wno-pragmas + +libSPELL_IPC_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_IPC_v3_la_SOURCES = src/SPELLipcDataChunk.C \ + src/SPELLipcIncoming.C \ + src/SPELLipcMessage.C \ + src/SPELLipcServerInterface.C \ + src/SPELLipcServerInterfaceSingle.C \ + src/SPELLipcChannel.C \ + src/SPELLsocket.C \ + src/SPELLipcClientInterface.C \ + src/SPELLipcHelper.C \ + src/SPELLipcInput.C \ + src/SPELLipcMessageMailbox.C \ + src/SPELLipcOutput.C \ + src/SPELLipcTrash.C + \ No newline at end of file diff --git a/lib/SPELL_IPC/src/SPELLipcChannel.C b/lib/SPELL_IPC/src/SPELLipcChannel.C index 29efc98..2e72709 100644 --- a/lib/SPELL_IPC/src/SPELLipcChannel.C +++ b/lib/SPELL_IPC/src/SPELLipcChannel.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of a peer ipc channel // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -27,11 +27,13 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // Local includes ---------------------------------------------------------- #include "SPELL_IPC/SPELLipcChannel.H" +#include "SPELL_IPC/SPELLipcError.H" // Project includes -------------------------------------------------------- #include "SPELL_UTIL/SPELLlog.H" #include "SPELL_UTIL/SPELLutils.H" #include "SPELL_SYN/SPELLmonitor.H" #include "SPELL_SYN/SPELLsyncError.H" +#include "SPELL_IPC/SPELLipcError.H" // System includes --------------------------------------------------------- @@ -60,23 +62,25 @@ SPELLipcChannel::SPELLipcChannel( const std::string& name, SPELLsocket* socket, SPELLipcChannel::~SPELLipcChannel() { DEBUG( NAME + "Destroying channel"); - if (m_socket) - { - delete m_socket; - m_socket = NULL; - } if (m_output) { + m_output->disconnect(false); m_output->join(); delete m_output; m_output = NULL; } if (m_input) { + m_input->disconnect(); m_input->join(); delete m_input; m_input = NULL; } + if (m_socket) + { + delete m_socket; + m_socket = NULL; + } DEBUG( NAME + "Channel destroyed"); } @@ -89,6 +93,7 @@ void SPELLipcChannel::connect() m_output = createOutput(); m_input = createInput(); m_input->start(); + m_output->start(); setConnected(true); DEBUG( NAME + "Connect done"); } @@ -138,8 +143,17 @@ void SPELLipcChannel::disconnect() { DEBUG( NAME + "Disconnect"); m_connected = false; - m_output->disconnect(true); + try + { + m_output->disconnect(true); + } + catch (SPELLipcError& ex) + { + + } + m_input->setHandleEOC(false); m_input->disconnect(); + m_input->join(); m_socket->close(); DEBUG( NAME + "Disconnect finished"); } @@ -217,11 +231,18 @@ void SPELLipcChannel::requestedEOC() { if (isConnected()) { - setConnected(false); - DEBUG( NAME + "Peer requested end of conversation"); - m_output->disconnect(false); - m_socket->shutdown(); - DEBUG( NAME + "Disconnected, reporting interface"); - m_ifc.connectionClosed(m_key); + try + { + setConnected(false); + DEBUG( NAME + "Peer requested end of conversation"); + m_output->disconnect(false); + m_socket->shutdown(); + DEBUG( NAME + "Disconnected, reporting interface"); + m_ifc.connectionClosed(m_key); + } + catch (SPELLipcError& ex) + { + // Ignore the IPC error if it happens when requeting EOC + } } } diff --git a/lib/SPELL_IPC/src/SPELLipcClientInterface.C b/lib/SPELL_IPC/src/SPELLipcClientInterface.C index e732e9e..8ac7e76 100644 --- a/lib/SPELL_IPC/src/SPELLipcClientInterface.C +++ b/lib/SPELL_IPC/src/SPELLipcClientInterface.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the interface for clients // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -59,6 +59,7 @@ SPELLipcClientInterface::SPELLipcClientInterface( const std::string& name, const { m_channel = NULL; m_listener = NULL; + m_socket = NULL; m_trash.start(); m_timeoutIpcRetry = SPELLconfiguration::instance().commonOrDefault("RetryTimeout", IPC_REQUEST_RETRY_DEFAULT_DELAY_MSEC ); @@ -339,15 +340,16 @@ void SPELLipcClientInterface::incomingRequest( const SPELLipcMessage& msg ) SPELLmonitor m(m_lock); - DEBUG(NAME + "Incoming request (" + msg.getSequenceStr() + ")"); + DEBUG(NAME + "Incoming request (" + msg.getSequenceStr() + ") for ("+msg.get("User")+")"); try { DEBUG(NAME + "Creating request processor (" + msg.getSequenceStr() + ")"); - SPELLipcIncomingRequest* ireq = new SPELLipcIncomingRequest( msg.requestId(), m_ifcName, msg, m_channel->getWriter(), *m_listener ); + SPELLipcIncomingRequest* ireq = new SPELLipcIncomingRequest( msg.requestId(), m_ifcName, msg, m_lock, &m_channel, *m_listener ); ireq->start(); - DEBUG(NAME + "Wait request processor to start (" + msg.getSequenceStr() + ")"); - ireq->waitStarted(); + //DEBUG(NAME + "Wait request processor to start (" + msg.getSequenceStr() + ")"); + // TODO: No reason to wait here. Remove once validated. + //ireq->waitStarted(); DEBUG(NAME + "Place in trash"); m_trash.place(ireq); DEBUG(NAME + "Creating request processor DONE (" + msg.getSequenceStr() + ")"); @@ -365,7 +367,7 @@ void SPELLipcClientInterface::incomingRequest( const SPELLipcMessage& msg ) //============================================================================= void SPELLipcClientInterface::incomingMessage( const SPELLipcMessage& msg ) { - DEBUG(NAME + "IPC interface incoming message " + msg.getId()); + DEBUG(NAME + "IPC interface incoming message " + msg.getId() + " for ("+msg.get("User")+")"); if (!m_connected) return; if (m_listener == NULL) return; @@ -374,11 +376,12 @@ void SPELLipcClientInterface::incomingMessage( const SPELLipcMessage& msg ) SPELLipcIncomingMessage* imsg = new SPELLipcIncomingMessage( msgId, m_ifcName, msg, *m_listener); imsg->start(); - DEBUG(NAME + "Wait processor to start"); - imsg->waitStarted(); - DEBUG(NAME + "Place in trash"); + //DEBUG(NAME + "Wait processor to start"); + // TODO: No reason to wait here. Remove once validated. + //imsg->waitStarted(); + //DEBUG(NAME + "Place in trash"); m_trash.place(imsg); - DEBUG(NAME + "IPC interface incoming message OUT"); + //DEBUG(NAME + "IPC interface incoming message OUT"); } //============================================================================= @@ -567,3 +570,10 @@ void SPELLipcClientInterface::removeListener() m_listener = NULL; } +SPELLipcMessage SPELLipcClientInterface::getPingMessage() +{ + if (m_listener != NULL) + return m_listener->getPingMessage(); + static SPELLipcMessage empty = SPELLipcMessage(); + return empty; +}; diff --git a/lib/SPELL_IPC/src/SPELLipcDataChunk.C b/lib/SPELL_IPC/src/SPELLipcDataChunk.C index ee78224..1307e43 100644 --- a/lib/SPELL_IPC/src/SPELLipcDataChunk.C +++ b/lib/SPELL_IPC/src/SPELLipcDataChunk.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the IPC data chunker // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_IPC/src/SPELLipcHelper.C b/lib/SPELL_IPC/src/SPELLipcHelper.C index efc196d..fffd854 100644 --- a/lib/SPELL_IPC/src/SPELLipcHelper.C +++ b/lib/SPELL_IPC/src/SPELLipcHelper.C @@ -5,7 +5,7 @@ // DESCRIPTION: IPC utilities // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_IPC/src/SPELLipcIncoming.C b/lib/SPELL_IPC/src/SPELLipcIncoming.C index d1fca4c..7cbfcf3 100644 --- a/lib/SPELL_IPC/src/SPELLipcIncoming.C +++ b/lib/SPELL_IPC/src/SPELLipcIncoming.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the incoming message handler // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -49,10 +49,11 @@ SPELLipcIncomingBase::SPELLipcIncomingBase( const std::string& id, const std::st m_processingFinishEvent(false), m_processingStartEvent(false), m_key(msg.getKey()), - m_finished(false), - m_started(false) + m_started(false), + m_finished(false) + { - DEBUG( NAME + "Created"); + //DEBUG( NAME + "Created"); DEBUG( NAME + "Using message " + m_message.getId()); m_processingFinishEvent.clear(); m_processingStartEvent.clear(); @@ -63,7 +64,7 @@ SPELLipcIncomingBase::SPELLipcIncomingBase( const std::string& id, const std::st //============================================================================= SPELLipcIncomingBase::~SPELLipcIncomingBase() { - DEBUG("Destroyed"); + //DEBUG("Destroyed"); m_finished = true; } @@ -128,7 +129,6 @@ void SPELLipcIncomingBase::setFinished() //============================================================================= bool SPELLipcIncomingBase::isFinished() { - SPELLmonitor m(m_dataLock); return m_finished; } @@ -164,16 +164,16 @@ int SPELLipcIncomingBase::getKey() //============================================================================= bool SPELLipcIncomingBase::wait( long timeoutMsec ) { - DEBUG( NAME + "Wait finish " + getMessage().getSequenceStr()); + //DEBUG( NAME + "Wait finish " + getMessage().getSequenceStr()); bool timedout = m_processingFinishEvent.wait( timeoutMsec ); if (timedout) { - DEBUG( NAME + "Finish timeout (" + ISTR(timeoutMsec) + ") " + getMessage().getSequenceStr()); - DEBUG( "MESSAGE " + getMessage().dataStr()); + //DEBUG( NAME + "Finish timeout (" + ISTR(timeoutMsec) + ") " + getMessage().getSequenceStr()); + //DEBUG( "MESSAGE " + getMessage().dataStr()); } else { - DEBUG( NAME + "Wait finish done " + getMessage().getSequenceStr()); + //DEBUG( NAME + "Wait finish done " + getMessage().getSequenceStr()); } return timedout; } @@ -187,6 +187,7 @@ void SPELLipcIncomingBase::finish() if (m_finished) return; m_processingFinishEvent.set(); m_finished = true; + m_message.clear(); } //============================================================================= @@ -222,11 +223,11 @@ SPELLipcIncomingMessage::~SPELLipcIncomingMessage() //============================================================================= void SPELLipcIncomingMessage::run() { - DEBUG( NAME + "Incoming message starting"); + //DEBUG( NAME + "Incoming message starting"); setStarted(); getListener().processMessage(getMessage()); finish(); - DEBUG( NAME + "Incoming message finished"); + //DEBUG( NAME + "Incoming message finished"); } //============================================================================= @@ -235,11 +236,12 @@ void SPELLipcIncomingMessage::run() SPELLipcIncomingRequest::SPELLipcIncomingRequest( const std::string& requestId, const std::string& ifcName, SPELLipcMessage msg, - SPELLipcOutput& writer, + SPELLmutex& mutex, + SPELLipcChannel** channel, SPELLipcInterfaceListener& listener) : SPELLipcIncomingBase(requestId,ifcName,msg,listener), - m_writer(writer) + m_channel(channel),m_mutex(mutex) { } @@ -248,7 +250,7 @@ SPELLipcIncomingRequest::SPELLipcIncomingRequest( const std::string& requestId, //============================================================================= SPELLipcIncomingRequest::~SPELLipcIncomingRequest() { - DEBUG( NAME + "Incoming request destroyed"); + //DEBUG( NAME + "Incoming request destroyed"); } //============================================================================= @@ -282,7 +284,12 @@ void SPELLipcIncomingRequest::run() try { DEBUG( NAME + "Sending response (" + copy.getSequenceStr() + ")"); - m_writer.send(response); + { + SPELLmonitor m(m_mutex); + if (*m_channel != NULL){ + (*m_channel)->getWriter().send(response); + } + } DEBUG( NAME + "Response sent (" + copy.getSequenceStr() + ")"); } catch(...) diff --git a/lib/SPELL_IPC/src/SPELLipcInput.C b/lib/SPELL_IPC/src/SPELLipcInput.C index 1018f00..8eacff9 100644 --- a/lib/SPELL_IPC/src/SPELLipcInput.C +++ b/lib/SPELL_IPC/src/SPELLipcInput.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data reader // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -38,7 +38,7 @@ // DEFINES ///////////////////////////////////////////////////////////////// #define NAME std::string("[ INPUT-KEY(") + ISTR(m_key) + ")-" + m_ifcName + " ] " -#define SIZE sizeof(unsigned int) +#define SIZE sizeof(uint32_t) // store the diagnostics config #pragma GCC diagnostic push @@ -58,6 +58,9 @@ SPELLipcInput::SPELLipcInput( const std::string& name, SPELLsocket& skt, int pee m_ifcName = name; m_key = peerKey; m_connected = true; + m_totalPacketLength = 0; + m_requestedEOC = false; + m_lastByte = 0; DEBUG( NAME + "Created"); } @@ -89,7 +92,10 @@ int SPELLipcInput::readPacketLength( int numRead ) m_totalPacketLength = length; //DEBUG( NAME + " readPacketLength length: " + ISTR(m_totalPacketLength) + " " + SPELLutils::dumpString(tmp)); - assert(m_totalPacketLength < 9999000); + // Kill connection in case a client sends more then 1000MB in a single packet + if (m_totalPacketLength > 1000000000){ + throw SPELLipcError( NAME + "Peer has sent bad data disconnecting!", 0); + } // Remove the length bytes from buffer for( unsigned int byte=0; byte<(numRead-SIZE); byte++) @@ -126,6 +132,7 @@ int SPELLipcInput::readMoreBytes() { LOG_WARN( NAME + "WARNING: retrying read..."); retries++; + usleep(50000); if (retries ==3) throw SPELLipcError(NAME + "Peer has disconnected (errno " + ISTR(errNo) + ")", errNo ); } else break; @@ -147,8 +154,8 @@ int SPELLipcInput::completeLength( int numRead ) { int more = m_socket.receive( m_buffer+numRead, IPC_BUFFER_SIZE-numRead ); int errNo = errno; - assert( more >= 0 ); - if (more == 0 && m_connected) + //assert( more >= 0 ); + if (more <= 0 && m_connected) { DEBUG( NAME + "Read 0, peer has disconnected"); // When we receive zero it means that the peer has invoked @@ -156,6 +163,7 @@ int SPELLipcInput::completeLength( int numRead ) // interface is in charge of that, we shall not touch the // socket file descriptor here in SPELLipcInput. Just finish the // loop. + // If more < 0 there was an during the receive call => disconect the client throw SPELLipcError( NAME + "Peer has disconnected (errno " + ISTR(errNo) + ")", errNo); } return more; @@ -344,7 +352,7 @@ void SPELLipcInput::run() { case IPC_ERROR_UNKNOWN_MSG_TYPE: { - LOG_ERROR( NAME + "Unable to process packet. " + ex.getError()) + LOG_ERROR( NAME + "Unable to process packet. " + ex.getError()); DEBUG("Packet size: " + ISTR(m_packet.size())); DEBUG("Packet expected length: " + ISTR(m_totalPacketLength)); DEBUG(SPELLutils::dumpString(m_packet)); @@ -407,7 +415,7 @@ void SPELLipcInput::disconnect() //============================================================================= void SPELLipcInput::dispatch() { - DEBUG( NAME + " Dispatch TRY IN"); + //DEBUG( NAME + " Dispatch TRY IN"); if (!m_connected) return; //DEBUG( NAME + "Dispatching: '" + m_packet + "'"); assert( m_packet.size() == m_totalPacketLength ); @@ -436,8 +444,9 @@ void SPELLipcInput::dispatch() msg.fromData( dataForMsg ); msg.setCompressed(compression); msg.setKey(m_key); - - DEBUG( NAME + "DISPATCH: " + msg.dataStr()); + msg.setSender(msg.getSender()); + msg.set("User",m_socket.getUser()); + //DEBUG( NAME + "DISPATCH: " + msg.dataStr()); switch(msg.getType()) { @@ -478,7 +487,7 @@ void SPELLipcInput::dispatch() m_packet = ""; m_lastByte = NULL; m_totalPacketLength = 0; - DEBUG( NAME + "DISPATCH OUT"); + //DEBUG( NAME + "DISPATCH OUT"); } #pragma GCC diagnostic pop // restore diagnostics mode diff --git a/lib/SPELL_IPC/src/SPELLipcMessage.C b/lib/SPELL_IPC/src/SPELLipcMessage.C index 96a583d..a784232 100644 --- a/lib/SPELL_IPC/src/SPELLipcMessage.C +++ b/lib/SPELL_IPC/src/SPELLipcMessage.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the IPC message // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -104,6 +104,7 @@ SPELLipcMessage::SPELLipcMessage( SPELLipcMessage* msg ) m_key = msg->m_key; m_compressed = msg->m_compressed; m_properties = msg->m_properties; + set("User", msg->get("User")); } //============================================================================= @@ -195,7 +196,13 @@ std::string SPELLipcMessage::get( std::string key ) const } return it->second; } - +void SPELLipcMessage::clear( ) +{ + m_properties.clear(); + m_id = ""; + m_senderId = ""; + m_receiverId = ""; +} //============================================================================= // METHOD: SPELLipcMessage:getRef //============================================================================= diff --git a/lib/SPELL_IPC/src/SPELLipcMessageMailbox.C b/lib/SPELL_IPC/src/SPELLipcMessageMailbox.C index 6ae3c8b..955752e 100644 --- a/lib/SPELL_IPC/src/SPELLipcMessageMailbox.C +++ b/lib/SPELL_IPC/src/SPELLipcMessageMailbox.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the message mailbox // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -108,22 +108,6 @@ void SPELLipcMessageMailbox::shutdown() DEBUG(NAME + "Shutdown") m_working = false; } - cancelAll(); -} - -//============================================================================= -// METHOD: SPELLipcMessageMailbox::cancelAll() -//============================================================================= -void SPELLipcMessageMailbox::cancelAll() -{ - SPELLipcMessageQueueMap::iterator mit; - for( mit = m_queueMap.begin(); mit != m_queueMap.end(); mit++) - { - SPELLipcMessage* dummyResponse = new SPELLipcMessage("dummy"); - dummyResponse->setType( MSG_TYPE_RESPONSE ); - SPELLipcMessageQueue* queue = (*mit).second; - queue->push(dummyResponse); - } } //============================================================================= diff --git a/lib/SPELL_IPC/src/SPELLipcOutput.C b/lib/SPELL_IPC/src/SPELLipcOutput.C index 7b06f0d..5fb960e 100644 --- a/lib/SPELL_IPC/src/SPELLipcOutput.C +++ b/lib/SPELL_IPC/src/SPELLipcOutput.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data writer // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -33,15 +33,16 @@ #include "SPELL_SYN/SPELLmonitor.H" #include "SPELL_UTIL/SPELLlog.H" #include "SPELL_UTIL/SPELLcompression.H" +#include "SPELL_EXC/SPELLexecutor.H" // System includes --------------------------------------------------------- // DEFINES ///////////////////////////////////////////////////////////////// #define NAME std::string("[ OUTPUT-KEY(") + ISTR(m_key) + ")-" + m_ifcName + " ] " -#define SIZE sizeof(unsigned int) +#define SIZE sizeof(uint32_t) #define MINIMUM_SIZE_FOR_COMPRESSION 512 -#define PING_PERIOD_SEC 250 +#define PING_PERIOD_SEC 10 //============================================================================= // FUNCTION: encodeLength @@ -70,7 +71,7 @@ SPELLipcOutput::SPELLipcOutput( const std::string& name, SPELLsocket& skt, int m m_channel(chan), m_connected(true) { - DEBUG( NAME + "Created"); + //DEBUG( NAME + "Created"); m_ping.setId("ping"); m_ping.setType(MSG_TYPE_PING); } @@ -80,7 +81,9 @@ SPELLipcOutput::SPELLipcOutput( const std::string& name, SPELLsocket& skt, int m //============================================================================= SPELLipcOutput::~SPELLipcOutput() { - DEBUG( NAME + "Destroyed"); + //DEBUG( NAME + "Destroyed"); + m_connected = false; + this->join(); } //============================================================================= @@ -88,20 +91,21 @@ SPELLipcOutput::~SPELLipcOutput() //============================================================================= void SPELLipcOutput::send( SPELLipcMessage& msg ) { - msg.setKey( m_key ); - DEBUG( NAME + "SEND " + msg.dataStr() ); try { - std::string data = msg.data(); - bool doCompression = (msg.getCompressed() && (data.size()>= MINIMUM_SIZE_FOR_COMPRESSION)); + SPELLmonitor m(m_lock); + msg.setKey(m_key); + //DEBUG(NAME + "SEND " + msg.dataStr()); + std::string data = msg.data(); + bool doCompression = (msg.getCompressed() && (data.size()>= MINIMUM_SIZE_FOR_COMPRESSION)); if (doCompression) { int origSize = data.size(); std::cerr << "Compressing message of length " << data.size() << " bytes (" << msg.getId() << ")" << std::endl; - DEBUG("Raw data: " + SPELLutils::dumpString(data) + " size " + ISTR(data.length()) ); + //DEBUG("Raw data: " + SPELLutils::dumpString(data) + " size " + ISTR(data.length()) ); SPELLcompression compressor(data); data = compressor.compress(); - DEBUG("Compressed data: " + SPELLutils::dumpString(data) + " size " + ISTR(data.length()) ); + //DEBUG("Compressed data: " + SPELLutils::dumpString(data) + " size " + ISTR(data.length()) ); double ratio = ((double) origSize - (double) data.size())/((double)origSize) * 100.0; std::cerr << "Compressed to " << data.size() << " bytes (ratio " << std::setprecision(2) << ratio << "%)" << std::endl; } @@ -109,6 +113,8 @@ void SPELLipcOutput::send( SPELLipcMessage& msg ) data = (doCompression ? COMPRESSION_FLAG_TRUE_STR : COMPRESSION_FLAG_FALSE_STR) + data; writeData( data ); + + //DEBUG( NAME + "SEND DONE" ); } catch(SPELLipcError& err) { @@ -118,10 +124,11 @@ void SPELLipcOutput::send( SPELLipcMessage& msg ) LOG_ERROR( NAME + " Connection lost at output " + std::string(err.what()) ); LOG_ERROR( NAME + " Data being sent: " + msg.dataStr()); m_channel.outputFailed(errNo, err.getError()); + throw; } else { - DEBUG( NAME + "Send failed, not connected: " + err.what() + ", errno=" + ISTR(errNo) ); + //DEBUG( NAME + "Send failed, not connected: " + err.what() + ", errno=" + ISTR(errNo) ); } } catch(...) @@ -135,10 +142,10 @@ void SPELLipcOutput::send( SPELLipcMessage& msg ) } else { - DEBUG( NAME + "Send failed, not connected, errno=" + ISTR(errNo) ); + //DEBUG( NAME + "Send failed, not connected, errno=" + ISTR(errNo) ); } } - DEBUG( NAME + "SEND DONE" ); + } //============================================================================= @@ -147,8 +154,8 @@ void SPELLipcOutput::send( SPELLipcMessage& msg ) void SPELLipcOutput::disconnect( bool send_eoc ) { if (!m_connected) return; - - DEBUG( NAME + "SPELLipcOutput disconnect"); + SPELLmonitor m(m_lock); + //DEBUG( NAME + "SPELLipcOutput disconnect"); if (send_eoc) { @@ -157,9 +164,10 @@ void SPELLipcOutput::disconnect( bool send_eoc ) eoc.setType( MSG_TYPE_EOC ); eoc.setKey(m_key); send(eoc); - DEBUG( NAME + "EOC sent"); + //DEBUG( NAME + "EOC sent"); } m_connected = false; + } //============================================================================= @@ -167,13 +175,13 @@ void SPELLipcOutput::disconnect( bool send_eoc ) //============================================================================= void SPELLipcOutput::writeData( std::string data ) { - DEBUG( NAME + "Write data TRY-IN"); - SPELLmonitor m(m_lock); - DEBUG( NAME + "Write data IN"); + ////DEBUG( NAME + "Write data TRY-IN"); + + ////DEBUG( NAME + "Write data IN"); if (m_connected) { unsigned int sizeData = data.size(); - //DEBUG( NAME + "Packet length " + ISTR(sizeData)); + ////DEBUG( NAME + "Packet length " + ISTR(sizeData)); int success = 0; @@ -198,11 +206,11 @@ void SPELLipcOutput::writeData( std::string data ) numBytes = sizeData; success = 0; offset = 0; - //DEBUG("writeData sending message: " + SPELLutils::dumpString(data)); + ////DEBUG("writeData sending message: " + SPELLutils::dumpString(data)); while( numBytes > 0 ) { success = m_socket.send(&data[offset],numBytes); - if (success == -1 || errno != 0) + if (success == -1) { throw SPELLipcError( NAME + "Error while sending data, errno " + ISTR(errno) ); } @@ -214,7 +222,7 @@ void SPELLipcOutput::writeData( std::string data ) m_lastSentTime.setCurrent(); } - DEBUG( NAME + "Write data OUT"); + ////DEBUG( NAME + "Write data OUT"); } //============================================================================= @@ -234,21 +242,22 @@ void SPELLipcOutput::run() delta = currentTime - lastTime; if (delta.getSeconds() > PING_PERIOD_SEC) { - std::cerr << "ping [" << NAME << "] " << currentTime.toString() << std::endl; try { - send(m_ping); + SPELLipcMessage ping = this->m_channel.m_ifc.getPingMessage(); + if (ping.isVoid()) { + send(m_ping); + } else { + send(ping); + } } catch(...) { - DEBUG( NAME + "Abort ping loop"); return; } } - DEBUG( NAME + "1"); usleep(2000000); // 2 seconds - DEBUG( NAME + "2"); } - DEBUG( NAME + "Finish ping loop"); + //DEBUG( NAME + "Finish ping loop"); } diff --git a/lib/SPELL_IPC/src/SPELLipcServerInterface.C b/lib/SPELL_IPC/src/SPELLipcServerInterface.C index 5c0d407..3a29092 100644 --- a/lib/SPELL_IPC/src/SPELLipcServerInterface.C +++ b/lib/SPELL_IPC/src/SPELLipcServerInterface.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the interface for servers // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -27,6 +27,7 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // Local includes ---------------------------------------------------------- #include "SPELL_IPC/SPELLipcError.H" +#include "SPELL_SYN/SPELLsyncError.H" #include "SPELL_IPC/SPELLipcServerInterface.H" #include "SPELL_IPC/SPELLipcHelper.H" #include "SPELL_IPC/SPELLtimeoutValues.H" @@ -56,7 +57,7 @@ SPELLipcServerInterface::SPELLipcServerInterface( const std::string& name, int k m_serverPort = port; m_clients.clear(); - + m_clientKeysToRemove.clear(); m_serverSocket = NULL; m_ifcName = name; @@ -125,6 +126,19 @@ void SPELLipcServerInterface::run() bool disconnected = false; SPELLsocket* clientSocket = m_serverSocket->acceptClient( &disconnected ); + if (clientSocket == NULL && !m_clientKeysToRemove.empty()) + { + SPELLmonitor m(m_clientCleanupLock); + SPELLmonitor m2(m_clientLock); + + std::vector::const_iterator it = m_clientKeysToRemove.begin(); + while(it != m_clientKeysToRemove.end()) + { + removeClientInternal(*it); + ++it; + } + m_clientKeysToRemove.clear(); + } if ((!disconnected)&&clientSocket != NULL) { LOG_INFO(NAME + "Accepted new client connection"); @@ -159,6 +173,10 @@ void SPELLipcServerInterface::run() { LOG_ERROR("Failed to accept client: " + err.what()); } + catch(SPELLsyncError& err) + { + LOG_ERROR("Failed to accept client: " + err.what()); + } } if (disconnected) return; @@ -213,7 +231,8 @@ bool SPELLipcServerInterface::hasClient( int key ) DEBUG(NAME + "Has client TRY-IN"); SPELLmonitor m(m_clientLock); DEBUG(NAME + "Has client OUT"); - return ( m_clients.find(key) != m_clients.end() ); + SPELLipcChannelMap::iterator client = m_clients.find(key); + return ( client != m_clients.end() && client->second != NULL ); } //============================================================================= @@ -223,12 +242,11 @@ void SPELLipcServerInterface::addClient( int key, SPELLsocket* skt ) { DEBUG( NAME + "add Client TRY-IN"); - SPELLmonitor m(m_clientLock); - SPELLipcChannel* channel = new SPELLipcChannel( std::string(m_ifcName), skt, key, *this ); channel->connect(); - m_clients.insert( std::make_pair( key, channel )); + SPELLmonitor m(m_clientLock); + m_clients[key] = channel; LOG_INFO(NAME + "Added client " + ISTR(key)); m_clientConnectionEvent.set(); } @@ -237,6 +255,13 @@ void SPELLipcServerInterface::addClient( int key, SPELLsocket* skt ) // METHOD: SPELLipcServerInterface:removeClient //============================================================================= void SPELLipcServerInterface::removeClient( int key ) +{ + SPELLmonitor m(m_clientCleanupLock); + m_clientKeysToRemove.push_back(key); +} + + +void SPELLipcServerInterface::removeClientInternal( int key ) { if (!hasClient(key)) { @@ -244,12 +269,22 @@ void SPELLipcServerInterface::removeClient( int key ) } DEBUG( NAME + "Remove client TRY-IN" ); - SPELLmonitor m(m_clientLock); - SPELLipcChannelMap::iterator cit = m_clients.find(key); - cit->second->disconnect(); - delete cit->second; - m_clients.erase(cit); + if (cit->second != NULL) + { + cit->second->disconnect(); + + m_clientLock.unlock(); + m_clientCleanupLock.unlock(); + delete cit->second; + m_clientCleanupLock.lock(); + m_clientLock.lock(); + + // FIXME: Clearing clients may invalidate references in other threads + // resulting in a crash. This ought to be fixed with the new IPC layer. + //m_clients.erase(cit); + m_clients[cit->first] = NULL; + } std::vector::iterator kit = std::find(m_usedClientKeys.begin(), m_usedClientKeys.end(), key); if (kit != m_usedClientKeys.end()) { @@ -270,12 +305,20 @@ void SPELLipcServerInterface::removeAllClients() for( cit = m_clients.begin(); cit != m_clients.end(); cit++) { DEBUG(NAME + "Removing client " + ISTR(cit->first)); - cit->second->disconnect(); - delete cit->second; + if (cit->second != NULL) + { + cit->second->disconnect(); + delete cit->second; + m_clients[cit->first] = NULL; + } + DEBUG(NAME + "Removing client " + ISTR(cit->first) + " done" ); } m_usedClientKeys.clear(); - m_clients.clear(); + m_clientKeysToRemove.clear(); + // FIXME: Clearing clients may invalidate references in other threads + // resulting in a crash. This ought to be fixed with the new IPC layer. + //m_clients.clear(); DEBUG( NAME + "Remove all clients OUT"); } @@ -339,7 +382,7 @@ void SPELLipcServerInterface::connectionLost( int peerKey, int errNo, const std: cit = m_clients.find(peerKey); } } - if (cit != m_clients.end()) + if (cit != m_clients.end() && cit->second != NULL) { LOG_INFO(NAME + "#### Peer " + ISTR(peerKey) + " reconnected!"); } @@ -376,8 +419,9 @@ void SPELLipcServerInterface::sendMessage( int peerKey, SPELLipcMessage& msg ) { // Must set the appropriate key to the message beforehand. The key info is used // to identify requests and their responses + SPELLmonitor m(m_clientLock); msg.setKey(peerKey); - getChannel(peerKey).getWriter().send(msg); + (*getChannel(peerKey))->getWriter().send(msg); } catch(SPELLipcError& err) { @@ -397,9 +441,10 @@ SPELLipcMessage SPELLipcServerInterface::sendRequest( int peerKey, SPELLipcMessa DEBUG(NAME + "Get writer for peer " + ISTR(peerKey)); // Must set the appropriate key to the message beforehand. The key info is used // to identify requests and their responses + SPELLmonitor m(m_clientLock); msg.setKey(peerKey); DEBUG(NAME + "Sending now request to peer " + ISTR(peerKey)); - resp = performRequest( getChannel(peerKey), msg, timeoutMsec ); + resp = performRequest( **getChannel(peerKey), msg, timeoutMsec ); } catch( SPELLipcError& err ) { @@ -418,16 +463,16 @@ SPELLipcMessage SPELLipcServerInterface::sendRequest( int peerKey, SPELLipcMessa //============================================================================= // METHOD: SPELLipcServerInterface:getChannel //============================================================================= -SPELLipcChannel& SPELLipcServerInterface::getChannel( int peerKey ) +SPELLipcChannel** SPELLipcServerInterface::getChannel( int peerKey ) { - DEBUG( NAME + "Get channel TRY-IN" ); + //DEBUG( NAME + "Get channel TRY-IN" ); SPELLmonitor m(m_clientLock); SPELLipcChannelMap::iterator cit = m_clients.find(peerKey); - if (cit != m_clients.end()) + if (cit != m_clients.end() && cit->second != NULL) { - DEBUG( NAME + "Get channel OUT" ); - return *cit->second; + //DEBUG( NAME + "Get channel OUT" ); + return &m_clients[peerKey]; } DEBUG( NAME + "Get channel FAIL" ); throw SPELLipcError("Cannot get writer, no such key: " + ISTR(peerKey)); @@ -514,11 +559,11 @@ void SPELLipcServerInterface::cancelOutgoingRequests( int peerKey ) DEBUG(NAME + "Cancel outgoing requests for peer " + ISTR(peerKey)); // Obtain the channel corresponding to the client - SPELLipcChannel& channel = getChannel(peerKey); + SPELLipcChannel* channel = *getChannel(peerKey); // Iterate over the list of ongoing requests, and cancel the requests in the mailbox std::vector::iterator it; - std::vector requestList = channel.getOngoingRequests(); + std::vector requestList = channel->getOngoingRequests(); for( it = requestList.begin(); it != requestList.end(); it++) { DEBUG(NAME + " - cancel " + *it); @@ -543,7 +588,7 @@ void SPELLipcServerInterface::cancelIncomingRequests( int peerKey ) //============================================================================= void SPELLipcServerInterface::incomingRequest( const SPELLipcMessage& msg ) { - DEBUG(NAME + "IPC interface incoming request TRY IN (" + msg.getSequenceStr() + ")"); + //DEBUG(NAME + "IPC interface incoming request TRY IN (" + msg.getSequenceStr() + ")"); { SPELLmonitor m(m_ipcLock); @@ -559,23 +604,24 @@ void SPELLipcServerInterface::incomingRequest( const SPELLipcMessage& msg ) } } - DEBUG(NAME + "IPC interface incoming request IN (" + msg.getSequenceStr() + ")"); + //DEBUG(NAME + "IPC interface incoming request IN (" + msg.getSequenceStr() + ")"); try { - DEBUG(NAME + "Creating request processor (" + msg.getSequenceStr() + ")"); - SPELLipcIncomingRequest* ireq = new SPELLipcIncomingRequest( msg.requestId(), m_ifcName, msg, getChannel(msg.getKey()).getWriter(), *m_listener ); + //DEBUG(NAME + "Creating request processor (" + msg.getSequenceStr() + ")"); + SPELLipcIncomingRequest* ireq = new SPELLipcIncomingRequest( msg.requestId(), m_ifcName, msg,m_clientLock, getChannel(msg.getKey()), *m_listener ); ireq->start(); - DEBUG(NAME + "Wait request processor to start (" + msg.getSequenceStr() + ")"); - ireq->waitStarted(); + //DEBUG(NAME + "Wait request processor to start (" + msg.getSequenceStr() + ")"); + // TODO: No reason to wait here. Remove once validated. + //ireq->waitStarted(); { - DEBUG(NAME + "Place in trash"); + //DEBUG(NAME + "Place in trash"); SPELLmonitor m(m_ipcLock); m_trash.place(ireq); - DEBUG(NAME + "Place in trash DONE"); + //DEBUG(NAME + "Place in trash DONE"); } - DEBUG(NAME + "Creating request processor DONE (" + msg.getSequenceStr() + ")"); + //DEBUG(NAME + "Creating request processor DONE (" + msg.getSequenceStr() + ")"); } catch(SPELLipcError& err) { @@ -597,19 +643,20 @@ void SPELLipcServerInterface::incomingMessage( const SPELLipcMessage& msg ) if (m_listener == NULL) return; } - DEBUG(NAME + "Creating message processor"); + //DEBUG(NAME + "Creating message processor"); SPELLipcIncomingMessage* imsg = new SPELLipcIncomingMessage( msg.requestId(), m_ifcName, msg, *m_listener); imsg->start(); - DEBUG(NAME + "Wait processor to start"); - imsg->waitStarted(); + //DEBUG(NAME + "Wait processor to start"); + // TODO: No reason to wait here. Remove once validated. + //imsg->waitStarted(); { - DEBUG(NAME + "Place in trash"); + //DEBUG(NAME + "Place in trash"); SPELLmonitor m(m_ipcLock); m_trash.place(imsg); - DEBUG(NAME + "Place in trash DONE"); + //DEBUG(NAME + "Place in trash DONE"); } - DEBUG(NAME + "IPC interface incoming message OUT"); + //DEBUG(NAME + "IPC interface incoming message OUT"); } //============================================================================= diff --git a/lib/SPELL_IPC/src/SPELLipcServerInterfaceSingle.C b/lib/SPELL_IPC/src/SPELLipcServerInterfaceSingle.C index 980740c..a8e12c0 100644 --- a/lib/SPELL_IPC/src/SPELLipcServerInterfaceSingle.C +++ b/lib/SPELL_IPC/src/SPELLipcServerInterfaceSingle.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the interface for single client servers // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -49,7 +49,8 @@ SPELLipcServerInterfaceSingle::SPELLipcServerInterfaceSingle( const std::string& m_connected(false), m_trash(name), m_ipcSequence(0), - m_mailbox(name) + m_mailbox(name), + m_removeClient(false) { m_serverKey = key; m_serverPort = port; @@ -73,6 +74,7 @@ SPELLipcServerInterfaceSingle::~SPELLipcServerInterfaceSingle() { DEBUG(NAME + "Destroying server interface"); DEBUG(NAME + "Server interface destroyed"); + removeClientInternal(); } //============================================================================= @@ -116,12 +118,14 @@ void SPELLipcServerInterfaceSingle::run() DEBUG(" - Entering select loop"); DEBUG(NAME + "Accepting connections"); - while(isConnected()) { bool disconnected = false; SPELLsocket* clientSocket = m_serverSocket->acceptClient( &disconnected ); - + if (clientSocket == NULL && m_removeClient) + { + removeClientInternal(); + } if ( (!disconnected) && (clientSocket != NULL) && (isConnected()) ) { LOG_INFO(NAME + "Accepted new client connection"); @@ -172,7 +176,7 @@ void SPELLipcServerInterfaceSingle::addClient( SPELLsocket* skt ) DEBUG( NAME + "add Client TRY-IN"); SPELLmonitor m(m_clientLock); - + m_removeClient = false; m_channel = new SPELLipcChannel( std::string(m_ifcName), skt, 1, *this ); m_channel->connect(); @@ -185,22 +189,27 @@ void SPELLipcServerInterfaceSingle::addClient( SPELLsocket* skt ) //============================================================================= void SPELLipcServerInterfaceSingle::removeClient() { - DEBUG( NAME + "Remove client TRY-IN" ); - SPELLmonitor m(m_clientLock); + m_removeClient = true; +} + +void SPELLipcServerInterfaceSingle::removeClientInternal() +{ + SPELLmonitor m(m_cleanupLock); if (m_channel) { - m_channel->disconnect(); - delete m_channel; - m_channel = NULL; + m_channel->disconnect(); + delete m_channel; + m_channel = NULL; + m_removeClient = false; } - DEBUG( NAME + "Remove client OUT" ); -} +} //============================================================================= // METHOD: SPELLipcServerInterfaceSingle:disconnect //============================================================================= void SPELLipcServerInterfaceSingle::disconnect() { + DEBUG(NAME + "Server interface disconnect TRY-IN"); if (!isConnected()) return; DEBUG(NAME + "Server interface disconnect ALL IN"); @@ -220,7 +229,7 @@ void SPELLipcServerInterfaceSingle::disconnect() //DEBUG(NAME + "Shutdown thread pool"); //m_msgPool.shutdown(); DEBUG(NAME + "Removing clients"); - removeClient(); + removeClientInternal(); LOG_INFO(NAME + "Wait to end..."); join(); LOG_INFO(NAME + "Server interface disconnected"); @@ -272,11 +281,14 @@ void SPELLipcServerInterfaceSingle::sendMessage( SPELLipcMessage& msg ) DEBUG(NAME + "Send message to peer"); try { - SPELLipcOutput& writer = m_channel->getWriter(); - // Must set the appropriate key to the message beforehand. The key info is used - // to identify requests and their responses - msg.setKey(1); - writer.send(msg); + if (m_channel != NULL) + { + SPELLipcOutput& writer = m_channel->getWriter(); + // Must set the appropriate key to the message beforehand. The key info is used + // to identify requests and their responses + msg.setKey(1); + writer.send(msg); + } } catch(SPELLipcError& err) { @@ -428,15 +440,16 @@ void SPELLipcServerInterfaceSingle::incomingRequest( const SPELLipcMessage& msg } } - DEBUG(NAME + "IPC interface incoming request IN (" + msg.getSequenceStr() + ")"); + DEBUG(NAME + "IPC interface incoming request IN (" + msg.getSequenceStr() + ") for ("+msg.get("User")+")"); try { DEBUG(NAME + "Creating request processor (" + msg.getSequenceStr() + ")"); - SPELLipcIncomingRequest* ireq = new SPELLipcIncomingRequest( msg.requestId(), m_ifcName, msg, m_channel->getWriter(), *m_listener ); + SPELLipcIncomingRequest* ireq = new SPELLipcIncomingRequest( msg.requestId(), m_ifcName, msg, m_clientLock,&m_channel, *m_listener ); ireq->start(); DEBUG(NAME + "Wait request processor to start (" + msg.getSequenceStr() + ")"); - ireq->waitStarted(); + // TODO: No reason to wait here. Remove once validated. + //ireq->waitStarted(); DEBUG(NAME + "Place in trash"); m_trash.place(ireq); DEBUG(NAME + "Place in trash DONE"); @@ -456,9 +469,9 @@ void SPELLipcServerInterfaceSingle::incomingRequest( const SPELLipcMessage& msg //============================================================================= void SPELLipcServerInterfaceSingle::incomingMessage( const SPELLipcMessage& msg ) { - DEBUG(NAME + "IPC interface incoming message TRY IN"); + // DEBUG(NAME + "IPC interface incoming message TRY IN"); SPELLmonitor m(m_incomingLock); - DEBUG(NAME + "IPC interface incoming message IN " + msg.getId()); + // DEBUG(NAME + "IPC interface incoming message IN " + msg.getId()); { SPELLmonitor m(m_ipcLock); if (!isConnected()) return; @@ -468,18 +481,29 @@ void SPELLipcServerInterfaceSingle::incomingMessage( const SPELLipcMessage& msg DEBUG(NAME + "Creating message processor"); while(m_trash.size()>50) { - //LOG_WARN(NAME + "Waiting for free space in queue"); - usleep(2000); + m_trash.clearData(); + if (m_trash.size()>50) + { + LOG_WARN(NAME + "Waiting for free space in queue"); + usleep(50000); + } + } + SPELLipcInterfaceListener* listener = m_listener; + if (listener != NULL){ + SPELLipcIncomingMessage* imsg = new SPELLipcIncomingMessage( msg.requestId(), m_ifcName, msg, *listener); + imsg->start(); + //DEBUG(NAME + "Wait processor to start"); + // TODO: No reason to wait here. Remove once validated. + //imsg->waitStarted(); + // DEBUG(NAME + "Place in trash"); + m_trash.place(imsg); + } else { + LOG_WARN("!! Request discarded: no listener: " + msg.getId()); } - SPELLipcIncomingMessage* imsg = new SPELLipcIncomingMessage( msg.requestId(), m_ifcName, msg, *m_listener); - imsg->start(); - DEBUG(NAME + "Wait processor to start"); - imsg->waitStarted(); - DEBUG(NAME + "Place in trash"); - m_trash.place(imsg); - DEBUG(NAME + "Place in trash DONE"); - - DEBUG(NAME + "IPC interface incoming message OUT"); + + // DEBUG(NAME + "Place in trash DONE"); + + // DEBUG(NAME + "IPC interface incoming message OUT"); } //============================================================================= diff --git a/lib/SPELL_IPC/src/SPELLipcTrash.C b/lib/SPELL_IPC/src/SPELLipcTrash.C index 3b7ce91..8789ebf 100644 --- a/lib/SPELL_IPC/src/SPELLipcTrash.C +++ b/lib/SPELL_IPC/src/SPELLipcTrash.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the request cleaner // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -137,7 +137,7 @@ void SPELLipcTrash::run() while(isWorking()) { if (haveData()) clearData(); - usleep(350000); + usleep(50000); } if (haveData()) clearData(); m_trashFinishEvent.set(); @@ -167,7 +167,7 @@ bool SPELLipcTrash::haveData() //============================================================================= unsigned int SPELLipcTrash::size() { - SPELLmonitor m(m_dataLock); + SPELLmonitor m(m_dataLock); return m_messages.size(); } @@ -181,22 +181,26 @@ void SPELLipcTrash::clearData() for( it = m_messages.begin(); it != m_messages.end(); it++) { - DEBUG( NAME + "Waiting for " + (*it)->getSequence() ); + //DEBUG( NAME + "Waiting for " + (*it)->getSequence() ); if ((*it)->isStarted()) { - bool timedout = (*it)->wait(500); // milliseconds + bool timedout = (*it)->wait(25); // milliseconds if (!timedout) { - DEBUG( NAME + "Joining " + (*it)->getSequence() ); - (*it)->join(); - DEBUG( NAME + "************ Deleting " + (*it)->getSequence() ); - delete (*it); - it = m_messages.erase(it); - DEBUG( NAME + "Deleted"); + if (!*it) { + it = m_messages.erase(it); + } else { + //DEBUG( NAME + "Joining " + (*it)->getSequence() ); + (*it)->join(); + //DEBUG( NAME + "************ Deleting " + (*it)->getSequence() ); + delete (*it); + it = m_messages.erase(it); + //DEBUG( NAME + "Deleted"); + } } else { - DEBUG( NAME + "Did not delete " + (*it)->getSequence() ); + //DEBUG( NAME + "Did not delete " + (*it)->getSequence() ); } } } diff --git a/lib/SPELL_IPC/src/SPELLsocket.C b/lib/SPELL_IPC/src/SPELLsocket.C index 29a651e..83f0f65 100644 --- a/lib/SPELL_IPC/src/SPELLsocket.C +++ b/lib/SPELL_IPC/src/SPELLsocket.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the socket wrapper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -38,14 +38,16 @@ #include #include #include - +#include +#include +#include // DEFINES ///////////////////////////////////////////////////////////////// //============================================================================= // CONSTRUCTOR: SPELLsocket::SPELLsocket //============================================================================= -SPELLsocket::SPELLsocket( int fd ) +SPELLsocket::SPELLsocket( int fd , std::string user, std::vector groups) : user(user), groups(groups) { m_socketFd = fd; m_connected = true; @@ -90,9 +92,13 @@ int SPELLsocket::send( const void* buffer, int size ) { //DEBUGX(std::string tmp); if (!m_connected) return 0; - int numWritten = write(m_socketFd,buffer,size); + int numWritten = ::send(m_socketFd,buffer,size,MSG_NOSIGNAL); //DEBUGX(tmp.assign((char*) buffer, ((char*)buffer) + numWritten)); - //DEBUG("SPELL SOCKET WROTE (" + ISTR(numWritten) + "):" + SPELLutils::dumpString(tmp)); + + if (errno > 0) { + //perror("send"); + LOG_ERROR("[SKT] Socket " + ISTR(m_socketFd) + " encountered error: " + ISTR(errno) + " bytes written: " + ISTR(numWritten) + "/" + ISTR(size)); + } return numWritten; } @@ -139,6 +145,63 @@ void SPELLsocket::close() ::close(m_socketFd); } +std::string _exec(std::string cmd) { + char buffer[128]; + std::string result = ""; + FILE* pipe = popen(cmd.c_str(), "r"); + if (!pipe) + return ""; + try { + while (fgets(buffer, sizeof buffer, pipe) != NULL) { + result += buffer; + } + } catch (...) { + pclose(pipe); + throw; + } + pclose(pipe); + std::string::size_type i = 0; + while (i < result.length()) { + i = result.find('\n', i); + if (i == std::string::npos) { + break; + } + result.erase(i); + } + return result; + +} + +std::vector split(std::string& s, const std::string& delimiter) { + std::vector tokens; + size_t pos = 0; + std::string token; + while ((pos = s.find(delimiter)) != std::string::npos) { + token = s.substr(0, pos); + tokens.push_back(token); + s.erase(0, pos + delimiter.length()); + } + tokens.push_back(s); + + return tokens; +} +template +std::string str(T begin, T end) +{ + std::stringstream ss; + bool first = true; + ss << "["; + for (; begin != end; begin++) + { + if (!first) + ss << ", "; + ss << *begin; + first = false; + } + ss << "]"; + return ss.str(); +} + //============================================================================= // METHOD: SPELLsocket::isConnected //============================================================================= @@ -169,10 +232,11 @@ SPELLsocket* SPELLsocket::acceptClient( bool* disconnected ) timeout.tv_usec = 0; // Wait on the file descriptors - int numfds = select( m_socketFd+1, &read_fds, NULL, NULL, &timeout); + int numfds = select( FD_SETSIZE, &read_fds, NULL, NULL, &timeout); if( numfds == -1) { + perror("select"); if (m_connected) { LOG_ERROR("Error on socket select: " + ISTR(errno)); @@ -196,8 +260,32 @@ SPELLsocket* SPELLsocket::acceptClient( bool* disconnected ) } else { + + in_port_t port = ntohs(client_addr.sin_port); +#ifdef WITH_KUBERNETES + DEBUG("Incoming Client from port: " + ISTR(port)); + std::string user = _exec("cat ~spell/procs/users | grep "+ISTR(port)+" | awk -F= '{print $2}'"); + int tries = 10; + while (user == "" && tries-- > 0){ + user = _exec("cat ~spell/procs/users | grep "+ISTR(port)+" | awk -F= '{print $2}'"); + if (user == "") { + usleep(500000); + } + } + std::string groupsStr = _exec("cat ~spell/procs/groups/" + user); + tries = 10; + while (groupsStr == "" && tries-- > 0){ + groupsStr = _exec("cat ~spell/procs/groups/" + user); + if (groupsStr == "") { + usleep(500000); + } + } + groups = split(groupsStr, " "); + + DEBUG("Incoming Client for user: " + user + " groups:" + str(groups.begin(), groups.end())); +#endif *disconnected = false; - return new SPELLsocket( newfd ); + return new SPELLsocket( newfd, user, groups); } } @@ -236,9 +324,10 @@ SPELLsocket* SPELLsocket::connect( const std::string& server, const int& port ) DEBUG(" - Connecting to server at port " + ISTR(port)); if (::connect(socketFd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) { + // perror("connect"); throw SPELLipcError("Error on socket connect: errno " + ISTR(errno)); } - return new SPELLsocket(socketFd); + return new SPELLsocket(socketFd, "spell",{}); } //============================================================================= @@ -246,64 +335,78 @@ SPELLsocket* SPELLsocket::connect( const std::string& server, const int& port ) //============================================================================= SPELLsocket* SPELLsocket::listen( int* port ) { + int retries = 0; + while (retries < 3) + { + if (retries) + usleep(250); + ++retries; - struct sockaddr_in serv_addr; - bzero( (char*) &serv_addr, sizeof(serv_addr)); + struct sockaddr_in serv_addr; + bzero( (char*) &serv_addr, sizeof(serv_addr)); - int socketFd; - // Get the listener socket - if((socketFd = socket(AF_INET, SOCK_STREAM, 0)) == -1) - { - throw SPELLipcError("Error on socket creation, errno " +ISTR(errno) ); - } + int socketFd; + // Get the listener socket + if((socketFd = socket(AF_INET, SOCK_STREAM, 0)) == -1) + { + if (retries < 3) continue; + throw SPELLipcError("Error on socket creation, errno " +ISTR(errno) ); + } - // Set reuse address - int yes = 1; - if(setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)) == -1) - { - throw SPELLipcError("Error on socket configuration (reuse addr), errno " + ISTR(errno)); - } + // Set reuse address + int32_t yes = 1; + if(setsockopt(socketFd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int32_t)) == -1) + { + if (retries < 3) continue; + throw SPELLipcError("Error on socket configuration (reuse addr), errno " + ISTR(errno)); + } - // Set no linger - struct linger linger; - linger.l_onoff = 1; /*0 = off (l_linger ignored), nonzero = on */ - linger.l_linger =0; /*0 = discard data, nonzero = wait for data sent */ - if(setsockopt(socketFd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)) == -1) - { - throw SPELLipcError("Error on socket configuration (linger), errno " + ISTR(errno)); - } + // Set no linger + struct linger linger; + linger.l_onoff = 1; /*0 = off (l_linger ignored), nonzero = on */ + linger.l_linger =0; /*0 = discard data, nonzero = wait for data sent */ + if(setsockopt(socketFd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)) == -1) + { + if (retries < 3) continue; + throw SPELLipcError("Error on socket configuration (linger), errno " + ISTR(errno)); + } - // Set keep alive - if(setsockopt(socketFd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int)) == -1) - { - throw SPELLipcError("Error on socket configuration (keep alive), errno " + ISTR(errno)); - } + // Set keep alive + if(setsockopt(socketFd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(int32_t)) == -1) + { + if (retries < 3) continue; + throw SPELLipcError("Error on socket configuration (keep alive), errno " + ISTR(errno)); + } - serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); - serv_addr.sin_port = htons(*port); - memset(&(serv_addr.sin_zero), '\0', 8); + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + serv_addr.sin_port = htons(*port); + memset(&(serv_addr.sin_zero), '\0', 8); - if (bind(socketFd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) - { - throw SPELLipcError("Error on socket bind, errno " + ISTR(errno)); - } + if (bind(socketFd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) + { + if (retries < 3) continue; + throw SPELLipcError("Error on socket bind, errno " + ISTR(errno)); + } - socklen_t address_len = sizeof serv_addr; - // Get the actual port (chosen by OS if m_serverPort was zero for bind) - if (getsockname( socketFd, (struct sockaddr*) &serv_addr, &address_len )<0) - { - std::cerr << errno << std::endl; - throw SPELLipcError("Error getting socket address, errno " + ISTR(errno) ); - } - *port = ntohs(serv_addr.sin_port); - DEBUG("Listening on port " + ISTR(*port)); + socklen_t address_len = sizeof serv_addr; + // Get the actual port (chosen by OS if m_serverPort was zero for bind) + if (getsockname( socketFd, (struct sockaddr*) &serv_addr, &address_len )<0) + { + if (retries < 3) continue; + throw SPELLipcError("Error getting socket address, errno " + ISTR(errno) ); + } + *port = ntohs(serv_addr.sin_port); + DEBUG("Listening on port " + ISTR(*port)); - if (::listen(socketFd,5) == -1) - { - throw SPELLipcError("Error on listen, errno " + ISTR(errno)); + if (::listen(socketFd,5) == -1) + { + if (retries < 3) continue; + throw SPELLipcError("Error on listen, errno " + ISTR(errno)); + } + return new SPELLsocket( socketFd , "spell",{}); } - return new SPELLsocket( socketFd ); + throw SPELLipcError("Error on listen, errno " + ISTR(errno)); } //============================================================================= diff --git a/lib/SPELL_LST/Makefile.am b/lib/SPELL_LST/Makefile.am index abab52e..09beeaf 100644 --- a/lib/SPELL_LST/Makefile.am +++ b/lib/SPELL_LST/Makefile.am @@ -1,34 +1,41 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_LST.la -libSPELL_LST_la_LDFLAGS = -version-info 3:0:0 -libSPELL_LST_la_SOURCES = \ - src/SPELLlistenerContext.C \ - src/SPELLlistenerGui.C \ - src/SPELLlistener.C - + +lib_LTLIBRARIES = libSPELL_LST_v2.la libSPELL_LST_v3.la + +libSPELL_LST_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_LST_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_LST_v2_la_SOURCES = src/SPELLlistenerContext.C \ + src/SPELLlistenerGui.C \ + src/SPELLlistener.C + +libSPELL_LST_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_LST_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_LST_v3_la_SOURCES = src/SPELLlistenerContext.C \ + src/SPELLlistenerGui.C \ + src/SPELLlistener.C + \ No newline at end of file diff --git a/lib/SPELL_LST/src/SPELLlistener.C b/lib/SPELL_LST/src/SPELLlistener.C index dc78b7c..697784a 100644 --- a/lib/SPELL_LST/src/SPELLlistener.C +++ b/lib/SPELL_LST/src/SPELLlistener.C @@ -5,7 +5,7 @@ // DESCRIPTION: // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -70,7 +70,16 @@ void SPELLlistener::start( const std::string& configFile ) m_contextListener = new SPELLlistenerContext( configFile ); m_guiListener->startup(m_contextListener, NULL); - m_contextListener->startup(m_guiListener, NULL); + m_contextListener->startup(m_guiListener, NULL); + + std::vector ctxList = + SPELLconfiguration::instance().getAvailableContexts(); + + for (std::vector::iterator it = ctxList.begin(); + it != ctxList.end(); it++) + { + m_contextListener->respawnContext((*it)); + } } void SPELLlistener::stop() diff --git a/lib/SPELL_LST/src/SPELLlistenerContext.C b/lib/SPELL_LST/src/SPELLlistenerContext.C index b799db0..564bd7e 100644 --- a/lib/SPELL_LST/src/SPELLlistenerContext.C +++ b/lib/SPELL_LST/src/SPELLlistenerContext.C @@ -5,7 +5,7 @@ // DESCRIPTION: // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -56,7 +56,7 @@ SPELLlistenerContext::SPELLlistenerContext(const std::string& configFile) m_gui = NULL; m_peer = NULL; - + m_ipc = NULL; m_configFile = configFile; m_port = STRI(SPELLconfiguration::instance().getListenerParameter("ContextListenerPort")); m_contextStartupCmd = SPELLconfiguration::instance().getListenerParameter("ContextScript"); @@ -220,6 +220,7 @@ SPELLipcMessage SPELLlistenerContext::startContext( const SPELLipcMessage& msg ) //============================================================================= SPELLipcMessage SPELLlistenerContext::destroyContext( const SPELLipcMessage& msg ) { + LOG_INFO("Destroying context"); std::string name; name = msg.get(MessageField::FIELD_CTX_NAME); @@ -234,11 +235,12 @@ SPELLipcMessage SPELLlistenerContext::destroyContext( const SPELLipcMessage& msg SPELLprocessManager::instance().killProcess("CTX_" + name); SPELLprocessManager::instance().waitProcess("CTX_" + name); SPELLprocessManager::instance().clearProcess("CTX_" + name); + usleep(10000000); + respawnContext(name); } catch(SPELLcoreException& ex) {}; LOG_INFO("Destroyed context"); - return SPELLipcHelper::createResponse(ContextMessages::RSP_DESTROY_CTX, msg); } @@ -312,7 +314,7 @@ SPELLipcMessage SPELLlistenerContext::stopContext( const SPELLipcMessage& msg ) notify.setSender("LST"); notify.setReceiver("GUI"); m_gui->displace(¬ify); - + //respawnContext(ctxInfo->m_name); } catch(SPELLcoreException& ex) {}; } @@ -412,14 +414,26 @@ void SPELLlistenerContext::fillContextInfo(const std::string ctxName, SPELLipcMe std::string driverName; SPELLcontextConfig* ctxConfig; SPELLdriverConfig* drvConfig; - ContextInfo* ctxInfo; + std::map::iterator ctxInfo; - ctxInfo = &m_openContexts[ctxName]; - ctxConfig = ctxInfo->m_ctxConfig; - drvConfig = ctxInfo->m_drvConfig; + ctxInfo = m_openContexts.find(ctxName); + if (ctxInfo == m_openContexts.end()){ + msg.set(MessageField::FIELD_CTX_NAME, "Unknown"); + msg.set(MessageField::FIELD_CTX_PORT, ISTR(0) ); + msg.set(MessageField::FIELD_CTX_DRV, "Unknown"); + msg.set(MessageField::FIELD_CTX_SC, "Unknown"); + msg.set(MessageField::FIELD_CTX_FAM, "Unknown"); + msg.set(MessageField::FIELD_CTX_GCS, "Unknown"); + msg.set(MessageField::FIELD_CTX_DESC, "Unknown"); + msg.set(MessageField::FIELD_CTX_STATUS, "ERROR"); + msg.set(MessageField::FIELD_CTX_MAXPROC, "0" ); + return; + } + ctxConfig = ctxInfo->second.m_ctxConfig; + drvConfig = ctxInfo->second.m_drvConfig; msg.set(MessageField::FIELD_CTX_NAME, ctxName); - msg.set( MessageField::FIELD_CTX_PORT, ISTR(ctxInfo->m_port) ); + msg.set( MessageField::FIELD_CTX_PORT, ISTR(ctxInfo->second.m_port) ); if (ctxConfig != NULL) { @@ -428,7 +442,7 @@ void SPELLlistenerContext::fillContextInfo(const std::string ctxName, SPELLipcMe msg.set(MessageField::FIELD_CTX_FAM, ctxConfig->getFamily()); msg.set(MessageField::FIELD_CTX_GCS, ctxConfig->getGCS()); msg.set(MessageField::FIELD_CTX_DESC, ctxConfig->getDescription()); - msg.set(MessageField::FIELD_CTX_STATUS, ctxInfo->m_status); + msg.set(MessageField::FIELD_CTX_STATUS, ctxInfo->second.m_status); } else { @@ -507,7 +521,7 @@ SPELLipcMessage SPELLlistenerContext::displace( const SPELLipcMessage& msg ) else if (msg.getId() == ContextMessages::REQ_DESTROY_CTX) res = this->destroyContext(msg); else - LOG_ERROR("Unprocessed message: " + msg.getId()) + LOG_ERROR("Unprocessed message: " + msg.getId()); DEBUG("Response IPC object: " + res.dataStr()); @@ -517,7 +531,7 @@ SPELLipcMessage SPELLlistenerContext::displace( const SPELLipcMessage& msg ) //============================================================================= // METHOD: SPELLlistenerContext::processStarted //============================================================================= -void SPELLlistenerContext::processStarted( const std::string& identifier ) +void SPELLlistenerContext::processStarted( const std::string identifier ) { LOG_INFO("Context process started: " + identifier); } @@ -525,7 +539,7 @@ void SPELLlistenerContext::processStarted( const std::string& identifier ) //============================================================================= // METHOD: SPELLlistenerContext::processFinished //============================================================================= -void SPELLlistenerContext::processFinished( const std::string& identifier, const int& retValue ) +void SPELLlistenerContext::processFinished( const std::string identifier, const int& retValue ) { LOG_INFO("Context process finished: " + identifier); } @@ -533,7 +547,7 @@ void SPELLlistenerContext::processFinished( const std::string& identifier, const //============================================================================= // METHOD: SPELLlistenerContext::processKilled //============================================================================= -void SPELLlistenerContext::processKilled( const std::string& identifier ) +void SPELLlistenerContext::processKilled( const std::string identifier ) { ContextInfo* ctxInfo; std::string ctxName = identifier.substr(4); @@ -551,18 +565,25 @@ void SPELLlistenerContext::processKilled( const std::string& identifier ) notify.setReceiver("GUI"); m_gui->displace(¬ify); DEBUG("Process killed processing done"); + usleep(10000000); + respawnContext(ctxName); } //============================================================================= // METHOD: SPELLlistenerContext::processFailed //============================================================================= -void SPELLlistenerContext::processFailed( const std::string& identifier ) +void SPELLlistenerContext::processFailed( const std::string identifier ) { ContextInfo* ctxInfo; LOG_ERROR("Context " + identifier + " failed"); ctxInfo = &m_openContexts[identifier.substr(4)]; ctxInfo->m_status = MessageValue::DATA_CTX_ERROR; - m_ipc->disconnect(); + try { + m_ipc->disconnectClient(ctxInfo->m_key); + } catch(SPELLcoreException& ex) + { + + } // Notify to other clients SPELLipcMessage notify( ListenerMessages::MSG_CONTEXT_OP ); @@ -572,8 +593,11 @@ void SPELLlistenerContext::processFailed( const std::string& identifier ) notify.setSender("LST"); notify.setReceiver("GUI"); m_gui->displace(¬ify); + usleep(10000000); + respawnContext(ctxInfo->m_name); } + //============================================================================= // METHOD: SPELLlistenerContext::notifyClients //============================================================================= diff --git a/lib/SPELL_LST/src/SPELLlistenerGui.C b/lib/SPELL_LST/src/SPELLlistenerGui.C index 44f5774..56b2ed2 100644 --- a/lib/SPELL_LST/src/SPELLlistenerGui.C +++ b/lib/SPELL_LST/src/SPELLlistenerGui.C @@ -5,7 +5,7 @@ // DESCRIPTION: // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -46,6 +46,9 @@ SPELLlistenerGui::SPELLlistenerGui(const std::string& configFile) m_configFile = configFile; m_port = STRI(SPELLconfiguration::instance().getListenerParameter("ListenerPort")); m_peer = NULL; + m_ctx = NULL; + m_peer = NULL; + m_ipc = NULL; } //============================================================================= diff --git a/lib/SPELL_PRC/Makefile.am b/lib/SPELL_PRC/Makefile.am index a29aed2..3836e60 100644 --- a/lib/SPELL_PRC/Makefile.am +++ b/lib/SPELL_PRC/Makefile.am @@ -1,33 +1,38 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_PRC.la -libSPELL_PRC_la_LDFLAGS = -version-info 3:0:0 -libSPELL_PRC_la_SOURCES = \ - src/SPELLprocess.C \ - src/SPELLprocessManager.C - + +lib_LTLIBRARIES = libSPELL_PRC_v2.la libSPELL_PRC_v3.la + +libSPELL_PRC_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_PRC_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_PRC_v2_la_SOURCES = src/SPELLprocess.C \ + src/SPELLprocessManager.C + +libSPELL_PRC_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_PRC_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_PRC_v3_la_SOURCES = src/SPELLprocess.C \ + src/SPELLprocessManager.C \ No newline at end of file diff --git a/lib/SPELL_PRC/src/SPELLprocess.C b/lib/SPELL_PRC/src/SPELLprocess.C index 042ade7..674f4c1 100644 --- a/lib/SPELL_PRC/src/SPELLprocess.C +++ b/lib/SPELL_PRC/src/SPELLprocess.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of process model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -103,6 +103,7 @@ SPELLprocess::SPELLprocess( std::string identifier, int pid, SPELLprocessManager SPELLprocess::~SPELLprocess() { DEBUG("[PRC] PROCESS " + m_identifier + " DESTROYED"); + this->join(); } //============================================================================= @@ -117,6 +118,7 @@ void SPELLprocess::run() m_statusCode = PSTATUS_INITIAL; try { + LOG_INFO("Starting process with command '" + m_command + "'"); m_processPID = popen( m_command ); } catch(SPELLcoreException& ex) @@ -223,7 +225,7 @@ void SPELLprocess::run() m_statusCode = PSTATUS_RUNNING; m_pManager->fireProcessStarted( m_identifier ); - bool repeat = false; + bool repeat = true; do { if ( !SPELLutils::pathExists( "/proc/" + ISTR(m_processPID) + "/stat" ) ) @@ -243,7 +245,7 @@ void SPELLprocess::run() } else { - usleep(1000); + usleep(100000); } } while(repeat); @@ -303,7 +305,7 @@ pid_t SPELLprocess::popen( std::string command ) } else if (pid == 0) { - if (execl("/bin/sh", "sh", "-c", command.c_str(), NULL) != 0) + if (execl("/bin/bash", "bash", "-c", command.c_str(), NULL) != 0) { std::cerr << "####################################################################" << std::endl; std::cerr << "####################################################################" << std::endl; diff --git a/lib/SPELL_PRC/src/SPELLprocessManager.C b/lib/SPELL_PRC/src/SPELLprocessManager.C index 8a5e911..c68daa9 100644 --- a/lib/SPELL_PRC/src/SPELLprocessManager.C +++ b/lib/SPELL_PRC/src/SPELLprocessManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of process manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -34,7 +34,7 @@ #include "SPELL_UTIL/SPELLbase.H" #include "SPELL_UTIL/SPELLerror.H" #include "SPELL_UTIL/SPELLlog.H" - +#include "SPELL_UTIL/SPELLutils.H" // GLOBALS ///////////////////////////////////////////////////////////////// @@ -56,6 +56,7 @@ SPELLprocessManager::SPELLprocessManager() //============================================================================= SPELLprocessManager::~SPELLprocessManager() { + m_processes.clear(); m_listeners.clear(); } @@ -81,6 +82,9 @@ void SPELLprocessManager::startProcess( const std::string& identifier, const std DEBUG("[PRCMGR] Starting process " + identifier); SPELLprocess* process = new SPELLprocess(identifier,command,this); process->start(); + if (m_processes.count(identifier) > 0) + LOG_ERROR("[PRCMGR] ProcessId " + identifier + " already exists!!!"); + m_processes.insert( std::make_pair( identifier, process )); DEBUG("[PRCMGR] Starting process " + identifier + " done"); } @@ -143,15 +147,32 @@ void SPELLprocessManager::killProcess( const std::string& identifier ) DEBUG("[PRCMGR] Kill process " + identifier + " done"); } +bool SPELLprocessManager::checkProcessExist( const std::string& identifier ) +{ + SPELLmonitor m(m_lock); + DEBUG("[PRCMGR] Checking process " + identifier); + SPELLprocessMap::iterator it = m_processes.find(identifier); + if (it == m_processes.end() ) + { + return false; + } + return true; + +} + //============================================================================= // METHOD : SPELLprocessManager::waitProcess() //============================================================================= int SPELLprocessManager::waitProcess( const std::string& identifier ) { - SPELLprocessMap::iterator it = m_processes.find(identifier); - if (it == m_processes.end() ) + SPELLprocessMap::iterator it; { - THROW_EXCEPTION("Cannot wait process " + identifier, "Not found", SPELL_ERROR_PROCESS); + SPELLmonitor m(m_lock); + it = m_processes.find(identifier); + if (it == m_processes.end() ) + { + THROW_EXCEPTION("Cannot wait process " + identifier, "Not found", SPELL_ERROR_PROCESS); + } } return it->second->wait(); } @@ -161,6 +182,7 @@ int SPELLprocessManager::waitProcess( const std::string& identifier ) //============================================================================= SPELLprocessStatus SPELLprocessManager::getProcessStatus( const std::string& identifier ) { + SPELLmonitor m(m_lock); SPELLprocessMap::iterator it = m_processes.find(identifier); if (it == m_processes.end() ) { @@ -174,11 +196,14 @@ SPELLprocessStatus SPELLprocessManager::getProcessStatus( const std::string& ide //============================================================================= void SPELLprocessManager::clearProcess( const std::string& identifier ) { + SPELLmonitor m(m_lock); DEBUG("[PRCMGR] Clear process " + identifier); SPELLprocessMap::iterator pit = m_processes.find(identifier); if (pit != m_processes.end()) { + m_lock.unlock(); pit->second->join(); + m_lock.lock(); delete pit->second; m_processes.erase(pit); DEBUG("[PRCMGR] Clear process " + identifier + " done"); @@ -230,14 +255,14 @@ void SPELLprocessManager::removeListener( const std::string& identifier, SPELLpr //============================================================================= // METHOD : SPELLprocessManager::fireProcessStarted() //============================================================================= -void SPELLprocessManager::fireProcessStarted( const std::string& identifier ) +void SPELLprocessManager::fireProcessStarted( const std::string identifier ) { - SPELLmonitor m(m_lock); DEBUG("[PRCMGR] Firing process started: " + identifier); - SPELLprocessListenersMap::iterator mit = m_listeners.find(identifier); + SPELLprocessListenersMap listeners = m_listeners; + SPELLprocessListenersMap::iterator mit = listeners.find(identifier); if (mit != m_listeners.end()) { - SPELLprocessListeners& list = mit->second; + SPELLprocessListeners list = mit->second; SPELLprocessListeners::iterator it; for( it = list.begin(); it != list.end(); it++) { @@ -253,14 +278,14 @@ void SPELLprocessManager::fireProcessStarted( const std::string& identifier ) //============================================================================= // METHOD : SPELLprocessManager::fireProcessFinished() //============================================================================= -void SPELLprocessManager::fireProcessFinished( const std::string& identifier, int retValue ) +void SPELLprocessManager::fireProcessFinished( std::string identifier, int retValue ) { - SPELLmonitor m(m_lock); DEBUG("[PRCMGR] Firing process finished: " + identifier); - SPELLprocessListenersMap::iterator mit = m_listeners.find(identifier); + SPELLprocessListenersMap listeners = m_listeners; + SPELLprocessListenersMap::iterator mit = listeners.find(identifier); if (mit != m_listeners.end()) { - SPELLprocessListeners& list = mit->second; + SPELLprocessListeners list = mit->second; SPELLprocessListeners::iterator it; for( it = list.begin(); it != list.end(); it++) { @@ -276,14 +301,14 @@ void SPELLprocessManager::fireProcessFinished( const std::string& identifier, in //============================================================================= // METHOD : SPELLprocessManager::fireProcessKilled() //============================================================================= -void SPELLprocessManager::fireProcessKilled( const std::string& identifier ) +void SPELLprocessManager::fireProcessKilled( std::string identifier ) { - SPELLmonitor m(m_lock); DEBUG("[PRCMGR] Firing process killed: " + identifier); - SPELLprocessListenersMap::iterator mit = m_listeners.find(identifier); + SPELLprocessListenersMap listeners = m_listeners; + SPELLprocessListenersMap::iterator mit = listeners.find(identifier); if (mit != m_listeners.end()) { - SPELLprocessListeners& list = mit->second; + SPELLprocessListeners list = mit->second; SPELLprocessListeners::iterator it; for( it = list.begin(); it != list.end(); it++) { @@ -294,20 +319,19 @@ void SPELLprocessManager::fireProcessKilled( const std::string& identifier ) } } } - DEBUG("[PRCMGR] Firing process killed done: " + identifier); } //============================================================================= // METHOD : SPELLprocessManager::fireProcessFailed() //============================================================================= -void SPELLprocessManager::fireProcessFailed( const std::string& identifier ) +void SPELLprocessManager::fireProcessFailed( std::string identifier ) { - SPELLmonitor m(m_lock); DEBUG("[PRCMGR] Firing process failed: " + identifier); - SPELLprocessListenersMap::iterator mit = m_listeners.find(identifier); + SPELLprocessListenersMap listeners = m_listeners; + SPELLprocessListenersMap::iterator mit = listeners.find(identifier); if (mit != m_listeners.end()) { - SPELLprocessListeners& list = mit->second; + SPELLprocessListeners list = mit->second; SPELLprocessListeners::iterator it; for( it = list.begin(); it != list.end(); it++) { diff --git a/lib/SPELL_PRD/Makefile.am b/lib/SPELL_PRD/Makefile.am index cb47a17..66ee734 100644 --- a/lib/SPELL_PRD/Makefile.am +++ b/lib/SPELL_PRD/Makefile.am @@ -1,34 +1,47 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_PRD.la -libSPELL_PRD_la_LDFLAGS = -version-info 2:1:0 -libSPELL_PRD_la_SOURCES = \ - src/SPELLprocedureManager.C \ - src/SPELLprocedure.C \ - src/SPELLlibrary.C - + +lib_LTLIBRARIES = libSPELL_PRD_v2.la libSPELL_PRD_v3.la + +libSPELL_PRD_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_PRD_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_PRD_v2_la_SOURCES = src/SPELLprocedureManager.C \ + src/SPELLprocedure.C \ + src/SPELLlibrary.C \ + src/SPELLpyService.C \ + src/SPELLserviceRegisterPython.C \ + src/SPELLserviceWatchdogPython.C + +libSPELL_PRD_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_PRD_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_PRD_v3_la_SOURCES = src/SPELLprocedureManager.C \ + src/SPELLprocedure.C \ + src/SPELLlibrary.C \ + src/SPELLpyService.C \ + src/SPELLserviceRegisterPython.C \ + src/SPELLserviceWatchdogPython.C + \ No newline at end of file diff --git a/lib/SPELL_PRD/src/SPELLlibrary.C b/lib/SPELL_PRD/src/SPELLlibrary.C index 3283c71..dd7cdaf 100644 --- a/lib/SPELL_PRD/src/SPELLlibrary.C +++ b/lib/SPELL_PRD/src/SPELLlibrary.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of library model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -48,9 +48,6 @@ SPELLlibrary::SPELLlibrary( const std::string& libPath, const std::string& filen // The remove the lib base path, not the subfolders m_libId = m_libId.substr( libPath.size()+1, m_libId.size()-libPath.size()-1 ); - - // Parse the file to get properties and source code - parseFile(filename); } //============================================================================= @@ -59,28 +56,3 @@ SPELLlibrary::SPELLlibrary( const std::string& libPath, const std::string& filen SPELLlibrary::~SPELLlibrary() { } - -//============================================================================= -// METHOD : SPELLlibrary::parseFile -//============================================================================= -void SPELLlibrary::parseFile( const std::string& path ) -{ - // Will be composed during parsing - m_source.clear(); - - std::ifstream file; - file.open( path.c_str() ); - if (!file.is_open()) - { - THROW_EXCEPTION("Cannot parse file " + path, "Unable to open", SPELL_ERROR_FILESYSTEM); - } - // Stores the last key found - std::string lastKey; - while(!file.eof()) - { - std::string line = ""; - std::getline(file,line); - m_source.addSourceCodeLine(line); - } - file.close(); -} diff --git a/lib/SPELL_PRD/src/SPELLprocedure.C b/lib/SPELL_PRD/src/SPELLprocedure.C index 376e73d..81f87f1 100644 --- a/lib/SPELL_PRD/src/SPELLprocedure.C +++ b/lib/SPELL_PRD/src/SPELLprocedure.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of procedure model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -36,15 +36,16 @@ #include #include #include -#include #define KEY_NAME "NAME" +#define KEY_PYTHON_VERSION "PYTHON VERSION" #define KEY_FILE "FILE" #define KEY_HISTORY "REVISION HISTORY" #define KEY_SC1 "SPACECRAFT" #define KEY_SC2 "SPACECRAFTS" const std::string CHECK_SPACECRAFT = "CheckSpacecraft"; +const std::string OVERRIDE_SC = "OverrideContextSpacecraftName"; //============================================================================= // CONSTRUCTOR : SPELLprocedure::SPELLprocedure() @@ -69,9 +70,6 @@ SPELLprocedure::SPELLprocedure( const std::string& procPath, const std::string& m_file = filename; m_header = ""; - // Obtain the file checksum - m_md5 = obtainChecksum(filename); - // Parse the file to get properties and source code parseFile(filename); } @@ -89,6 +87,55 @@ SPELLprocedure::~SPELLprocedure() //============================================================================= std::string SPELLprocedure::getHeader() { + if (!m_header.empty()) + return m_header; + + std::ifstream file; + file.open( m_file.c_str() ); + if (!file.is_open()) + { + THROW_EXCEPTION("Cannot parse file " + m_file, "Unable to open", SPELL_ERROR_FILESYSTEM); + } + // Will be true while in comment lines + bool isComment = false; + // Will be true while inside the header + bool inHeader = false; + // Will be true when finished parsing the header + bool headerDone = false; + // Will be true if parsing the history of changes + bool inHistory = false; + m_header = ""; + // Stores the last key found + std::string lastKey; + while(!file.eof()) + { + std::string line = ""; + std::getline(file,line); + + if ((line.size()>0)&&(!headerDone)) + { + isComment = (line[0] == '#'); + if (isComment && (!inHeader)) + { + // If it is a header delimiter line we are entering the header + if (isLimitLine(line)) + { + inHeader = true; + } + } + else if (isComment && inHeader) + { + m_header += line + "\n"; + // If we are in header and another limit line comes, we have finished + if (isLimitLine(line)) + { + headerDone = true; + break; + } + } + } + } + file.close(); return m_header; } @@ -129,7 +176,6 @@ void SPELLprocedure::parseFile( const std::string& path ) { std::string line = ""; std::getline(file,line); - m_source.addSourceCodeLine(line); if ((line.size()>0)&&(!headerDone)) { isComment = (line[0] == '#'); @@ -143,7 +189,6 @@ void SPELLprocedure::parseFile( const std::string& path ) } else if (isComment && inHeader) { - m_header += line + "\n"; // If we are in header and another limit line comes, we have finished if (isLimitLine(line)) { @@ -224,43 +269,6 @@ void SPELLprocedure::parseFile( const std::string& path ) m_properties[KEY_FILE] = m_file; } -//============================================================================= -// METHOD : SPELLprocedure::obtainChecksum() -//============================================================================= -std::string SPELLprocedure::obtainChecksum( const std::string& filename ) -{ - int fd = open(filename.c_str(), O_RDONLY); - if (fd<0) - { - THROW_EXCEPTION("Cannot calculate MD5 for file " + filename, "Unable to open", SPELL_ERROR_FILESYSTEM); - } - struct stat statbuf; - if(fstat(fd, &statbuf) < 0) - { - THROW_EXCEPTION("Cannot calculate MD5 for file " + filename, "Unable to stat file", SPELL_ERROR_FILESYSTEM); - } - int fileSize = statbuf.st_size; - char* fileBuffer; - unsigned char result[MD5_DIGEST_LENGTH]; - - fileBuffer = (char*) mmap(0, fileSize, PROT_READ, MAP_SHARED, fd, 0); - MD5((unsigned char*) fileBuffer, fileSize, result); - - // Print the MD5 sum as hex-digits. - int i; - char* hex = new char[2]; - std::string checksum = ""; - for(i=0; i SPELLprocedureSourceCode::getSourceCodeLines() { + if (!m_sourceLines.empty()) + return m_sourceLines; + + std::ifstream file; + file.open( m_filename.c_str() ); + if (!file.is_open()) + { + THROW_EXCEPTION("Cannot parse file " + m_filename, "Unable to open", SPELL_ERROR_FILESYSTEM); + } + // Stores the last key found + std::string lastKey; + std::string line = ""; + while(!file.eof()) + { + std::getline(file,line); + m_sourceLines.push_back(line); + } + + return m_sourceLines; +}; diff --git a/lib/SPELL_PRD/src/SPELLprocedureManager.C b/lib/SPELL_PRD/src/SPELLprocedureManager.C index 326deb9..137ac73 100644 --- a/lib/SPELL_PRD/src/SPELLprocedureManager.C +++ b/lib/SPELL_PRD/src/SPELLprocedureManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of procedure manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -33,7 +33,7 @@ #include "SPELL_UTIL/SPELLlog.H" #include "SPELL_UTIL/SPELLpythonHelper.H" #include "SPELL_SYN/SPELLmonitor.H" - +#include "SPELL_WRP/SPELLpyHandle.H" static SPELLprocedureManager* s_instance = 0; @@ -94,11 +94,16 @@ void SPELLprocedureManager::resolvePaths() SPELLcontextConfig& ctx = SPELLconfiguration::instance().getContext(m_ctxName); m_procPath = ctx.getProcPath(); m_libPath = ctx.getLibPath(); + m_servicePath = ctx.getServicePath(); // Resolve the path m_procPath = SPELLutils::resolvePath(m_procPath); m_libPath = SPELLutils::resolvePath(m_libPath); + m_servicePath = SPELLutils::resolvePath(m_servicePath); + SPELLutils::trim(m_procPath, "\r\n"); SPELLutils::trim(m_libPath, "\r\n"); + SPELLutils::trim(m_servicePath, "\r\n"); + if (!SPELLutils::isDirectory(m_procPath)) { LOG_ERROR("Procedure path not found or not a directory: '" + m_procPath + "'"); @@ -125,6 +130,24 @@ void SPELLprocedureManager::resolvePaths() { LOG_INFO("[PRCM] No user libraries defined"); } + + if (m_servicePath != "" && m_servicePath != "None") + { + if (!SPELLutils::isDirectory(m_servicePath)) + { + LOG_ERROR("Spell Service path not found or not a directory: '" + m_servicePath + "'"); + m_servicePath = ""; + } + else + { + LOG_INFO("[PRCM] Spell Service path: " + m_servicePath); + } + } + else + { + LOG_INFO("[PRCM] No Spell Services defined"); + } + } //============================================================================= @@ -158,6 +181,17 @@ void SPELLprocedureManager::refresh() LOG_INFO("Loading library files"); findLibraries( m_libPath ); } + if (m_servicePath != "" && m_servicePath != "None") + { + SPELLpythonHelper::instance().addToPath(m_libPath); + LOG_INFO("Loading Spell Services"); + findServices( m_servicePath, sc ); + + ServiceModelMap::iterator serviceIt = m_serviceModels.begin(); + for(;serviceIt != m_serviceModels.end();serviceIt++){ + std::cout << "Service " << serviceIt->second->getName() << " was found" << std::endl; + } + } } //============================================================================= @@ -223,7 +257,20 @@ const SPELLprocedureManager::ProcList SPELLprocedureManager::getProcList() } return list; } +//============================================================================= +// METHOD : SPELLprocedureManager::getServiceList +//============================================================================= +const SPELLprocedureManager::ProcList SPELLprocedureManager::getServiceList() +{ + ProcList list; + ServiceModelMap::iterator it; + for( it = m_serviceModels.begin(); it != m_serviceModels.end(); it++) + { + list.push_back( it->first ); + } + return list; +} //============================================================================= // METHOD : SPELLprocedureManager::getProcName //============================================================================= @@ -235,6 +282,10 @@ const std::string SPELLprocedureManager::getProcName( const std::string& procId ProcMap::iterator it = m_idToName.find(theProcId); if (it == m_idToName.end()) { + if (m_serviceModels.find(theProcId) != m_serviceModels.end()){ + return theProcId; + } + DEBUG("[PRCM] Did not find any name for: " + theProcId ); THROW_EXCEPTION("Cannot get name for procedure '" + theProcId + "'", "No such identifier", SPELL_ERROR_PROCEDURES); } @@ -242,6 +293,82 @@ const std::string SPELLprocedureManager::getProcName( const std::string& procId return it->second; } +//============================================================================= +// METHOD : SPELLprocedureManager::findProcedures +//============================================================================= +void SPELLprocedureManager::registerServiceHandler( PyObject* serviceImpl ) +{ + SPELLpyService* serviceModel = new SPELLpyService(serviceImpl); + + m_serviceModels[serviceModel->getName()] = serviceModel; +} + + +//============================================================================= +// METHOD : SPELLprocedureManager::findProcedures +//============================================================================= +void SPELLprocedureManager::findServices( const std::string& basePath, const std::string& sc ) +{ + if (!SPELLutils::isDirectory(basePath)) + { + return; + } + + std::list files = SPELLutils::getFilesInDir( basePath ); + std::list::iterator it; + std::list::iterator end = files.end(); + + std::string procPath = m_servicePath; + + // Add the current path to python path + if (SPELLpythonHelper::instance().isInitialized()) + { + SPELLpythonHelper::instance().addToPath( basePath ); + } + + PyObject* result = SPELLpythonHelper::instance().callFunction("spell.utils.spell_service","SupportServices",NULL); + Py_IncRef(result); + SPELLpythonHelper::instance().checkError(); + if (result == Py_False) + return; + + for( it = files.begin(); it != end; it++ ) + { + if ((*it) == "__init__.py") continue; + + std::size_t idx = (*it).find_last_of(".py"); + std::size_t idxb = (*it).find(".py~"); // Filter out backup files + std::size_t idxc = (*it).find(".pyc"); // Filter out compiled py files + if ((idx != std::string::npos && idx == ((*it).length() - 1)) + && (idxb == std::string::npos) && (idxc == std::string::npos)) + { + std::string serviceFile = basePath + PATH_SEPARATOR + (*it); + std::string serviceModuleName = it->substr(0, idx-2); + try + { + std::cout << "Loading Service " << serviceModuleName << std::endl; + + SPELLpyHandle serviceModule = PyImport_ImportModule(serviceModuleName.c_str()); + SPELLpythonHelper::instance().checkError(); + } + catch (SPELLcoreException& e) + { + + } + + } + } + + std::list subdirs = SPELLutils::getSubdirs( basePath ); + // Procedure folder order will be ['Bus', 'Ground', 'Mission', 'Payload', 'Prod_SES', 'Regression', 'SAE', 'Validation'] + subdirs.sort(SPELLutils::compareNocase); + end = subdirs.end(); + for( it = subdirs.begin(); it != end; it++ ) + { + findServices( basePath + PATH_SEPARATOR + (*it), sc ); + } +} + //============================================================================= // METHOD : SPELLprocedureManager::findProcedures //============================================================================= @@ -265,7 +392,8 @@ void SPELLprocedureManager::findProcedures( const std::string& basePath, const s std::size_t idx = (*it).find_last_of(".py"); std::size_t idxb = (*it).find(".py~"); // Filter out backup files - if ((idx != std::string::npos && idx == ((*it).length() - 1) ) && ( idxb == std::string::npos )) + std::size_t idxc = (*it).find(".pyc"); // Filter out compiled py files + if ((idx != std::string::npos && idx == ((*it).length() - 1) ) && ( idxb == std::string::npos ) && ( idxc == std::string::npos )) { std::string procFile = basePath + PATH_SEPARATOR + (*it); SPELLprocedure* proc = NULL; @@ -301,6 +429,8 @@ void SPELLprocedureManager::findProcedures( const std::string& basePath, const s } std::list subdirs = SPELLutils::getSubdirs( basePath ); + // Procedure folder order will be ['Bus', 'Ground', 'Mission', 'Payload', 'Prod_SES', 'Regression', 'SAE', 'Validation'] + subdirs.sort(SPELLutils::compareNocase); end = subdirs.end(); for( it = subdirs.begin(); it != end; it++ ) { @@ -366,7 +496,14 @@ SPELLprocedureSourceCode SPELLprocedureManager::getSourceCode( const std::string LibModels::iterator libit = m_libModels.find(procId); if (libit == m_libModels.end()) { - THROW_EXCEPTION("Cannot get source code for " + procId, "Procedure or library not found", SPELL_ERROR_PROCEDURES); + ServiceModelMap::iterator mit = m_serviceModels.find(theProcId); + if (mit == m_serviceModels.end()) + { + THROW_EXCEPTION("Cannot get source code for " + procId, "Procedure or library not found", SPELL_ERROR_PROCEDURES); + + } + SPELLpyService* service = mit->second; + return service->getSourceCode(); } SPELLlibrary* lib = libit->second; lib->refresh(); @@ -391,7 +528,16 @@ SPELLprocedure::PropertyKeys SPELLprocedureManager::getPropertyKeys( const std:: it = m_procModels.find(theProcId); if (it == m_procModels.end()) { - THROW_EXCEPTION("Cannot get properties for " + theProcId, "Procedure not found", SPELL_ERROR_PROCEDURES); + ServiceModelMap::iterator mit; + mit = m_serviceModels.find(theProcId); + if (mit == m_serviceModels.end()) + { + THROW_EXCEPTION("Cannot get properties for " + theProcId, "Procedure not found", SPELL_ERROR_PROCEDURES); + } + // Services don't have properties for now + SPELLprocedure::PropertyKeys empty = SPELLprocedure::PropertyKeys(); + empty.clear(); + return empty; } SPELLprocedure* proc = (*it).second; proc->refresh(); @@ -418,7 +564,7 @@ const std::string SPELLprocedureManager::getProperty( const std::string& procId, //============================================================================= // METHOD : SPELLprocedureManager::noInstanceId //============================================================================= -const std::string SPELLprocedureManager::noInstanceId( const std::string& procId ) +const std::string noInstanceId( const std::string& procId ) { std::string noIId = procId; std::size_t pos = procId.find_first_of("#"); @@ -429,6 +575,22 @@ const std::string SPELLprocedureManager::noInstanceId( const std::string& procId return noIId; } +//============================================================================= +// METHOD : SPELLprocedureManager::getProcedure +//============================================================================= +SPELLpyService& SPELLprocedureManager::getService( const std::string& serviceId ) +{ + SPELLmonitor lock(m_lock); + std::string noIId = noInstanceId(serviceId); + ServiceModelMap::iterator it; + it = m_serviceModels.find(noIId); + if (it != m_serviceModels.end()) + { + return *it->second; + } + THROW_EXCEPTION("Cannot obtain service model", "No such id: " + serviceId, SPELL_ERROR_PROCEDURES); +} + //============================================================================= // METHOD : SPELLprocedureManager::getProcedure //============================================================================= diff --git a/lib/SPELL_PRD/src/SPELLpyService.C b/lib/SPELL_PRD/src/SPELLpyService.C new file mode 100644 index 0000000..1521e88 --- /dev/null +++ b/lib/SPELL_PRD/src/SPELLpyService.C @@ -0,0 +1,115 @@ +// ################################################################################ +// FILE : SPELLservice.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of spell service model +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +#include "SPELL_CFG/SPELLconfiguration.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLerror.H" +#include "SPELL_WRP/SPELLpyHandle.H" +#include "SPELL_UTIL/SPELLpythonHelper.H" +#include "SPELL_UTIL/SPELLpythonMonitors.H" + +// System includes --------------------------------------------------------- +#include +#include +#include +#include +#include "SPELL_PRD/SPELLpyService.H" +#include "SPELL_PRD/SPELLserviceWatchdogPython.H" + +//============================================================================= +// CONSTRUCTOR : SPELLpyService::SPELLpyService() +//============================================================================= +SPELLpyService::SPELLpyService(PyObject* serviceImpl) : +m_source("") +{ + SPELLsafePythonOperations ops("SPELLpyService::SPELLpyService"); + SPELLpyHandle pyServiceName = SPELLpythonHelper::instance().callMethod( + serviceImpl, "getName", NULL); + m_name = PyString_AsString(pyServiceName.get()); + SPELLpyHandle basePath = SPELLpythonHelper::instance().callMethod(serviceImpl, "getBasePath", NULL); + m_basePath = PyString_AsString(basePath.get()); + basePath = SPELLpythonHelper::instance().callMethod(serviceImpl, "getMainFile", NULL); + m_mainServiceFile = PyString_AsString(basePath.get()); + + m_service = serviceImpl; + Py_IncRef(serviceImpl); + std::cout << "Found Spell Service " << m_name << std::endl; + m_source = SPELLprocedureSourceCode(this->getMainServiceFile()); +} + +//============================================================================= +// DESTRUCTOR : SPELLpyService::~SPELLpyService +//============================================================================= +SPELLpyService::~SPELLpyService() +{ + Py_DecRef(m_service); +} + +bool SPELLpyService::useCommandOverride() const { + SPELLsafePythonOperations ops("useCommandOverride"); + SPELLpyHandle userOverride = SPELLpythonHelper::instance().callMethod(m_service,"useCommandOverride",NULL); + + SPELLpythonHelper::instance().checkError(); + + return userOverride.get() == Py_True; +} + +std::string SPELLpyService::getCommandOverride(int startTimeout, int loginTimeout, std::string config, std::string contextName, int ipcPort, std::string instanceId, + std::string warmstartFile) const { + SPELLsafePythonOperations ops("getCommandOverride"); + + SPELLpyHandle newCommand = PyObject_CallMethod(m_service,(char*)"getCommandOverride", (char*)"(iississ)", startTimeout, loginTimeout, config.c_str(), contextName.c_str(), ipcPort, instanceId.c_str(), warmstartFile.c_str(),NULL); + SPELLpythonHelper::instance().checkError(); + return PyString_AsString(newCommand.get()); +} + +void SPELLpyService::startWatchDog(void (*callback)(std::string&)) +{ + SPELLsafePythonOperations ops("startWatchDog"); + PyObject* callbackObj = SpellServiceWatchdog_Create(callback); + SPELLpyHandle newCommand = PyObject_CallMethod(m_service,(char*)"_startWatchDog", (char*)"(O)", callbackObj,NULL); + SPELLpythonHelper::instance().checkError(); +} + +void SPELLpyService::start() +{ + SPELLsafePythonOperations ops("start"); + + SPELLpyHandle newCommand = PyObject_CallMethod(m_service,(char*)"_start", (char*)"()",NULL); + SPELLpythonHelper::instance().checkError(); +} + +PyObject* SPELLpyService::getStartFunction() const +{ + SPELLsafePythonOperations ops("_getStartCode"); + + PyObject* funcCode = PyObject_CallMethod(m_service,(char*)"_getStartCode", (char*)"()",NULL); + SPELLpythonHelper::instance().checkError(); + + return PyMethod_Function(funcCode); +} diff --git a/lib/SPELL_PRD/src/SPELLserviceRegisterPython.C b/lib/SPELL_PRD/src/SPELLserviceRegisterPython.C new file mode 100644 index 0000000..9d6345e --- /dev/null +++ b/lib/SPELL_PRD/src/SPELLserviceRegisterPython.C @@ -0,0 +1,181 @@ +// ################################################################################ +// FILE : SPELLSpellServicePython.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the goto Python bindings +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" +#include "SPELL_PRD/SPELLservicePython.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLpythonHelper.H" +#include "SPELL_PRD/SPELLprocedureManager.H" +// System includes --------------------------------------------------------- +#include "structmember.h" + + + +//============================================================================ +// FUNCTION : SpellService_Init +// DESCRIPTION : Initialized of the SpellService python object +//============================================================================ +static int SpellService_Init( PySpellServiceObject* self, PyObject* args, PyObject* kwds ); +//============================================================================ +// FUNCTION : SpellService_Dealloc +// DESCRIPTION : Cleanup of the SpellService python object +//============================================================================ +static void SpellService_Dealloc( PySpellServiceObject* self ); +//============================================================================ +// FUNCTION : SpellService_New +// DESCRIPTION : Constructor of the SpellService python object +//============================================================================ +static PyObject* SpellService_New( PyTypeObject* type, PyObject* args, PyObject* kwds ); +//============================================================================ +// FUNCTION : SpellService_Call +// DESCRIPTION : Call a SpellService statement +//============================================================================ +static PyObject* SpellService_Call( PyObject* self, PyObject* args, PyObject* kargs ); + + + +//============================================================================ +// SpellService object member specification +//============================================================================ +static PyMemberDef SpellService_Members[] = +{ + {NULL} /* Sentinel */ +}; + +//============================================================================ +// SpellService object method specification +//============================================================================ +static PyMethodDef SpellService_Methods[] = +{ + {NULL} /* Sentinel */ +}; + +//============================================================================ +// Python representation of the SPELL SpellService object type +//============================================================================ +static PyTypeObject SpellService_Type = +{ + PyVarObject_HEAD_INIT(NULL,0) + "context.RegisterSpellService", //tp_name + sizeof(PySpellServiceObject), //tp_basicsize + 0, //tp_itemsize + (destructor)SpellService_Dealloc, //tp_dealloc + 0, //tp_print + 0, //tp_getattr + 0, //tp_setattr + 0, //tp_compare + 0, //tp_repr + 0, //tp_as_number + 0, //tp_as_sequence + 0, //tp_as_mapping + 0, //tp_hash + SpellService_Call, //tp_call + 0, //tp_str + 0, //tp_getattro + 0, //tp_setattro + 0, //tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, //tp_flags + "RegisterSpellService mechanism", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + SpellService_Methods, // tp_methods + SpellService_Members, // tp_members + 0, // tp_getset + 0, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + (initproc)SpellService_Init, // tp_init + 0, // tp_alloc + SpellService_New, // tp_new +}; + + +//============================================================================ +// FUNCTION : SpellService_Init +//============================================================================ +static int SpellService_Init( PySpellServiceObject* self, PyObject* args, PyObject* kwds ) +{ + return 0; +} + +//============================================================================ +// FUNCTION : SpellService_Dealloc +//============================================================================ +static void SpellService_Dealloc( PySpellServiceObject* self ) +{ + Py_TYPE(self)->tp_free( (PyObject*) self ); +} + +//============================================================================ +// FUNCTION : SpellService_New +//============================================================================ +static PyObject* SpellService_New( PyTypeObject* type, PyObject* args, PyObject* kwds ) +{ + //NOTE: Py_INCREF is already called! + PySpellServiceObject* self; + self = (PySpellServiceObject*) type->tp_alloc(type,0); + return (PyObject*)self; +} + +//============================================================================ +// FUNCTION : SpellService_Call +//============================================================================ +static PyObject* SpellService_Call( PyObject* self, PyObject* args, PyObject* kargs ) +{ + PyObject* target = PyTuple_GetItem(args,0); + Py_IncRef(target); + + SPELLprocedureManager::instance().registerServiceHandler(target); + + return target; +} + +//============================================================================ +// FUNCTION : SpellService_Install +//============================================================================ +void SpellService_Install() +{ + LOG_INFO("Installing SpellService object"); + + if (PyType_Ready(&SpellService_Type) < 0 ) + { + THROW_EXCEPTION("Cannot install SpellService", "Unable to register object type", SPELL_ERROR_PYTHON_API); + } + + PyObject* SpellService = SpellService_New( &SpellService_Type, NULL, NULL ); + Py_INCREF(SpellService); + + SPELLpythonHelper::instance().install(SpellService,"RegisterSpellService"); +} + diff --git a/lib/SPELL_PRD/src/SPELLserviceWatchdogPython.C b/lib/SPELL_PRD/src/SPELLserviceWatchdogPython.C new file mode 100644 index 0000000..7018933 --- /dev/null +++ b/lib/SPELL_PRD/src/SPELLserviceWatchdogPython.C @@ -0,0 +1,192 @@ +// ################################################################################ +// FILE : SPELLSpellServicePython.C +// DATE : Mar 17, 2011 +// PROJECT : SPELL +// DESCRIPTION: Implementation of the goto Python bindings +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE //////////////////////////////////////////////////////// +// Local includes ---------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" +#include "SPELL_PRD/SPELLserviceWatchdogPython.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLpythonHelper.H" +#include "SPELL_PRD/SPELLprocedureManager.H" +// System includes --------------------------------------------------------- +#include "structmember.h" + + + +//============================================================================ +// FUNCTION : SpellService_Init +// DESCRIPTION : Initialized of the SpellService python object +//============================================================================ +static int SpellServiceWatchdog_Init( PySpellServiceWatchdogObject* self, PyObject* args, PyObject* kwds ); +//============================================================================ +// FUNCTION : SpellService_Dealloc +// DESCRIPTION : Cleanup of the SpellService python object +//============================================================================ +static void SpellServiceWatchdog_Dealloc( PySpellServiceWatchdogObject* self ); +//============================================================================ +// FUNCTION : SpellService_New +// DESCRIPTION : Constructor of the SpellService python object +//============================================================================ +static PyObject* SpellServiceWatchdog_New( PyTypeObject* type, PyObject* args, PyObject* kwds ); +//============================================================================ +// FUNCTION : SpellService_Call +// DESCRIPTION : Call a SpellService statement +//============================================================================ +static PyObject* SpellServiceWatchdog_Call( PyObject* self, PyObject* args, PyObject* kargs ); + + + +//============================================================================ +// SpellService object member specification +//============================================================================ +static PyMemberDef SpellServiceWatchdog_Members[] = +{ + {"callback",T_OBJECT_EX,offsetof(PySpellServiceWatchdogObject,callback),0,"callback to C code"}, + {NULL} /* Sentinel */ +}; + +//============================================================================ +// SpellService object method specification +//============================================================================ +static PyMethodDef SpellServiceWatchdog_Methods[] = +{ + {NULL} /* Sentinel */ +}; + +//============================================================================ +// Python representation of the SPELL SpellService object type +//============================================================================ +static PyTypeObject SpellServiceWatchdog_Type = +{ + PyVarObject_HEAD_INIT(NULL,0) + "context.WatchdogCallback", //tp_name + sizeof(PySpellServiceWatchdogObject), //tp_basicsize + 0, //tp_itemsize + (destructor)SpellServiceWatchdog_Dealloc, //tp_dealloc + 0, //tp_print + 0, //tp_getattr + 0, //tp_setattr + 0, //tp_compare + 0, //tp_repr + 0, //tp_as_number + 0, //tp_as_sequence + 0, //tp_as_mapping + 0, //tp_hash + SpellServiceWatchdog_Call, //tp_call + 0, //tp_str + 0, //tp_getattro + 0, //tp_setattro + 0, //tp_as_buffer + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, //tp_flags + "WatchdogCallback mechanism", // tp_doc + 0, // tp_traverse + 0, // tp_clear + 0, // tp_richcompare + 0, // tp_weaklistoffset + 0, // tp_iter + 0, // tp_iternext + SpellServiceWatchdog_Methods, // tp_methods + SpellServiceWatchdog_Members, // tp_members + 0, // tp_getset + 0, // tp_base + 0, // tp_dict + 0, // tp_descr_get + 0, // tp_descr_set + 0, // tp_dictoffset + (initproc)SpellServiceWatchdog_Init, // tp_init + 0, // tp_alloc + SpellServiceWatchdog_New, // tp_new +}; + + +//============================================================================ +// FUNCTION : SpellService_Init +//============================================================================ +static int SpellServiceWatchdog_Init( PySpellServiceWatchdogObject* self, PyObject* args, PyObject* kwds ) +{ + return 0; +} + +//============================================================================ +// FUNCTION : SpellService_Dealloc +//============================================================================ +static void SpellServiceWatchdog_Dealloc( PySpellServiceWatchdogObject* self ) +{ + Py_TYPE(self)->tp_free( (PyObject*) self ); +} + +//============================================================================ +// FUNCTION : SpellService_New +//============================================================================ +static PyObject* SpellServiceWatchdog_New( PyTypeObject* type, PyObject* args, PyObject* kwds ) +{ + //NOTE: Py_INCREF is already called! + PySpellServiceWatchdogObject* self; + self = (PySpellServiceWatchdogObject*) type->tp_alloc(type,0); + PyObject* callback = PyTuple_GetItem(args,0); + Py_INCREF(callback); + PyObject_SetAttrString((PyObject*)self, "callback",callback); + + return (PyObject*)self; +} + +//============================================================================ +// FUNCTION : SpellService_Call +//============================================================================ +static PyObject* SpellServiceWatchdog_Call( PyObject* self, PyObject* args, PyObject* kargs ) +{ + PyObject* target = PyTuple_GetItem(args,0); + Py_IncRef(target); + + void (*callback)(std::string&); + PyObject* obj = PyObject_GetAttrString(self,"callback"); + callback = (void (*)(std::string&))PyCapsule_GetPointer(obj, NULL); + + std::string targetStr= PyString_AsString(target); + callback(targetStr); + Py_INCREF(Py_None); + return Py_None; +} + +//============================================================================ +// FUNCTION : SpellService_Install +//============================================================================ +PyObject* SpellServiceWatchdog_Create(void (*callback)(std::string&) ) +{ + LOG_INFO("Installing SpellService object"); + + if (PyType_Ready(&SpellServiceWatchdog_Type) < 0 ) + { + THROW_EXCEPTION("Cannot install SpellService", "Unable to register object type", SPELL_ERROR_PYTHON_API); + } + PyObject* callbackObj = PyCapsule_New((void*) callback,NULL,NULL); + PyObject* args = PyTuple_Pack(1,callbackObj,NULL); + PyObject* watchdog = SpellServiceWatchdog_New( &SpellServiceWatchdog_Type, args, NULL ); + Py_INCREF(watchdog); + + return watchdog; +} + diff --git a/lib/SPELL_SDB/Makefile.am b/lib/SPELL_SDB/Makefile.am index 1d58057..bdbd0a6 100644 --- a/lib/SPELL_SDB/Makefile.am +++ b/lib/SPELL_SDB/Makefile.am @@ -1,41 +1,46 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_SDB.la +lib_LTLIBRARIES = libSPELL_SDB_v2.la libSPELL_SDB_v3.la -libSPELL_SDB_la_CPPFLAGS = ${AM_CPPFLAGS} -DWITH_DEBUG -libSPELL_SDB_la_LDFLAGS = -version-info 1:0:0 +libSPELL_SDB_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} -libSPELL_SDB_la_SOURCES = \ - src/SPELLpyDatabaseMethods.C \ - src/SPELLpyDatabaseModule.C \ - src/SPELLdatabase.C \ - src/SPELLdatabaseFile.C \ - src/SPELLdatabaseFileSPB.C \ - src/SPELLdatabaseFactory.C - +libSPELL_SDB_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_SDB_v2_la_SOURCES = src/SPELLpyDatabaseMethods.C \ + src/SPELLpyDatabaseModule.C \ + src/SPELLdatabase.C \ + src/SPELLdatabaseFile.C \ + src/SPELLdatabaseFileSPB.C \ + src/SPELLdatabaseFactory.C + +libSPELL_SDB_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_SDB_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_SDB_v3_la_SOURCES = src/SPELLpyDatabaseMethods.C \ + src/SPELLpyDatabaseModule.C \ + src/SPELLdatabase.C \ + src/SPELLdatabaseFile.C \ + src/SPELLdatabaseFileSPB.C \ + src/SPELLdatabaseFactory.C diff --git a/lib/SPELL_SDB/src/SPELLdatabase.C b/lib/SPELL_SDB/src/SPELLdatabase.C index 28ca202..641784d 100644 --- a/lib/SPELL_SDB/src/SPELLdatabase.C +++ b/lib/SPELL_SDB/src/SPELLdatabase.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of SPELL database // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_SDB/src/SPELLdatabaseFactory.C b/lib/SPELL_SDB/src/SPELLdatabaseFactory.C index e7f1f61..f2118df 100644 --- a/lib/SPELL_SDB/src/SPELLdatabaseFactory.C +++ b/lib/SPELL_SDB/src/SPELLdatabaseFactory.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of SPELL database factory // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_SDB/src/SPELLdatabaseFile.C b/lib/SPELL_SDB/src/SPELLdatabaseFile.C index 072f6a7..5dda807 100644 --- a/lib/SPELL_SDB/src/SPELLdatabaseFile.C +++ b/lib/SPELL_SDB/src/SPELLdatabaseFile.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of database based on local files // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_SDB/src/SPELLdatabaseFileSPB.C b/lib/SPELL_SDB/src/SPELLdatabaseFileSPB.C index 8f7494c..243cc5d 100644 --- a/lib/SPELL_SDB/src/SPELLdatabaseFileSPB.C +++ b/lib/SPELL_SDB/src/SPELLdatabaseFileSPB.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation based on local SPB files // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_SDB/src/SPELLpyDatabaseMethods.C b/lib/SPELL_SDB/src/SPELLpyDatabaseMethods.C index f0844fe..2be34d0 100644 --- a/lib/SPELL_SDB/src/SPELLpyDatabaseMethods.C +++ b/lib/SPELL_SDB/src/SPELLpyDatabaseMethods.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of database Python bindings // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -46,7 +46,7 @@ int SPELLpyDatabase_Init( PyDatabaseObject* self, PyObject* args, PyObject* kwds std::string name = ""; std::string file = ""; std::string ext = ""; - std::string type = self->ob_type->tp_name; + std::string type = Py_TYPE(self)->tp_name; if (PyTuple_Size(args)!=3) { THROW_SYNTAX_EXCEPTION_NR("Cannot initialize database", "Expected database name, file and default extension"); @@ -93,7 +93,7 @@ void SPELLpyDatabase_Dealloc( PyDatabaseObject* self ) { delete self->__pdb; self->__pdb = NULL; - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ @@ -614,8 +614,7 @@ static PyMethodDef dictiter_methods[] = { PyTypeObject PyDatabaseIterator_Type = { - PyObject_HEAD_INIT(&PyType_Type) - 0, /* ob_size */ + PyVarObject_HEAD_INIT(&PyType_Type,0) "database-keyiterator", /* tp_name */ sizeof(PyDatabaseIteratorObject), /* tp_basicsize */ 0, /* tp_itemsize */ @@ -668,7 +667,7 @@ PyObject* SPELLpyDatabase_NewIterator( PyDatabaseObject* container ) //============================================================================ void SPELLpyDatabase_DeallocIterator( PyDatabaseIteratorObject* di ) { - di->ob_type->tp_free( (PyObject*)di ); + Py_TYPE(di)->tp_free( (PyObject*)di ); } //============================================================================ @@ -676,34 +675,26 @@ void SPELLpyDatabase_DeallocIterator( PyDatabaseIteratorObject* di ) //============================================================================ PyObject* SPELLpyDatabase_NextKey( PyDatabaseIteratorObject* di ) { - PyObject *key; - register Py_ssize_t i, mask; - register PyDictEntry *ep; - PyDictObject* d = di->di_dict; + PyObject *key,*value; + Py_ssize_t i; + int result; + PyObject* d = (PyObject*)di->di_dict; if (d == NULL) return NULL; assert (PyDict_Check(d)); - if (di->di_used != d->ma_used) - { - PyErr_SetString(PyExc_RuntimeError, "dictionary changed size during iteration"); - di->di_used = -1; /* Make this state sticky */ - return NULL; - } - i = di->di_pos; if (i < 0) goto fail; - ep = d->ma_table; - mask = d->ma_mask; - while (i <= mask && ep[i].me_value == NULL) i++; - di->di_pos = i+1; - if (i > mask) goto fail; + + result = PyDict_Next(d, &di->di_pos, &key, &value); + + if (!result) goto fail; + di->len--; - key = ep[i].me_key; + Py_INCREF(key); return key; fail: - Py_DECREF(d); di->di_dict = NULL; return NULL; } diff --git a/lib/SPELL_SDB/src/SPELLpyDatabaseModule.C b/lib/SPELL_SDB/src/SPELLpyDatabaseModule.C index 37c8ee9..dd40883 100644 --- a/lib/SPELL_SDB/src/SPELLpyDatabaseModule.C +++ b/lib/SPELL_SDB/src/SPELLpyDatabaseModule.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of Python database module // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -78,8 +78,8 @@ DATABASE_DEFINITION( SPELLpyDatabaseFileSPB_Type, "spell.adapter.databases.Datab ////////////////////////////////////////////////////////////////////////////// // PYTHON MODULE INITIALIZATION ////////////////////////////////////////////////////////////////////////////// -PyMODINIT_FUNC -initlibSPELL_SDB(void) + +MOD_INIT(libSPELL_SDB) { // Will hold the Python module PyObject* module; @@ -90,10 +90,12 @@ initlibSPELL_SDB(void) DATABASE_TYPE_READY(SPELLpyDatabaseFileSPB_Type); // Initialize the module - module = Py_InitModule3("libSPELL_SDB", NULL, "Module for SPELL databases"); + MOD_DEF(module,MOD_NAME(libSPELL_SDB),"Module for SPELL databases",NULL) // Add the classes to the module dictionary DATABASE_TYPE_LOAD(module, SPELLpyDatabase_Type, "Database" ); DATABASE_TYPE_LOAD(module, SPELLpyDatabaseFile_Type, "DatabaseFile" ); DATABASE_TYPE_LOAD(module, SPELLpyDatabaseFileSPB_Type, "DatabaseFileSPB" ); + + return MOD_SUCCESS_VAL(module); } diff --git a/lib/SPELL_SYN/Makefile.am b/lib/SPELL_SYN/Makefile.am index 8416b6c..0759e83 100644 --- a/lib/SPELL_SYN/Makefile.am +++ b/lib/SPELL_SYN/Makefile.am @@ -1,49 +1,53 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_SYN.la - -libSPELL_SYN_la_LDFLAGS = -version-info 1:0:0 - -if PLATFORM_LINUX - ADDITIONAL_SRC= src/SPELLmutexPOSIX.C \ - src/SPELLmutexFactoryPOSIX.C \ - src/SPELLconditionPOSIX.C \ - src/SPELLconditionFactoryPOSIX.C -else - ADDITIONAL_SRC= -endif - -libSPELL_SYN_la_SOURCES = $(ADDITIONAL_SRC) \ - src/SPELLtimer.C \ - src/SPELLevent.C \ - src/SPELLthread.C \ - src/SPELLthreadWorker.C \ - src/SPELLthreadPool.C - - +lib_LTLIBRARIES = libSPELL_SYN_v2.la libSPELL_SYN_v3.la + +libSPELL_SYN_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_SYN_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_SYN_v2_la_SOURCES = src/SPELLmutexPOSIX.C \ + src/SPELLmutexFactoryPOSIX.C \ + src/SPELLconditionPOSIX.C \ + src/SPELLconditionFactoryPOSIX.C \ + src/SPELLtimer.C \ + src/SPELLevent.C \ + src/SPELLthread.C \ + src/SPELLthreadWorker.C \ + src/SPELLthreadPool.C + +libSPELL_SYN_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_SYN_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_SYN_v3_la_SOURCES = src/SPELLmutexPOSIX.C \ + src/SPELLmutexFactoryPOSIX.C \ + src/SPELLconditionPOSIX.C \ + src/SPELLconditionFactoryPOSIX.C \ + src/SPELLtimer.C \ + src/SPELLevent.C \ + src/SPELLthread.C \ + src/SPELLthreadWorker.C \ + src/SPELLthreadPool.C + \ No newline at end of file diff --git a/lib/SPELL_SYN/src/SPELLconditionFactoryPOSIX.C b/lib/SPELL_SYN/src/SPELLconditionFactoryPOSIX.C index fbdad75..a689e98 100644 --- a/lib/SPELL_SYN/src/SPELLconditionFactoryPOSIX.C +++ b/lib/SPELL_SYN/src/SPELLconditionFactoryPOSIX.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of condition factory for POSIX // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_SYN/src/SPELLconditionPOSIX.C b/lib/SPELL_SYN/src/SPELLconditionPOSIX.C index 34757f5..7fe518b 100644 --- a/lib/SPELL_SYN/src/SPELLconditionPOSIX.C +++ b/lib/SPELL_SYN/src/SPELLconditionPOSIX.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of condition mechanism for POSIX // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -36,7 +36,21 @@ //============================================================================= SPELLconditionPOSIX::SPELLconditionPOSIX() { - pthread_cond_init(&m_condition, NULL); + int result = pthread_cond_init(&m_condition, NULL); + if (result == EAGAIN){ + LOG_ERROR("[SPELLconditionPOSIX.create] Error creating condition! The system lacked the necessary resources (other than memory) to initialise another condition variable."); + } else if (result == ENOMEM) { + LOG_ERROR("[SPELLconditionPOSIX.create] Error creating condition! Insufficient memory exists to initialise the condition variable."); + } else if (result == EBUSY) { + LOG_ERROR("[SPELLconditionPOSIX.create] Error creating condition! The implementation has detected an attempt to re-initialise the object referenced by cond, a previously initialised, but not yet destroyed, condition variable."); + } else if (result == EINVAL) { + LOG_ERROR("[SPELLconditionPOSIX.create] Error creating condition! The value specified by attr is invalid."); + } +//#ifdef WITH_DEBUG +// std::stringstream buf; +// buf << "SPELLconditionPOSIX create " << &m_condition; +// DEBUG( buf.str()); +//#endif } //============================================================================= @@ -44,7 +58,12 @@ SPELLconditionPOSIX::SPELLconditionPOSIX() //============================================================================= SPELLconditionPOSIX::~SPELLconditionPOSIX() { - pthread_cond_destroy(&m_condition); +//#ifdef WITH_DEBUG +// std::stringstream buf; +// buf << "SPELLconditionPOSIX destroy " << &m_condition; +// DEBUG( buf.str()); +//#endif + pthread_cond_destroy(&m_condition); } //============================================================================= @@ -73,8 +92,23 @@ bool SPELLconditionPOSIX::wait( SPELLmutex* m, unsigned long timeoutMsec ) clock_gettime(CLOCK_REALTIME, &abstime); abstime.tv_sec += timeoutMsec / 1000; abstime.tv_nsec += ( timeoutMsec - (timeoutMsec / 1000)*1000 ) * 1000; + if (abstime.tv_nsec >= 1000000000){ + DEBUG("[SPELLconditionPOSIX.wait] abstime.tv_nsec is >= 1000000000. This may cause Condition error EINVAL. Value will be corrected."); + abstime.tv_sec += abstime.tv_nsec / 1000000000; + abstime.tv_nsec = abstime.tv_nsec % 1000000000; + } + if (abstime.tv_nsec < 0) { + DEBUG("[SPELLconditionPOSIX.wait] abstime.tv_nsec is < 0. This may cause Condition error EINVAL. Value will be corrected."); + abstime.tv_sec -= 1; + abstime.tv_nsec = (abstime.tv_nsec + 1000000000) % 1000000000; + } SPELLmutexPOSIX* pmutex = dynamic_cast(m->getImpl()); assert(pmutex->get() != 0); +//#ifdef WITH_DEBUG +// std::stringstream buf; +// buf << "SPELLconditionPOSIX pthread_cond_timedwait " << &m_condition; +// DEBUG( buf.str()); +//#endif int ret = pthread_cond_timedwait(&m_condition, pmutex->get(), &abstime); if (ret == ETIMEDOUT) { diff --git a/lib/SPELL_SYN/src/SPELLevent.C b/lib/SPELL_SYN/src/SPELLevent.C index 644d4e3..8ded4c3 100644 --- a/lib/SPELL_SYN/src/SPELLevent.C +++ b/lib/SPELL_SYN/src/SPELLevent.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the event mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -42,7 +42,7 @@ SPELLevent::SPELLevent( bool log ) { m_log = log; m_isClear = false; - DEBUG("[EVE] Event " + PSTR(this) + " created with mutex " + PSTR(&m_mutex)) + //DEBUG("[EVE] Event " + PSTR(this) + " created with mutex " + PSTR(&m_mutex)) if (m_log) std::cerr << " CREATE EVENT " << (void*) this << std::endl; } @@ -51,7 +51,7 @@ SPELLevent::SPELLevent( bool log ) //============================================================================= SPELLevent::~SPELLevent() { - DEBUG("[EVE] Event " + PSTR(this) + " destroyed") + //DEBUG("[EVE] Event " + PSTR(this) + " destroyed") if (m_log) std::cerr << " DESTROY EVENT " << (void*) this << std::endl; } @@ -61,7 +61,7 @@ SPELLevent::~SPELLevent() void SPELLevent::set() { if (m_log) std::cerr << "SET EVENT TRY IN " << (void*) this << std::endl; - DEBUG("[EVE] Event " + PSTR(this) + " set") + //DEBUG("[EVE] Event " + PSTR(this) + " set") m_mutex.lock(); if (m_isClear) { @@ -80,7 +80,7 @@ void SPELLevent::set() void SPELLevent::clear() { if (m_log) std::cerr << "CHECK CLEAR EVENT IN " << (void*) this << std::endl; - DEBUG("[EVE] Event " + PSTR(this) + " clear") + //DEBUG("[EVE] Event " + PSTR(this) + " clear") m_mutex.lock(); m_isClear = true; m_mutex.unlock(); @@ -93,14 +93,14 @@ void SPELLevent::clear() void SPELLevent::wait() { if (m_log) std::cerr << "WAIT EVENT TRY IN " << (void*) this << std::endl; - DEBUG("[EVE] Event " + PSTR(this) + " wait in with mutex " + PSTR(&m_mutex)) + //DEBUG("[EVE] Event " + PSTR(this) + " wait in with mutex " + PSTR(&m_mutex)) m_mutex.lock(); while (m_isClear) { m_condition.wait(&m_mutex); } m_mutex.unlock(); - DEBUG("[EVE] Event " + PSTR(this) + " wait out") + //DEBUG("[EVE] Event " + PSTR(this) + " wait out") if (m_log) std::cerr << "WAIT EVENT FINISH " << (void*) this << std::endl; } @@ -110,7 +110,7 @@ void SPELLevent::wait() bool SPELLevent::wait( unsigned long timeoutMsec ) { bool timedout = false; - DEBUG("[EVE] Event " + PSTR(this) + " wait in with mutex " + PSTR(&m_mutex)) + //DEBUG("[EVE] Event " + PSTR(this) + " wait in with mutex " + PSTR(&m_mutex)) if (m_log) std::cerr << " WAIT EVENT T.O. TRY IN " << (void*) this << std::endl; m_mutex.lock(); while (m_isClear && !timedout) @@ -121,7 +121,7 @@ bool SPELLevent::wait( unsigned long timeoutMsec ) } m_mutex.unlock(); if (m_log) std::cerr << " WAIT EVENT T.O. FINISH" << (void*) this << std::endl; - DEBUG("[EVE] Event " + PSTR(this) + " wait out") + //DEBUG("[EVE] Event " + PSTR(this) + " wait out") return timedout; } @@ -131,7 +131,7 @@ bool SPELLevent::wait( unsigned long timeoutMsec ) bool SPELLevent::isClear() { if (m_log) std::cerr << " IS CLEAR EVENT IN " << (void*) this << std::endl; - DEBUG("[EVE] Event " + PSTR(this) + " isclear with mutex " + PSTR(&m_mutex)) + //DEBUG("[EVE] Event " + PSTR(this) + " isclear with mutex " + PSTR(&m_mutex)) bool isClear = false; m_mutex.lock(); isClear = m_isClear; diff --git a/lib/SPELL_SYN/src/SPELLmutexFactoryPOSIX.C b/lib/SPELL_SYN/src/SPELLmutexFactoryPOSIX.C index c9c759e..e81712c 100644 --- a/lib/SPELL_SYN/src/SPELLmutexFactoryPOSIX.C +++ b/lib/SPELL_SYN/src/SPELLmutexFactoryPOSIX.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the mutex factory for POSIX // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_SYN/src/SPELLmutexPOSIX.C b/lib/SPELL_SYN/src/SPELLmutexPOSIX.C index f3fe595..e98e9fb 100644 --- a/lib/SPELL_SYN/src/SPELLmutexPOSIX.C +++ b/lib/SPELL_SYN/src/SPELLmutexPOSIX.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the mutex mechanism for POSIX // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -36,6 +36,7 @@ SPELLmutexPOSIX::SPELLmutexPOSIX() : SPELLmutexIF() { + m_threadId = 0; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); diff --git a/lib/SPELL_SYN/src/SPELLthread.C b/lib/SPELL_SYN/src/SPELLthread.C index 5746b09..cbaa733 100644 --- a/lib/SPELL_SYN/src/SPELLthread.C +++ b/lib/SPELL_SYN/src/SPELLthread.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the thread mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -55,7 +55,7 @@ SPELLthread::SPELLthread( std::string id ) pthread_attr_init(&m_threadAttr); pthread_attr_setdetachstate(&m_threadAttr, PTHREAD_CREATE_JOINABLE); m_threadRunning = false; - DEBUG("[THR] Thread " + m_threadId + " created") + // DEBUG("[THR] Thread " + m_threadId + " created") } //============================================================================= @@ -63,7 +63,7 @@ SPELLthread::SPELLthread( std::string id ) //============================================================================= SPELLthread::~SPELLthread() { - DEBUG("[THR] Thread " + m_threadId + " do destroy"); + //DEBUG("[THR] Thread " + m_threadId + " do destroy"); m_threadRunning = false; pthread_attr_destroy(&m_threadAttr); } @@ -74,12 +74,24 @@ SPELLthread::~SPELLthread() void SPELLthread::start() { SPELLmonitor m(m_threadLock); - int result = pthread_create(&m_thread, (const pthread_attr_t*) &m_threadAttr, do_run, this ); - //std::cerr << "==== create thread " << m_thread << " " << m_threadId << std::endl; - if( result != 0) - { - throw SPELLsyncError("Unable to start thread", "Cannot create thread: " + ISTR(result)); - } + int result; + int tries = 0; + do { + result = pthread_create(&m_thread, (const pthread_attr_t*) &m_threadAttr, do_run, this ); + tries++; + if (result != 0) + { + if (tries > 10) + { + perror("pthread_create"); + throw SPELLsyncError("Unable to start thread", "Cannot create thread: " + ISTR(result)); + } + else + { + usleep(500000); + } + } + } while (result != 0); } //============================================================================= @@ -116,10 +128,10 @@ void SPELLthread::doRun() void SPELLthread::join() { //std::cerr << "==== before join " << m_thread << " " << m_threadId << std::endl; - SPELLmonitor m(m_threadLock); - if (m_thread != 0) + pthread_t thread = m_thread; + if (thread != 0) { - pthread_join(m_thread, NULL); + pthread_join(thread, NULL); //std::cerr << "==== after join " << m_thread << " " << m_threadId << std::endl; m_thread = 0; m_threadRunning = false; diff --git a/lib/SPELL_SYN/src/SPELLthreadPool.C b/lib/SPELL_SYN/src/SPELLthreadPool.C index a1447d8..be732e9 100644 --- a/lib/SPELL_SYN/src/SPELLthreadPool.C +++ b/lib/SPELL_SYN/src/SPELLthreadPool.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the thread pool mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_SYN/src/SPELLthreadWorker.C b/lib/SPELL_SYN/src/SPELLthreadWorker.C index e11d396..a3de9a2 100644 --- a/lib/SPELL_SYN/src/SPELLthreadWorker.C +++ b/lib/SPELL_SYN/src/SPELLthreadWorker.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the thread worker mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -44,6 +44,7 @@ SPELLthreadWorker::SPELLthreadWorker( const std::string& id ) m_work = NULL; m_shutdown = false; m_newWork.clear(); + m_busy = false; } //============================================================================= diff --git a/lib/SPELL_SYN/src/SPELLtimer.C b/lib/SPELL_SYN/src/SPELLtimer.C index 18290b8..4f2400b 100644 --- a/lib/SPELL_SYN/src/SPELLtimer.C +++ b/lib/SPELL_SYN/src/SPELLtimer.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the timer mechanism // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -46,7 +46,7 @@ SPELLtimer::SPELLtimer( unsigned long period, SPELLtimerListener& listener ) m_period = period; m_elapsed = 0; m_timeout = 0; - DEBUG("[TMR] Timer created (1) " + PSTR(this)) + //DEBUG("[TMR] Timer created (1) " + PSTR(this)) } //============================================================================= @@ -61,7 +61,7 @@ SPELLtimer::SPELLtimer( unsigned long period, SPELLtimerListener& listener, unsi m_period = period; m_elapsed = 0; m_timeout = timeout; - DEBUG("[TMR] Timer created (2) " + PSTR(this)) + //DEBUG("[TMR] Timer created (2) " + PSTR(this)) } //============================================================================= @@ -79,26 +79,26 @@ SPELLtimer::~SPELLtimer() //============================================================================= void SPELLtimer::run() { - DEBUG("[TMR] Timer thread started: " + PSTR(this)) + //DEBUG("[TMR] Timer thread started: " + PSTR(this)) setCounting(true); while(isCounting()) { usleep(m_period*1000);// Period is given in msec if (callTimerCallback()) { - DEBUG("[TMR] Ending timer thread (callback) for " + PSTR(this)) + //DEBUG("[TMR] Ending timer thread (callback) for " + PSTR(this)) return; } if (checkTimeout()) { - DEBUG("[TMR] Ending timer thread (timeout) for " + PSTR(this)) + //DEBUG("[TMR] Ending timer thread (timeout) for " + PSTR(this)) return; } - DEBUG("[TMR] Stop event wait in " + PSTR(this)) + //DEBUG("[TMR] Stop event wait in " + PSTR(this)) wait(); - DEBUG("[TMR] Stop event wait out " + PSTR(this)) + //DEBUG("[TMR] Stop event wait out " + PSTR(this)) } - DEBUG("[TMR] Ending timer thread for " + PSTR(this)) + //DEBUG("[TMR] Ending timer thread for " + PSTR(this)) } //============================================================================= @@ -137,10 +137,10 @@ bool SPELLtimer::checkTimeout() //============================================================================= void SPELLtimer::cancel() { - DEBUG("[TMR] Cancelling timer thread IN" + PSTR(this)) + //DEBUG("[TMR] Cancelling timer thread IN" + PSTR(this)) setCounting(false); cont(); - DEBUG("[TMR] Cancelling timer thread " + PSTR(this) + " OUT ") + //DEBUG("[TMR] Cancelling timer thread " + PSTR(this) + " OUT ") } //============================================================================= @@ -166,7 +166,7 @@ void SPELLtimer::setCounting( bool counting ) //============================================================================= void SPELLtimer::stop() { - DEBUG("[TMR] Stopping timer IN" + PSTR(this)) + //DEBUG("[TMR] Stopping timer IN" + PSTR(this)) SPELLmonitor monitor(m_mutex); m_stopEvent.clear(); } @@ -176,7 +176,7 @@ void SPELLtimer::stop() //============================================================================= void SPELLtimer::cont() { - DEBUG("[TMR] Continue timer IN" + PSTR(this)) + //DEBUG("[TMR] Continue timer IN" + PSTR(this)) SPELLmonitor monitor(m_mutex); m_stopEvent.set(); } @@ -186,6 +186,6 @@ void SPELLtimer::cont() //============================================================================= void SPELLtimer::wait() { - DEBUG("[TMR] Wait timer IN" + PSTR(this)) + //DEBUG("[TMR] Wait timer IN" + PSTR(this)) m_stopEvent.wait(); } diff --git a/lib/SPELL_UTIL/Makefile.am b/lib/SPELL_UTIL/Makefile.am index fbfa0e5..a2d346e 100644 --- a/lib/SPELL_UTIL/Makefile.am +++ b/lib/SPELL_UTIL/Makefile.am @@ -1,41 +1,51 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_UTIL.la +lib_LTLIBRARIES = libSPELL_UTIL_v2.la libSPELL_UTIL_v3.la -libSPELL_UTIL_la_LDFLAGS = -version-info 3:0:0 -libSPELL_UTIL_la_SOURCES = \ - src/SPELLtime.C \ - src/SPELLutils.C \ - src/SPELLlog.C \ - src/SPELLpythonHelper.C \ - src/SPELLlogPython.C \ - src/SPELLpyArgs.C \ - src/SPELLcompression.C \ - src/SPELLerror.C - +libSPELL_UTIL_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_UTIL_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_UTIL_v2_la_SOURCES = src/SPELLtime.C \ + src/SPELLutils.C \ + src/SPELLlog.C \ + src/SPELLpythonHelper.C \ + src/SPELLlogPython.C \ + src/SPELLpyArgs.C \ + src/SPELLcompression.C \ + src/SPELLerror.C + + +libSPELL_UTIL_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_UTIL_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_UTIL_v3_la_SOURCES = src/SPELLtime.C \ + src/SPELLutils.C \ + src/SPELLlog.C \ + src/SPELLpythonHelper.C \ + src/SPELLlogPython.C \ + src/SPELLpyArgs.C \ + src/SPELLcompression.C \ + src/SPELLerror.C diff --git a/lib/SPELL_UTIL/src/SPELLcompression.C b/lib/SPELL_UTIL/src/SPELLcompression.C index 6e4dd38..4d35184 100644 --- a/lib/SPELL_UTIL/src/SPELLcompression.C +++ b/lib/SPELL_UTIL/src/SPELLcompression.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the compression utility // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_UTIL/src/SPELLerror.C b/lib/SPELL_UTIL/src/SPELLerror.C index b431062..49939d0 100644 --- a/lib/SPELL_UTIL/src/SPELLerror.C +++ b/lib/SPELL_UTIL/src/SPELLerror.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the Python error bindings // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -101,8 +101,7 @@ static PyMethodDef ExecutionAborted_Methods[] = //============================================================================ static PyTypeObject ExecutionAborted_Type = { - PyObject_HEAD_INIT(NULL) - 0, //ob_size + PyVarObject_HEAD_INIT(NULL,0) "executor.ExecutionAborted", //tp_name sizeof(PyExecutionAbortedObject), //tp_basicsize 0, //tp_itemsize @@ -163,8 +162,7 @@ static PyMethodDef ExecutionTerminated_Methods[] = //============================================================================ static PyTypeObject ExecutionTerminated_Type = { - PyObject_HEAD_INIT(NULL) - 0, //ob_size + PyVarObject_HEAD_INIT(NULL,0) "executor.ExecutionTerminated", //tp_name sizeof(PyExecutionTerminatedObject), //tp_basicsize 0, //tp_itemsize @@ -232,7 +230,7 @@ static int ExecutionAborted_Init( PyExecutionAbortedObject* self, PyObject* args static void ExecutionAborted_Dealloc( PyExecutionAbortedObject* self ) { Py_XDECREF(self->message); - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ @@ -269,7 +267,7 @@ static int ExecutionTerminated_Init( PyExecutionTerminatedObject* self, PyObject //============================================================================ static void ExecutionTerminated_Dealloc( PyExecutionTerminatedObject* self ) { - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ @@ -499,7 +497,7 @@ SPELLcoreException* SPELLerror::errorToException( PyObject* err, PyObject* ptype } else { - message = " (no back trace information)\n"; + message = ""; } LOG_ERROR("**************************************"); @@ -513,7 +511,7 @@ SPELLcoreException* SPELLerror::errorToException( PyObject* err, PyObject* ptype else { PyObject* data = PyTuple_GetItem(pvalue, 1); - if (PyTuple_Size(data)==4) + if (data && PyTuple_Size(data)==4) { PyObject* filename = PyTuple_GetItem( data, 0); PyObject* lineno = PyTuple_GetItem( data, 1); @@ -555,7 +553,7 @@ SPELLcoreException* SPELLerror::errorToException( PyObject* err, PyObject* ptype else if (PyErr_GivenExceptionMatches(err, PyExc_SyntaxError)) { // It is a tuple like ('invalid syntax', ('file.py', lineno, offset, 'text')) - message = "Unknown syntax error (could not gather information)\n" + message; + message = "Syntax error\n" + message; if (PyTuple_Size(pvalue)==2) { PyObject* data = PyTuple_GetItem(pvalue, 1); diff --git a/lib/SPELL_UTIL/src/SPELLlog.C b/lib/SPELL_UTIL/src/SPELLlog.C index dea7c12..5a8139e 100644 --- a/lib/SPELL_UTIL/src/SPELLlog.C +++ b/lib/SPELL_UTIL/src/SPELLlog.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the logger // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -109,7 +109,7 @@ void SPELLlog::setLogFile( const std::string& filename, const std::string timest logfilename = logbase + "/" + logfilename; m_logFileName = logfilename; - log4cplus::SharedFileAppenderPtr appender( new log4cplus::RollingFileAppender(LOG4CPLUS_TEXT(logfilename), 10*1024*1024, 5, false, true)); + log4cplus::SharedFileAppenderPtr appender( new log4cplus::RollingFileAppender(LOG4CPLUS_TEXT(logfilename), 10*1024*1024, 5, true, true)); appender->setLayout( std::auto_ptr(new log4cplus::PatternLayout(LOG_PATTERN)) ); appender->getloc(); log4cplus::Logger::getRoot().addAppender( log4cplus::SharedAppenderPtr(appender.get()) ); diff --git a/lib/SPELL_UTIL/src/SPELLlogPython.C b/lib/SPELL_UTIL/src/SPELLlogPython.C index 72465d6..5c87bc6 100644 --- a/lib/SPELL_UTIL/src/SPELLlogPython.C +++ b/lib/SPELL_UTIL/src/SPELLlogPython.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the logger bindings // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -88,8 +88,7 @@ static PyMethodDef Log_Methods[] = //============================================================================ static PyTypeObject Log_Type = { - PyObject_HEAD_INIT(NULL) - 0, //ob_size + PyVarObject_HEAD_INIT(NULL, 0) "executor.LOG", //tp_name sizeof(PyLogObject), //tp_basicsize 0, //tp_itemsize @@ -143,7 +142,7 @@ static int Log_Init( PyLogObject* self, PyObject* args, PyObject* kwds ) //============================================================================ static void Log_Dealloc( PyLogObject* self ) { - self->ob_type->tp_free( (PyObject*) self ); + Py_TYPE(self)->tp_free( (PyObject*) self ); } //============================================================================ @@ -232,6 +231,8 @@ static PyObject* Log_Call( PyObject* self, PyObject* args, PyObject* kargs ) case LOG_ERROR: LOG_ERROR_PY(message); break; + default: + break; } Py_RETURN_NONE; diff --git a/lib/SPELL_UTIL/src/SPELLpyArgs.C b/lib/SPELL_UTIL/src/SPELLpyArgs.C index 13cd96c..3fe504a 100644 --- a/lib/SPELL_UTIL/src/SPELLpyArgs.C +++ b/lib/SPELL_UTIL/src/SPELLpyArgs.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the argument helper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -220,7 +220,7 @@ SPELLtime SPELLpyArgs::getModifier_AsTime( const std::string& modifier ) const bool SPELLpyArgs::getModifier_AsBoolean( const std::string& modifier ) const { PyObject* valueObj = (*this)[modifier]; - DEBUG("[CFGHLP] Configured modifier: " + PYREPR(valueObj)) + DEBUG("[CFGHLP] Configured modifier: " + PYREPR(valueObj)); bool value = false; if (valueObj != NULL) { @@ -282,7 +282,7 @@ SPELLtime SPELLpyArgs::getModifier_Delay() const std::string SPELLpyArgs::getModifier_ValueFormat() const { PyObject* formatObj = (*this)[LanguageModifiers::ValueFormat]; - DEBUG("[CFGHLP] Configured ValueFormat: " + PYREPR(formatObj)) + DEBUG("[CFGHLP] Configured ValueFormat: " + PYREPR(formatObj)); std::string format = LanguageConstants::ENG; @@ -310,7 +310,7 @@ std::string SPELLpyArgs::getModifier_ValueFormat() const std::string SPELLpyArgs::getModifier_Expected() const { PyObject* expectedObj = (*this)[LanguageModifiers::Expected]; - DEBUG("[CFGHLP] Configured Expected: " + PYREPR(expectedObj)) + DEBUG("[CFGHLP] Configured Expected: " + PYREPR(expectedObj)); std::string expected = ""; if ( expectedObj != NULL ) @@ -333,7 +333,7 @@ std::string SPELLpyArgs::getModifier_Expected() const std::string SPELLpyArgs::getModifier_LoRed() const { PyObject* valueObj = (*this)[LanguageModifiers::LoRed]; - DEBUG("[CFGHLP] Configured LoRed: " + PYREPR(valueObj)) + DEBUG("[CFGHLP] Configured LoRed: " + PYREPR(valueObj)); std::string value = ""; if ( valueObj != NULL ) @@ -349,7 +349,7 @@ std::string SPELLpyArgs::getModifier_LoRed() const std::string SPELLpyArgs::getModifier_LoYel() const { PyObject* valueObj = (*this)[LanguageModifiers::LoYel]; - DEBUG("[CFGHLP] Configured LoYel: " + PYREPR(valueObj)) + DEBUG("[CFGHLP] Configured LoYel: " + PYREPR(valueObj)); std::string value = ""; if ( valueObj != NULL ) @@ -365,7 +365,7 @@ std::string SPELLpyArgs::getModifier_LoYel() const std::string SPELLpyArgs::getModifier_HiRed() const { PyObject* valueObj = (*this)[LanguageModifiers::HiRed]; - DEBUG("[CFGHLP] Configured HiRed: " + PYREPR(valueObj)) + DEBUG("[CFGHLP] Configured HiRed: " + PYREPR(valueObj)); std::string value = ""; if ( valueObj != NULL ) @@ -381,7 +381,39 @@ std::string SPELLpyArgs::getModifier_HiRed() const std::string SPELLpyArgs::getModifier_HiYel() const { PyObject* valueObj = (*this)[LanguageModifiers::HiYel]; - DEBUG("[CFGHLP] Configured HiYel: " + PYREPR(valueObj)) + DEBUG("[CFGHLP] Configured HiYel: " + PYREPR(valueObj)); + + std::string value = ""; + if ( valueObj != NULL ) + { + value = PYSSTR(valueObj); + } + return value; +} + +//============================================================================= +// METHOD: SPELLpyArgs::getModifier_Select() +//============================================================================= +std::string SPELLpyArgs::getModifier_Select() const +{ + PyObject* valueObj = (*this)[LanguageModifiers::Select]; + DEBUG("[CFGHLP] Configured Select: " + PYREPR(valueObj)); + + std::string value = ""; + if ( valueObj != NULL ) + { + value = PYSSTR(valueObj); + } + return value; +} + +//============================================================================= +// METHOD: SPELLpyArgs::getModifier_LimitType() +//============================================================================= +std::string SPELLpyArgs::getModifier_LimitType() const +{ + PyObject* valueObj = (*this)[LanguageModifiers::Type]; + DEBUG("[CFGHLP] Configured Limit Type: " + PYREPR(valueObj)); std::string value = ""; if ( valueObj != NULL ) @@ -546,10 +578,16 @@ std::string SPELLpyArgs::getModifier_Default() const } else if (PyFloat_Check( valueObj )) { +#if PY_MAJOR_VERSION >= 3 + PyObject* obj = PyObject_Str(valueObj); + value = PyUnicode_AsUTF8(obj); + Py_DECREF(obj); +#else char* buffer = new char[512]; - PyFloat_AsString(buffer, (PyFloatObject*) (valueObj) ); - value = std::string(buffer); - delete buffer; + PyFloat_AsString(buffer, (PyFloatObject*) (valueObj) ); + value = std::string(buffer); + delete buffer; +#endif } else if (PyBool_Check( valueObj )) { @@ -573,3 +611,4 @@ std::string SPELLpyArgs::getModifier_Default() const } return value; } + diff --git a/lib/SPELL_UTIL/src/SPELLpythonHelper.C b/lib/SPELL_UTIL/src/SPELLpythonHelper.C index 1cc035d..5357c39 100644 --- a/lib/SPELL_UTIL/src/SPELLpythonHelper.C +++ b/lib/SPELL_UTIL/src/SPELLpythonHelper.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the Python helper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -39,7 +39,12 @@ #include "Python-ast.h" #include "opcode.h" #include "traceback.h" - +#include "pystate.h" +#if PY_MAJOR_VERSION >= 3 +# include "code.h" +# include "frameobject.h" +# include "structmember.h" +#endif // See setNewLine method #undef MIN #undef MAX @@ -63,7 +68,7 @@ static PyObject * objargs_mktuple(va_list va) int i, n = 0; va_list countva; PyObject *result, *tmp; - countva = va; + va_copy(countva, va); while ( ( (PyObject *) va_arg(countva, PyObject *)) != NULL) { ++n; @@ -139,7 +144,7 @@ void SPELLpythonHelper::importAllFrom( const std::string& package ) moduleDict = PyModule_GetDict( module ); } - PyObject* mainModule = PyImport_AddModule("__main__"); + PyObject* mainModule = PyImport_ImportModule(BUILTINS); if (mainModule == NULL) { THROW_EXCEPTION("Cannot import " + package, "Unable to access main module", SPELL_ERROR_PYTHON_API ); @@ -152,18 +157,59 @@ void SPELLpythonHelper::importAllFrom( const std::string& package ) PyDict_Merge(main_dict, moduleDict, 0); DEBUG("[PYH] Imported all from module " + package); } +void SPELLpythonHelper::updateBuiltinsWith( const std::string& package ) +{ + LOG_INFO("Import all from " + package); + PyObject* dict = PyDict_New(); + PyObject* module = PyImport_ImportModuleEx( const_cast(package.c_str()), dict, dict, NULL); + if ( module == NULL ) + { + SPELLcoreException* ex = SPELLerror::instance().errorToException(); + throw *ex; + } + + // Borrowed + PyObject* moduleDict = PyModule_GetDict( module ); + std::vector tokens = SPELLutils::tokenize( package, "." ); + + std::vector::iterator it; + for( it = tokens.begin(); it != tokens.end(); it++ ) + { + // Skip the first token + if (it == tokens.begin()) continue; + // Get the submodule + // Borrowed reference + module = PyDict_GetItemString( moduleDict, (*it).c_str() ); + moduleDict = PyModule_GetDict( module ); + } + + PyObject* mainModule = PyImport_ImportModule(BUILTINS); + if (mainModule == NULL) + { + THROW_EXCEPTION("Cannot import " + package, "Unable to access "BUILTINS" module", SPELL_ERROR_PYTHON_API ); + } + PyObject* main_dict = PyModule_GetDict(mainModule); + if (main_dict== NULL) + { + THROW_EXCEPTION("Cannot import " + package, "Unable to access "BUILTINS" module dictionary", SPELL_ERROR_PYTHON_API ); + } + PyDict_Merge(main_dict, moduleDict, 0); + DEBUG("[PYH] Imported all from module " + package); +} //============================================================================ // METROD : SPELLpythonHelper::importUserLibraries //============================================================================ -void SPELLpythonHelper::importUserLibraries( const std::string& libraryPath ) +bool SPELLpythonHelper::importUserLibraries( const std::string& libraryPath, std::string& errors ) { + bool success = true; + if (libraryPath != "" && libraryPath != "None" ) { if (SPELLutils::isDirectory(libraryPath)) { // Import the main module first - PyObject* mainModule = PyImport_AddModule("__main__"); + PyObject* mainModule = PyImport_ImportModule(BUILTINS); if (mainModule == NULL) { THROW_EXCEPTION("Cannot import user libraries", "Unable to access main module", SPELL_ERROR_PYTHON_API ); @@ -179,7 +225,7 @@ void SPELLpythonHelper::importUserLibraries( const std::string& libraryPath ) if (PyDict_Contains( main_dict, flag.get()) ) { LOG_WARN("[PYH] No need to re-import user libraries"); - return; + return true; } LOG_INFO("Loading user libraries in " + libraryPath); @@ -205,12 +251,12 @@ void SPELLpythonHelper::importUserLibraries( const std::string& libraryPath ) } catch(SPELLcoreException& ex) { - LOG_ERROR("Failed to import user library " + filename + ": " + ex.what()); - PyErr_Clear(); + success = false; + if (errors != "") errors += "\n - "; + errors += "When importing library " + filename + ": " + ex.what(); } } - // Now ensure that all libraries contain all imported names as well for( it = files.begin(); it != end; it++) { @@ -228,8 +274,9 @@ void SPELLpythonHelper::importUserLibraries( const std::string& libraryPath ) } catch(SPELLcoreException& ex) { - LOG_ERROR("Failed to update user library " + filename + ": " + ex.what()); - PyErr_Clear(); + success = false; + if (errors != "") errors += "\n - "; + errors += "When updating library " + filename + ": " + ex.what(); } } @@ -238,9 +285,12 @@ void SPELLpythonHelper::importUserLibraries( const std::string& libraryPath ) } else { - THROW_EXCEPTION("Unable to load USER libraries", "Library path '" + libraryPath + "' is not a directory", SPELL_ERROR_FILESYSTEM); + success = false; + if (errors != "") errors += "\n - "; + errors += "Path '" + libraryPath + "' is not a directory"; } } + return success; } //============================================================================ @@ -320,7 +370,7 @@ void SPELLpythonHelper::importUserLibrary( PyObject* main_dict, const std::strin //============================================================================ PyObject* SPELLpythonHelper::getMainDict() { - PyObject* mainModule = PyImport_AddModule("__main__"); + PyObject* mainModule = PyImport_AddModule(BUILTINS); if (mainModule == NULL) { THROW_EXCEPTION("Cannot access main dictionary", "Unable to access main module", SPELL_ERROR_PYTHON_API ); @@ -335,7 +385,7 @@ PyObject* SPELLpythonHelper::getMainDict() void SPELLpythonHelper::install( PyObject* object, const std::string& name ) { DEBUG("[PYH] Installing object in globals: " + name); - install(object,name,"__main__"); + install(object,name,BUILTINS); } //============================================================================ @@ -343,7 +393,7 @@ void SPELLpythonHelper::install( PyObject* object, const std::string& name ) //============================================================================ void SPELLpythonHelper::install( PyObject* object, const std::string& name, const std::string& module ) { - PyObject* moduleObj = PyImport_AddModule( module.c_str() ); + PyObject* moduleObj = PyImport_ImportModule( module.c_str() ); if (moduleObj == NULL) { THROW_EXCEPTION("Cannot install " + name, "Unable to access module " + module, SPELL_ERROR_PYTHON_API ); @@ -365,9 +415,8 @@ void SPELLpythonHelper::install( PyObject* object, const std::string& name, cons //============================================================================= PyObject* SPELLpythonHelper::callMethod( PyObject* obj, const std::string& method, ... ) { - DEBUG("[PYH] Call method " + method + " on " + PYREPR(obj)); - SPELLsafePythonOperations ops("SPELLpythonHelper::callMethod " + method); + DEBUG("[PYH] Call method " + method + " on " + PYREPR(obj)); // Returns new reference @@ -487,8 +536,10 @@ PyObject* SPELLpythonHelper::getModule( const std::string& module ) moduleObj = PyImport_ImportModule( module.c_str() ); if (moduleObj == NULL || moduleObj == Py_None ) { + checkError(); THROW_EXCEPTION("Unable to get module " + module, "Import failed", SPELL_ERROR_PYTHON_API ); } + Py_INCREF(moduleObj); DEBUG("[PYH] Get module done"); return moduleObj; } @@ -498,7 +549,8 @@ PyObject* SPELLpythonHelper::getModule( const std::string& module ) //============================================================================= void SPELLpythonHelper::initialize() { - m_initialized = false; + if (m_initialized) + return; LOG_INFO("[PYH] Initializing Python"); // Initialize python, builtins, main module, etc. @@ -531,11 +583,16 @@ void SPELLpythonHelper::initialize() } // Initialize the system arguments to empty list (no script is executed) - char** argv = new char*[1]; - argv[0] = (char*) ""; - PySys_SetArgv(1,argv); + + CHAR** argv = new CHAR*[1]; + argv[0] = (CHAR*) L""; + PySys_SetArgv(-1,argv); m_initialized = true; + Py_BEGIN_ALLOW_THREADS}; +// PyEval_ReleaseThread(PyThreadState_Get()); +// PyEval_ReleaseLock(); + } //============================================================================= @@ -543,13 +600,13 @@ void SPELLpythonHelper::initialize() //============================================================================= void SPELLpythonHelper::loadFramework() { - importAllFrom("spell.lang.functions"); - importAllFrom("spell.lang.constants"); - importAllFrom("spell.lang.modifiers"); - importAllFrom("spell.lang.user"); - importAllFrom("spell.lib.adapter.utctime"); - importAllFrom("spell.lib.adapter.file"); - importAllFrom("math"); + updateBuiltinsWith("spell.lang.functions"); + updateBuiltinsWith("spell.lang.constants"); + updateBuiltinsWith("spell.lang.modifiers"); + updateBuiltinsWith("spell.lang.user"); + updateBuiltinsWith("spell.lib.adapter.utctime"); + updateBuiltinsWith("spell.lib.adapter.file"); + updateBuiltinsWith("math"); } //============================================================================= @@ -569,7 +626,7 @@ void SPELLpythonHelper::finalize() void SPELLpythonHelper::addToPath( const std::string& path ) { LOG_INFO("[PYH] Append to python path: " + path); - + SPELLsafePythonOperations ops("SPELLpythonHelper::addToPath"); // Retrieve first the current path std::string libs = ""; PyObject* sys = PySys_GetObject( (char*) "path" ); @@ -582,8 +639,11 @@ void SPELLpythonHelper::addToPath( const std::string& path ) } if (!libs.empty()) libs += ":"; libs += path; - +#if PY_MAJOR_VERSION >= 3 + PySys_SetPath( const_cast(std::wstring(libs.begin(),libs.end()).c_str()) ); +#else PySys_SetPath( const_cast(libs.c_str()) ); +#endif } //============================================================================= @@ -663,36 +723,60 @@ SPELLtime SPELLpythonHelper::evalTime( PyObject* expression ) if (isTime(expression)) { SPELLpyHandle isRel = callMethod(expression,"isRel",NULL); + long secs = 0; + unsigned int msecs = 0; + unsigned int usecs = 0; + SPELLpyHandle h = callMethod(expression,"millisecond",NULL); + msecs = PyLong_AsLong(h.get()); + h = callMethod(expression,"microsecond",NULL); + usecs = PyLong_AsLong(h.get()); if (isRel.get() == Py_True) { SPELLpyHandle pySecs = callMethod(expression,"rel",NULL); - long secs = PyLong_AsLong(pySecs.get()); + if (PyFloat_Check(pySecs.get())) + { + double fsecs = PyFloat_AsDouble(pySecs.get()); + // New reference + SPELLpyHandle pyDoub = PyLong_FromDouble(fsecs); + secs = PyLong_AsLong(pyDoub.get()); + } + else + { + secs = PyLong_AsLong(pySecs.get()); + } DEBUG("[PYH] Evaluate time from python object done"); - return SPELLtime( secs, 0, true ); + //DEBUG("@@@@ Initialized rel values:"); + //DEBUG(" - secs: " + ISTR(secs) ); + //DEBUG(" - msecs: " + ISTR(msecs) ); + //DEBUG(" - usecs: " + ISTR(usecs) ); + return SPELLtime( secs, msecs, usecs, true ); } else { // New reference SPELLpyHandle pySecs = callMethod(expression,"abs",NULL); - int seconds = 0; if (PyFloat_Check(pySecs.get())) { double fsecs = PyFloat_AsDouble(pySecs.get()); // New reference SPELLpyHandle pyDoub = PyLong_FromDouble(fsecs); - seconds = PyLong_AsLong(pyDoub.get()); + secs = PyLong_AsLong(pyDoub.get()); } else { - seconds = PyLong_AsLong(pySecs.get()); + secs = PyLong_AsLong(pySecs.get()); } DEBUG("[PYH] Evaluate time from python object done"); - return SPELLtime( seconds, 0, false ); + //DEBUG("@@@@ Initialized abs values:"); + //DEBUG(" - secs: " + ISTR(secs) ); + //DEBUG(" - msecs: " + ISTR(msecs) ); + //DEBUG(" - usecs: " + ISTR(usecs) ); + return SPELLtime( secs, msecs, usecs, false ); } } else { - THROW_FATAL_EXCEPTION("Unable to evaluate time", "Not a TIME instance", SPELL_ERROR_PYTHON_API ); + return evalTime(PYSSTR(expression)); } // Just to avoid warnings... return SPELLtime(); @@ -764,12 +848,26 @@ PyObject* SPELLpythonHelper::pythonTime( const SPELLtime& time ) //============================================================================= PyThreadState* SPELLpythonHelper::beginSafeAllowThreads() { + // Don't call PyEval_SaveThread if this thread doesn't hold the GIL! This would cause weird CPython crashes. + PyThreadState *hasGIL = PyGILState_GetThisThreadState(); + if (hasGIL == NULL) + return NULL; +#if PY_MAJOR_VERSION >= 3 + // Need to check whether we hold the python lock in py3 as well. + if (!PyGILState_Check()) + return NULL; +#endif PyThreadState *tstate = PyThreadState_GET(); + if (tstate != NULL) { -// std::cerr << "BEGIN ALLOW THREADS ##########################################################" << std::endl; - tstate = PyEval_SaveThread(); -// std::cerr << "FRAME: " << PSTR(tstate->frame) << std::endl; + //std::cerr << "BEGIN ALLOW THREADS ##########################################################" << std::endl; + // Make sure this thread is holding the GIL when trying to call PyEval_SaveThread which requires the GIL + if (hasGIL == tstate) + tstate = PyEval_SaveThread(); + else + return NULL; + //std::cerr << "FRAME: " << PSTR(tstate->frame) << std::endl; } return tstate; } @@ -781,8 +879,8 @@ void SPELLpythonHelper::endSafeAllowThreads( PyThreadState* tstate ) { if (tstate != NULL) { -// std::cerr << "END ALLOW THREADS ############################################################" << std::endl; -// std::cerr << "FRAME: " << PSTR(tstate->frame) << std::endl; + //std::cerr << "END ALLOW THREADS ############################################################" << std::endl; + //std::cerr << "FRAME: " << PSTR(tstate->frame) << std::endl; PyEval_RestoreThread(tstate); } } @@ -792,10 +890,10 @@ void SPELLpythonHelper::endSafeAllowThreads( PyThreadState* tstate ) //============================================================================= PyGILState_STATE SPELLpythonHelper::acquireGIL() { -// std::cerr << "ACQUIRE GIL ##################################################################" << std::endl; + //std::cerr << "ACQUIRE GIL ##################################################################" << std::endl; PyGILState_STATE state = PyGILState_Ensure(); -// std::cerr << "FRAME: " << PSTR(PyEval_GetFrame()) << std::endl; -// std::cerr << "STATE: " << state << std::endl; + //std::cerr << "FRAME: " << PSTR(PyEval_GetFrame()) << std::endl; + //std::cerr << "STATE: " << state << std::endl; return state; } @@ -804,9 +902,9 @@ PyGILState_STATE SPELLpythonHelper::acquireGIL() //============================================================================= void SPELLpythonHelper::releaseGIL( PyGILState_STATE state ) { -// std::cerr << "RELEASE GIL ##################################################################" << std::endl; -// std::cerr << "FRAME: " << PSTR(PyEval_GetFrame()) << std::endl; -// std::cerr << "STATE: " << state << std::endl; + //std::cerr << "RELEASE GIL ##################################################################" << std::endl; + //std::cerr << "FRAME: " << PSTR(PyEval_GetFrame()) << std::endl; + //std::cerr << "STATE: " << state << std::endl; PyGILState_Release(state); } @@ -858,7 +956,7 @@ std::string SPELLpythonHelper::readProcedureFile( const std::string& filename ) //============================================================================= PyCodeObject* SPELLpythonHelper::compile( const std::string& filePath ) { - DEBUG("[PYH] Compiling procedure " + filePath) + DEBUG("[PYH] Compiling procedure " + filePath); // Will hold the AST preprocessed bytecode mod_ty ast; @@ -1001,13 +1099,424 @@ PyCodeObject* SPELLpythonHelper::compileScript( const std::string& source ) return code; } + +#if PY_MAJOR_VERSION >= 3 + +#define OFF(x) offsetof(PyFrameObject, x) + +/* Setter for f_lineno - you can set f_lineno from within a trace function in + * order to jump to a given line of code, subject to some restrictions. Most + * lines are OK to jump to because they don't make any assumptions about the + * state of the stack (obvious because you could remove the line and the code + * would still work without any stack errors), but there are some constructs + * that limit jumping: + * + * o Lines with an 'except' statement on them can't be jumped to, because + * they expect an exception to be on the top of the stack. + * o Lines that live in a 'finally' block can't be jumped from or to, since + * the END_FINALLY expects to clean up the stack after the 'try' block. + * o 'try'/'for'/'while' blocks can't be jumped into because the blockstack + * needs to be set up before their code runs, and for 'for' loops the + * iterator needs to be on the stack. + * o Jumps cannot be made from within a trace function invoked with a + * 'return' or 'exception' event since the eval loop has been exited at + * that time. + */ +static int +frame_setlineno(PyFrameObject *f, PyObject* p_new_lineno, void *Py_UNUSED(ignored)) +{ + int new_lineno = 0; /* The new value of f_lineno */ + long l_new_lineno; + int overflow; + int new_lasti = 0; /* The new value of f_lasti */ + int new_iblock = 0; /* The new value of f_iblock */ + unsigned char *code = NULL; /* The bytecode for the frame... */ + Py_ssize_t code_len = 0; /* ...and its length */ + unsigned char *lnotab = NULL; /* Iterating over co_lnotab */ + Py_ssize_t lnotab_len = 0; /* (ditto) */ + int offset = 0; /* (ditto) */ + int line = 0; /* (ditto) */ + int addr = 0; /* (ditto) */ + int min_addr = 0; /* Scanning the SETUPs and POPs */ + int max_addr = 0; /* (ditto) */ + int delta_iblock = 0; /* (ditto) */ + int min_delta_iblock = 0; /* (ditto) */ + int min_iblock = 0; /* (ditto) */ + int f_lasti_setup_addr = 0; /* Policing no-jump-into-finally */ + int new_lasti_setup_addr = 0; /* (ditto) */ + int blockstack[CO_MAXBLOCKS]; /* Walking the 'finally' blocks */ + int in_finally[CO_MAXBLOCKS]; /* (ditto) */ + int blockstack_top = 0; /* (ditto) */ + unsigned char setup_op = 0; /* (ditto) */ + + if (p_new_lineno == NULL) { + PyErr_SetString(PyExc_AttributeError, "cannot delete attribute"); + return -1; + } + /* f_lineno must be an integer. */ + if (!PyLong_CheckExact(p_new_lineno)) { + PyErr_SetString(PyExc_ValueError, + "lineno must be an integer"); + return -1; + } + + /* Upon the 'call' trace event of a new frame, f->f_lasti is -1 and + * f->f_trace is NULL, check first on the first condition. + * Forbidding jumps from the 'call' event of a new frame is a side effect + * of allowing to set f_lineno only from trace functions. */ + if (f->f_lasti == -1) { + PyErr_Format(PyExc_ValueError, + "can't jump from the 'call' trace event of a new frame"); + return -1; + } + + /* You can only do this from within a trace function, not via + * _getframe or similar hackery. */ +// if (!f->f_trace) { +// PyErr_Format(PyExc_ValueError, +// "f_lineno can only be set by a trace function"); +// return -1; +// } + + /* Forbid jumps upon a 'return' trace event (except after executing a + * YIELD_VALUE or YIELD_FROM opcode, f_stacktop is not NULL in that case) + * and upon an 'exception' trace event. + * Jumps from 'call' trace events have already been forbidden above for new + * frames, so this check does not change anything for 'call' events. */ + if (f->f_stacktop == NULL) { + PyErr_SetString(PyExc_ValueError, + "can only jump from a 'line' trace event"); + return -1; + } + + /* Fail if the line comes before the start of the code block. */ + l_new_lineno = PyLong_AsLongAndOverflow(p_new_lineno, &overflow); + if (overflow +#if SIZEOF_LONG > SIZEOF_INT + || l_new_lineno > INT_MAX + || l_new_lineno < INT_MIN +#endif + ) { + PyErr_SetString(PyExc_ValueError, + "lineno out of range"); + return -1; + } + new_lineno = (int)l_new_lineno; + + if (new_lineno < f->f_code->co_firstlineno) { + PyErr_Format(PyExc_ValueError, + "line %d comes before the current code block", + new_lineno); + return -1; + } + else if (new_lineno == f->f_code->co_firstlineno) { + new_lasti = 0; + new_lineno = f->f_code->co_firstlineno; + } + else { + /* Find the bytecode offset for the start of the given + * line, or the first code-owning line after it. */ + char *tmp; + PyBytes_AsStringAndSize(f->f_code->co_lnotab, + &tmp, &lnotab_len); + lnotab = (unsigned char *) tmp; + addr = 0; + line = f->f_code->co_firstlineno; + new_lasti = -1; + for (offset = 0; offset < lnotab_len; offset += 2) { + addr += lnotab[offset]; + line += (signed char)lnotab[offset+1]; + if (line >= new_lineno) { + new_lasti = addr; + new_lineno = line; + break; + } + } + } + + /* If we didn't reach the requested line, return an error. */ + if (new_lasti == -1) { + PyErr_Format(PyExc_ValueError, + "line %d comes after the current code block", + new_lineno); + return -1; + } + + /* We're now ready to look at the bytecode. */ + PyBytes_AsStringAndSize(f->f_code->co_code, (char **)&code, &code_len); + + /* The trace function is called with a 'return' trace event after the + * execution of a yield statement. */ + assert(f->f_lasti != -1); + if (code[f->f_lasti] == YIELD_VALUE || code[f->f_lasti] == YIELD_FROM) { + PyErr_SetString(PyExc_ValueError, + "can't jump from a yield statement"); + return -1; + } + + min_addr = Py_MIN(new_lasti, f->f_lasti); + max_addr = Py_MAX(new_lasti, f->f_lasti); + + /* You can't jump onto a line with an 'except' statement on it - + * they expect to have an exception on the top of the stack, which + * won't be true if you jump to them. They always start with code + * that either pops the exception using POP_TOP (plain 'except:' + * lines do this) or duplicates the exception on the stack using + * DUP_TOP (if there's an exception type specified). See compile.c, + * 'com_try_except' for the full details. There aren't any other + * cases (AFAIK) where a line's code can start with DUP_TOP or + * POP_TOP, but if any ever appear, they'll be subject to the same + * restriction (but with a different error message). */ + if (code[new_lasti] == DUP_TOP || code[new_lasti] == POP_TOP) { + PyErr_SetString(PyExc_ValueError, + "can't jump to 'except' line as there's no exception"); + return -1; + } + + /* You can't jump into or out of a 'finally' block because the 'try' + * block leaves something on the stack for the END_FINALLY to clean + * up. So we walk the bytecode, maintaining a simulated blockstack. + * When we reach the old or new address and it's in a 'finally' block + * we note the address of the corresponding SETUP_FINALLY. The jump + * is only legal if neither address is in a 'finally' block or + * they're both in the same one. 'blockstack' is a stack of the + * bytecode addresses of the SETUP_X opcodes, and 'in_finally' tracks + * whether we're in a 'finally' block at each blockstack level. */ + f_lasti_setup_addr = -1; + new_lasti_setup_addr = -1; + memset(blockstack, '\0', sizeof(blockstack)); + memset(in_finally, '\0', sizeof(in_finally)); + blockstack_top = 0; + for (addr = 0; addr < code_len; addr += sizeof(_Py_CODEUNIT)) { + unsigned char op = code[addr]; + switch (op) { + case SETUP_LOOP: + case SETUP_EXCEPT: + case SETUP_FINALLY: + case SETUP_WITH: + case SETUP_ASYNC_WITH: + blockstack[blockstack_top++] = addr; + in_finally[blockstack_top-1] = 0; + break; + + case POP_BLOCK: + assert(blockstack_top > 0); + setup_op = code[blockstack[blockstack_top-1]]; + if (setup_op == SETUP_FINALLY || setup_op == SETUP_WITH + || setup_op == SETUP_ASYNC_WITH) { + in_finally[blockstack_top-1] = 1; + } + else { + blockstack_top--; + } + break; + + case END_FINALLY: + /* Ignore END_FINALLYs for SETUP_EXCEPTs - they exist + * in the bytecode but don't correspond to an actual + * 'finally' block. (If blockstack_top is 0, we must + * be seeing such an END_FINALLY.) */ + if (blockstack_top > 0) { + setup_op = code[blockstack[blockstack_top-1]]; + if (setup_op == SETUP_FINALLY || setup_op == SETUP_WITH + || setup_op == SETUP_ASYNC_WITH) { + blockstack_top--; + } + } + break; + } + + /* For the addresses we're interested in, see whether they're + * within a 'finally' block and if so, remember the address + * of the SETUP_FINALLY. */ + if (addr == new_lasti || addr == f->f_lasti) { + int i = 0; + int setup_addr = -1; + for (i = blockstack_top-1; i >= 0; i--) { + if (in_finally[i]) { + setup_addr = blockstack[i]; + break; + } + } + + if (setup_addr != -1) { + if (addr == new_lasti) { + new_lasti_setup_addr = setup_addr; + } + + if (addr == f->f_lasti) { + f_lasti_setup_addr = setup_addr; + } + } + } + } + + /* Verify that the blockstack tracking code didn't get lost. */ + //assert(blockstack_top == 0); + if (blockstack_top != 0) { + PyErr_SetString(PyExc_ValueError, + "can't jump blockstack_top pointer got lost"); + return -1; + } + + /* After all that, are we jumping into / out of a 'finally' block? */ + if (new_lasti_setup_addr != f_lasti_setup_addr) { + PyErr_SetString(PyExc_ValueError, + "can't jump into or out of a 'finally' block"); + return -1; + } + + + /* Police block-jumping (you can't jump into the middle of a block) + * and ensure that the blockstack finishes up in a sensible state (by + * popping any blocks we're jumping out of). We look at all the + * blockstack operations between the current position and the new + * one, and keep track of how many blocks we drop out of on the way. + * By also keeping track of the lowest blockstack position we see, we + * can tell whether the jump goes into any blocks without coming out + * again - in that case we raise an exception below. */ + delta_iblock = 0; + for (addr = min_addr; addr < max_addr; addr += sizeof(_Py_CODEUNIT)) { + unsigned char op = code[addr]; + switch (op) { + case SETUP_LOOP: + case SETUP_EXCEPT: + case SETUP_FINALLY: + case SETUP_WITH: + case SETUP_ASYNC_WITH: + delta_iblock++; + break; + + case POP_BLOCK: + delta_iblock--; + break; + } + + min_delta_iblock = Py_MIN(min_delta_iblock, delta_iblock); + } + + /* Derive the absolute iblock values from the deltas. */ + min_iblock = f->f_iblock + min_delta_iblock; + if (new_lasti > f->f_lasti) { + /* Forwards jump. */ + new_iblock = f->f_iblock + delta_iblock; + } + else { + /* Backwards jump. */ + new_iblock = f->f_iblock - delta_iblock; + } + + /* Are we jumping into a block? */ + if (new_iblock > min_iblock) { + PyErr_SetString(PyExc_ValueError, + "can't jump into the middle of a block"); + return -1; + } + + /* Pop any blocks that we're jumping out of. */ + while (f->f_iblock > new_iblock) { + PyTryBlock *b = &f->f_blockstack[--f->f_iblock]; + while ((f->f_stacktop - f->f_valuestack) > b->b_level) { + PyObject *v = (*--f->f_stacktop); + Py_DECREF(v); + } + if (b->b_type == SETUP_FINALLY && + code[b->b_handler] == WITH_CLEANUP_START) + { + /* Pop the exit function. */ + PyObject *v = (*--f->f_stacktop); + Py_DECREF(v); + } + } + + /* Finally set the new f_lineno and f_lasti and return OK. */ + f->f_lineno = new_lineno; + f->f_lasti = new_lasti; + return 0; +} + + +#endif + //============================================================================= // METHOD : SPELLpythonHelper::setNewLine(); //============================================================================= bool SPELLpythonHelper::setNewLine( PyFrameObject* frame, const int& new_lineno, const int& new_lasti ) { LOG_WARN("[PYH] Changing line number on frame " + PYCREPR(frame)); + LOG_WARN("[PYH] new_lineno = " + ISTR(new_lineno) + " new_lasti = " + ISTR(new_lasti)); + +#if PY_MAJOR_VERSION >= 3 + PyObject* line = PyLong_FromLong(new_lineno); + int success = -1; + // In case frame->f_stacktop is NULL and frame->f_back is not NULL, + // (case when just returned from a function call) + // set current frame to the previous frame + // Otherwise, keep using current frame + // (case of a return into the root frame for example) + if (frame->f_stacktop == NULL && frame->f_back != NULL) + frame = frame->f_back; + success = frame_setlineno(frame, line, NULL); + PyObject* err = PyErr_Occurred(); + if (err != NULL) + { + std::cerr << std::endl << "===============================" << std::endl; + //PyErr_Print(); + PyObject* ptype; + PyObject* pvalue; + PyObject* ptraceback; + PyErr_Fetch( &ptype, &pvalue, &ptraceback ); + std::cerr << "TYPE : " << PYREPR(ptype) << std::endl; + std::cerr << "VALUE: " << PYREPR(pvalue) << std::endl; + if (ptraceback) + { + std::cerr << "Traceback: " << std::endl; + PyTracebackObject* traceback = (PyTracebackObject*) ptraceback; + while(traceback != NULL) + { + std::cerr << "at " << PYREPR(traceback->tb_frame->f_code->co_filename) << ":" << traceback->tb_lineno << std::endl; + traceback = traceback->tb_next; + } + } + + std::cerr << "===============================" << std::endl << std::endl; + // Parse the exception to give better information about the error on python side + PyObject* spellException = SPELLpythonHelper::instance().getObject("spell.lib.exception", "SpellException"); + std::string excType = PYREPR(ptype); + if (PyObject_IsInstance( pvalue, spellException )) + { + PyObject* msg = PyObject_GetAttrString( pvalue, "message" ); + PyObject* rea = PyObject_GetAttrString( pvalue, "reason" ); + std::string message = ""; + std::string reason = ""; + if (msg != NULL) + { + message = PYSSTR(msg); + } + if (rea != NULL ) + { + reason = PYSSTR(rea); + } + /** \todo decide the fatal flag value */ + std::cerr << "RAISING INTERNAL DRIVER EXCEPTION" << std::endl; + PyErr_Clear(); + THROW_EXCEPTION("Cannot set new line", reason, SPELL_DRIVER_ERROR ); + } + else if ( PYREPR(pvalue) == "EXECUTION ABORTED" || PYREPR(pvalue) == "EXECUTION TERMINATED" ) + { + // Do not raise exceptions if the detected exception is an aborted or finished internal exception + PyErr_Clear(); + return false; + } + else + { + std::cerr << "RAISING PYTHON ERROR EXCEPTION" << std::endl; + PyErr_Clear(); + THROW_EXCEPTION("Cannot set new line", PYSSTR(pvalue), SPELL_ERROR_PYTHON_API ); + } + } +#else ///////////////////////////////////////////////////////// // THIS IS NASTY: copied from Python 2.5 implementation ///////////////////////////////////////////////////////// @@ -1051,10 +1560,10 @@ bool SPELLpythonHelper::setNewLine( PyFrameObject* frame, const int& new_lineno, * cases (AFAIK) where a line's code can start with DUP_TOP or * POP_TOP, but if any ever appear, they'll be subject to the same * restriction (but with a different error message). */ - if (code[new_lasti] == DUP_TOP || code[new_lasti] == POP_TOP) - { - THROW_EXCEPTION("Cannot set new line", "Cannot go inside a try/except block", SPELL_ERROR_PYTHON_API); - } + //if (code[new_lasti] == DUP_TOP || code[new_lasti] == POP_TOP) + //{ + // THROW_EXCEPTION("Cannot set new line", "Cannot go inside a try/except block", SPELL_ERROR_PYTHON_API); + //} /* You can't jump into or out of a 'finally' block because the 'try' * block leaves something on the stack for the END_FINALLY to clean @@ -1215,6 +1724,7 @@ bool SPELLpythonHelper::setNewLine( PyFrameObject* frame, const int& new_lineno, /* Finally set the new f_lineno and f_lasti and return OK. */ frame->f_lineno = new_lineno; frame->f_lasti = new_lasti; +#endif LOG_WARN("[PYH] Set frame lineno " + ISTR(new_lineno) + ":" + ISTR(new_lasti)); return true; } @@ -1230,6 +1740,7 @@ PyFrameObject* SPELLpythonHelper::createFrame( const std::string& filename, PyCo PyObject* mainModule = PyImport_AddModule("__main__"); + if (mainModule == NULL) { THROW_EXCEPTION("Unable to create frame", "Cannot access main module", SPELL_ERROR_PYTHON_API ); @@ -1256,11 +1767,32 @@ PyFrameObject* SPELLpythonHelper::createFrame( const std::string& filename, PyCo return NULL; } } + DEBUG("[PYH] Frame creation"); - frame = PyFrame_New( PyThreadState_Get(), code, mainDict, mainDict ); + + frame = PyFrame_New( PyThreadState_Get(), code,mainDict , NULL ); Py_INCREF(frame); return frame; } +//============================================================================= +// METHOD : SPELLpythonHelper::createFrame(); +//============================================================================= +PyFrameObject* SPELLpythonHelper::createFrameFromFunction( const std::string& filename, PyObject* func ) +{ + DEBUG("[PYH] Creating frame for code " + PYCREPR(func)); + + PyFrameObject* frame = NULL; + + PyCodeObject* code = (PyCodeObject*)PyFunction_GetCode(func); + PyObject* globals = PyFunction_GetGlobals(func); + Py_INCREF(globals); + Py_INCREF(code); + frame = PyFrame_New( PyThreadState_Get(), code,globals , NULL); + + Py_INCREF(frame); + checkError(); + return frame; +} //============================================================================= // METHOD : SPELLpythonHelper::checkError @@ -1290,6 +1822,7 @@ void SPELLpythonHelper::checkError() traceback = traceback->tb_next; } } + std::cerr << "===============================" << std::endl << std::endl; // Parse the exception to give better information about the error on python side PyObject* spellException = SPELLpythonHelper::instance().getObject("spell.lib.exception", "SpellException"); diff --git a/lib/SPELL_UTIL/src/SPELLtime.C b/lib/SPELL_UTIL/src/SPELLtime.C index 71015be..3c9a666 100644 --- a/lib/SPELL_UTIL/src/SPELLtime.C +++ b/lib/SPELL_UTIL/src/SPELLtime.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the time object // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -47,7 +47,7 @@ SPELLtime::SPELLtime() //============================================================================= SPELLtime::SPELLtime( unsigned long secs, bool delta ) { - set(secs,0,delta); + set(secs,0,0,delta); } //============================================================================= @@ -55,7 +55,15 @@ SPELLtime::SPELLtime( unsigned long secs, bool delta ) //============================================================================= SPELLtime::SPELLtime( unsigned long secs, unsigned int msecs, bool delta ) { - set(secs,msecs,delta); + set(secs,msecs,0,delta); +} + +//============================================================================= +// CONSTRUCTOR: SPELLtime::SPELLtime +//============================================================================= +SPELLtime::SPELLtime( unsigned long secs, unsigned int msecs, unsigned int usecs, bool delta ) +{ + set(secs,msecs,usecs,delta); } //============================================================================= @@ -63,7 +71,7 @@ SPELLtime::SPELLtime( unsigned long secs, unsigned int msecs, bool delta ) //============================================================================= SPELLtime::SPELLtime( const SPELLtime& other ) { - set(other.m_secs,other.m_msecs,other.m_delta); + set(other.m_secs,other.m_msecs,other.m_usecs,other.m_delta); } //============================================================================= @@ -83,6 +91,7 @@ SPELLtime& SPELLtime::operator=( const SPELLtime& other ) { m_secs = other.m_secs; m_msecs = other.m_msecs; + m_usecs = other.m_usecs; m_delta = other.m_delta; } // by convention, always return this @@ -95,7 +104,12 @@ SPELLtime& SPELLtime::operator=( const SPELLtime& other ) SPELLtime SPELLtime::operator+(const SPELLtime& other) { SPELLtime result(0,true); - result.set( m_secs + other.m_secs, m_msecs + other.m_msecs, m_delta && other.m_delta ); + // Check for overflow + unsigned long secs = m_secs + other.m_secs; + unsigned int usecs = m_usecs + other.m_usecs; + secs += usecs / (1000*1000); + usecs %= (1000*1000); + result.set( secs, usecs/1000, usecs, m_delta && other.m_delta ); return result; } @@ -104,7 +118,12 @@ SPELLtime SPELLtime::operator+(const SPELLtime& other) //============================================================================= SPELLtime& SPELLtime::operator+=(SPELLtime& other) { - set( m_secs + other.m_secs, m_msecs + other.m_msecs, m_delta && other.m_delta ); + // Check for overflow + unsigned long secs = m_secs + other.m_secs; + unsigned int usecs = m_usecs + other.m_usecs; + secs += usecs / (1000*1000); + usecs %= (1000*1000); + set( secs, usecs/1000, usecs, m_delta && other.m_delta ); return *this; } @@ -117,7 +136,8 @@ SPELLtime SPELLtime::operator-(const SPELLtime& other) bool delta = (m_delta && other.m_delta) || (!m_delta && !other.m_delta); long secs = m_secs - other.m_secs; int msecs = m_msecs - other.m_msecs; - result.set( (secs>0)? secs: 0, (msecs>0)? msecs: 0, delta ); + int usecs = m_usecs - other.m_usecs; + result.set( (secs>0)? secs: 0, (msecs>0)? msecs: 0, (usecs>0)? usecs: 0, delta ); return result; } @@ -129,7 +149,8 @@ SPELLtime& SPELLtime::operator-=(SPELLtime& other) bool delta = (m_delta && other.m_delta) || (!m_delta && !other.m_delta); long secs = m_secs - other.m_secs; int msecs = m_msecs - other.m_msecs; - set( (secs>0)? secs: 0, (msecs>0)? msecs: 0, delta ); + int usecs = m_usecs - other.m_usecs; + set( (secs>0)? secs: 0, (msecs>0)? msecs: 0, (usecs>0)? usecs: 0, delta ); return *this; } @@ -142,6 +163,10 @@ bool SPELLtime::operator<( const SPELLtime& other ) const if (m_secs == other.m_secs) { if (m_msecs < other.m_msecs) return true; + if (m_msecs == other.m_msecs) + { + if (m_usecs < other.m_usecs) return true; + } } return false; } @@ -155,6 +180,10 @@ bool SPELLtime::operator>( const SPELLtime& other ) const if (m_secs == other.m_secs) { if (m_msecs > other.m_msecs) return true; + if (m_msecs == other.m_msecs) + { + if (m_usecs > other.m_usecs) return true; + } } return false; } @@ -178,25 +207,25 @@ bool SPELLtime::operator<( const unsigned long& secs ) const } //============================================================================= -// METHOD: SPELLtime::operator< +// METHOD: SPELLtime::operator== //============================================================================= bool SPELLtime::operator==( const SPELLtime& other ) const { - if (m_secs == other.m_secs) + if (m_secs == other.m_secs && m_msecs == other.m_msecs && m_usecs == other.m_usecs) { - if (m_msecs == other.m_msecs) return true; + return true; } return false; } //============================================================================= -// METHOD: SPELLtime::operator< +// METHOD: SPELLtime::operator== //============================================================================= bool SPELLtime::operator==( const unsigned long& secs ) const { if (m_secs == secs) { - if (m_msecs == 0) return true; + if (m_msecs == 0 && m_usecs == 0) return true; } return false; } @@ -206,10 +235,14 @@ bool SPELLtime::operator==( const unsigned long& secs ) const //============================================================================= bool SPELLtime::operator>=( const SPELLtime& other ) const { - if (m_secs >= other.m_secs) return true; + if (m_secs > other.m_secs ) return true; if (m_secs == other.m_secs) { - if (m_msecs >= other.m_msecs) return true; + if (m_msecs > other.m_msecs) return true; + if (m_msecs == other.m_msecs) + { + if (m_usecs >= other.m_usecs) return true; + } } return false; } @@ -219,10 +252,14 @@ bool SPELLtime::operator>=( const SPELLtime& other ) const //============================================================================= bool SPELLtime::operator<=( const SPELLtime& other ) const { - if (m_secs <= other.m_secs) return true; + if (m_secs < other.m_secs) return true; if (m_secs == other.m_secs) { - if (m_msecs <= other.m_msecs) return true; + if (m_msecs < other.m_msecs) return true; + if (m_msecs == other.m_msecs) + { + if (m_usecs <= other.m_usecs) return true; + } } return false; } @@ -232,7 +269,7 @@ bool SPELLtime::operator<=( const SPELLtime& other ) const //============================================================================= std::string SPELLtime::toString() const { - if (m_secs==0) + if (m_secs==0 && m_msecs==0 && m_usecs==0) { return "0"; } @@ -245,6 +282,7 @@ std::string SPELLtime::toString() const if (seconds<60) // Very short date format { timeStr = ISTR(seconds); + timeStr += getMicrosecondsSuffix(); } else { @@ -267,6 +305,8 @@ std::string SPELLtime::toString() const //Seconds if ( seconds < 10 ) timeStr += "0"; timeStr += ISTR(seconds); + //Milliseconds+Microseconds + timeStr += getMicrosecondsSuffix(); } } //Absolute date e.g. 2009-12-07 16:55:12 @@ -274,7 +314,7 @@ std::string SPELLtime::toString() const { time_t seconds = m_secs; struct tm* ptm = localtime(&seconds); - if (ptm==NULL) + if (ptm==NULL && m_msecs==0 && m_usecs==0) { return "\?\?\?\?-\?\?-\?\? \?\?:\?\?:\?\?"; } @@ -294,16 +334,19 @@ std::string SPELLtime::toString() const //Seconds if ( ptm->tm_sec < 10 ) timeStr += "0"; timeStr += ISTR(ptm->tm_sec); + //Milliseconds+Microseconds + timeStr += getMicrosecondsSuffix(); } return timeStr; } + //============================================================================= // METHOD: SPELLtime::toString //============================================================================= std::string SPELLtime::toTIMEString() const { - if (m_secs==0) + if (m_secs==0 && m_msecs==0 && m_usecs==0) { return "+000 00:00:00"; } @@ -336,13 +379,15 @@ std::string SPELLtime::toTIMEString() const //Seconds if ( seconds < 10 ) timeStr += "0"; timeStr += ISTR(seconds); + //Milliseconds+Microseconds + timeStr += getMicrosecondsSuffix(); } //Absolute date e.g. e.g. 2009-12-07:16:55:12 else { time_t seconds = m_secs; struct tm* ptm = localtime(&seconds); - if (ptm==NULL) + if (ptm==NULL && m_msecs==0 && m_usecs==0) { return "\?\?\?\?-\?\?-\?\? \?\?:\?\?:\?\?"; } @@ -353,7 +398,7 @@ std::string SPELLtime::toTIMEString() const if (month.length()==1) month = "0" + month; std::string day = ISTR(ptm->tm_mday); if (day.length()==1) day = "0" + day; - timeStr += month + "-" + day + ":"; + timeStr += month + "-" + day + " "; //Hours if ( ptm->tm_hour < 10 ) timeStr += "0"; @@ -364,6 +409,8 @@ std::string SPELLtime::toTIMEString() const //Seconds if ( ptm->tm_sec < 10 ) timeStr += "0"; timeStr += ISTR(ptm->tm_sec); + //Milliseconds+Microseconds + timeStr += getMicrosecondsSuffix(); } return timeStr; } @@ -374,7 +421,7 @@ std::string SPELLtime::toTIMEString() const void SPELLtime::setCurrent() { SPELLutils::SPELLtimeDesc time = SPELLutils::getSystemTime(); - set( time.seconds, time.useconds/1000, false ); + set( time.seconds, time.useconds/1000, time.useconds%1000, false ); } //============================================================================= @@ -382,20 +429,46 @@ void SPELLtime::setCurrent() //============================================================================= void SPELLtime::set( unsigned long secs, unsigned int msecs ) { - set(secs,msecs,true); + set(secs,msecs,0,true); } //============================================================================= // METHOD: SPELLtime::set //============================================================================= -void SPELLtime::set( unsigned long secs, unsigned int msecs, bool delta ) +void SPELLtime::set( unsigned long secs, unsigned int msecs, unsigned int usecs ) +{ + set(secs,msecs,usecs,true); +} + +//============================================================================= +// METHOD: SPELLtime::set +//============================================================================= +void SPELLtime::set( unsigned long secs, unsigned int msecs, unsigned int usecs, bool delta ) { m_secs = secs; m_msecs = msecs; + m_usecs = usecs; m_delta = delta; - //DEBUG("[TIME] Set time: " + ISTR(m_secs) + ", " + ISTR(m_msecs) + ", " + BSTR(m_delta)) + //DEBUG("[TIME] Set time: " + ISTR(m_secs) + ", " + ISTR(m_msecs) + ", " + ISTR(m_usecs) + ", " + BSTR(m_delta)) } +//============================================================================= +// METHOD: SPELLtime::getMicrosecondsSuffix +//============================================================================= +std::string SPELLtime::getMicrosecondsSuffix() const { + std::stringstream ss; + if ( m_msecs == 0 && m_usecs == 0 ) + { + return ""; + } + ss << "."; + ss << std::setfill('0') << std::setw(3) << m_msecs; + if ( m_usecs%1000 != 0) { + ss << std::setfill('0') << std::setw(3) << m_usecs%1000; + } + return ss.str(); +}; + //============================================================================= // STATIC: SPELLticker::tickIn //============================================================================= diff --git a/lib/SPELL_UTIL/src/SPELLutils.C b/lib/SPELL_UTIL/src/SPELLutils.C index 47a0352..36770b5 100644 --- a/lib/SPELL_UTIL/src/SPELLutils.C +++ b/lib/SPELL_UTIL/src/SPELLutils.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the utilities // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -29,6 +29,7 @@ #include "SPELL_UTIL/SPELLbase.H" #include "SPELL_UTIL/SPELLerror.H" #include "SPELL_UTIL/SPELLpythonMonitors.H" +#include "SPELL_IPC/SPELLipcError.H" // Project includes -------------------------------------------------------- #include "SPELL_WRP/SPELLpyHandle.H" // System includes --------------------------------------------------------- @@ -36,8 +37,6 @@ static int s_timeFormat = TIME_FORMAT_DEFAULT; - - //============================================================================ // FUNCTION : PYSIZE //============================================================================ @@ -146,7 +145,7 @@ std::string SPELLutils::pyObjectRepr( PyObject* obj ) SPELLpyHandle str = PyObject_Repr(obj); if (str.get() != NULL) { - result = PyString_AsString(str.get()); + result = std::string(PyString_AsString(str.get())); } return result; } @@ -186,7 +185,6 @@ std::map > SPELLutils::tokenizeData( const std::s std::vector value; unsigned int keyLength; unsigned int valueLength; - unsigned int byte; unsigned int pos; for( pos = 0 ; pos < data.size() ; ) @@ -201,6 +199,10 @@ std::map > SPELLutils::tokenizeData( const std::s valueLength += (((unsigned char) data[pos+2]) << 8); valueLength += (unsigned char) data[pos+3]; pos += 4; + if (pos + valueLength > data.size()) + { + throw SPELLipcError( "Peer has sent bad data disconnecting!", 0); + } value.assign(data.begin() + pos, data.begin() + pos + valueLength); pos += valueLength; @@ -428,6 +430,20 @@ bool SPELLutils::isFile( const std::string& path ) return isFile; } +//============================================================================ +// FUNCTION : SPELLutils::isLink +//============================================================================ +bool SPELLutils::isLink( const std::string& path ) +{ + struct stat buf; + bool isLink = false; + if (stat(path.c_str(),&buf) == 0) + { + isLink = S_ISLNK( buf.st_mode ); + } + return isLink; +} + //============================================================================ // FUNCTION : SPELLutils::fileCopy //============================================================================ @@ -448,6 +464,18 @@ void SPELLutils::copyFile( const std::string& sourcePath, const std::string& tar outfile.close(); } +bool compare_nocase (const std::string& first, const std::string& second) +{ + unsigned int i=0; + while ( (itolower(second[i])) return false; + ++i; + } + return ( first.length() < second.length() ); +} + //============================================================================ // FUNCTION : SPELLutils::getFilesInDir //============================================================================ @@ -455,6 +483,7 @@ std::list SPELLutils::getFilesInDir( const std::string& path ) { DIR *dirp; struct dirent *dp; + char buffer[512]; std::list files; @@ -463,7 +492,29 @@ std::list SPELLutils::getFilesInDir( const std::string& path ) { while ( (dp = readdir(dirp)) != NULL ) { - if (dp->d_type & DT_REG) + if (dp->d_type == DT_LNK) + { + // Check the target of the link + std::string abspath = path + "/" + dp->d_name; + ssize_t fwd = readlink( abspath.c_str(), buffer, 512); + // Ignore it in case of errors + if (fwd<0) + { + LOG_ERROR("Unable to follow link " + std::string(dp->d_name)); + continue; + } + std::string target_name(buffer,fwd); + if (target_name.find("/") != 0) // root path target + { + target_name = path + "/" + target_name; + } + LOG_WARN("Resolving file link " + std::string(dp->d_name) + " to " + target_name); + if (SPELLutils::isFile(target_name)) + { + files.push_back(dp->d_name); + } + } + else if (dp->d_type == DT_REG) { files.push_back(dp->d_name); } @@ -484,6 +535,8 @@ std::list SPELLutils::getFilesInDir( const std::string& path ) { LOG_ERROR("Failed to open directory '" + path + "' to read"); } + files.sort(compare_nocase); + return files; } @@ -494,10 +547,11 @@ std::list SPELLutils::getSubdirs( const std::string& path ) { DIR* dirp; struct dirent *dp; + char buffer[512]; std::string dot = "."; std::string pdot = ".."; - std::string svn = ".svn"; + std::list subdirs; dirp = opendir( path.c_str() ); @@ -505,23 +559,45 @@ std::list SPELLutils::getSubdirs( const std::string& path ) { while ( (dp = readdir(dirp)) != NULL ) { - if (dp->d_type & DT_DIR) + std::string fname = dp->d_name; + if ( fname.find(".") == 0 ) + continue; // discard ., .., all hidden directories including .svn and .git + + if (dp->d_type == DT_LNK) { - if ( dp->d_name != dot && dp->d_name != pdot && dp->d_name != svn ) + // Check the target of the link + std::string abspath = path + "/" + dp->d_name; + LOG_WARN("Found link " + abspath); + + ssize_t fwd = readlink( abspath.c_str(), buffer, 512); + // Ignore it in case of errors + if (fwd<0) + { + LOG_ERROR("Unable to follow link " + std::string(dp->d_name)); + continue; + } + std::string target_name(buffer,fwd); + if (target_name.find("/") != 0) // root path target + { + target_name = path + "/" + target_name; + } + LOG_WARN("Resolving directory link " + std::string(dp->d_name) + " to " + target_name); + if (SPELLutils::isDirectory(target_name)) { subdirs.push_back(dp->d_name); } } + else if (dp->d_type & DT_DIR) + { + subdirs.push_back(dp->d_name); + } // IMPORTANT: in some filesystems, the type may be impossible // to get with readdir. In these cases we need to use stat(). else if (dp->d_type == DT_UNKNOWN) { if ( isDirectory( path + PATH_SEPARATOR + std::string(dp->d_name) ) ) { - if ( dp->d_name != dot && dp->d_name != pdot && dp->d_name != svn ) - { - subdirs.push_back(dp->d_name); - } + subdirs.push_back(dp->d_name); } } // Ignore other types like symbolic links, sockets, etc. @@ -665,9 +741,7 @@ void SPELLutils::showEnvironment() envVar("SPELL_COTS"); envVar("SPELL_DATA"); envVar("SPELL_CONFIG"); - envVar("SPELL_SYS_DATA"); envVar("SPELL_LOG"); - envVar("SPELL_PROCS"); std::cerr << "==============================================================================" << std::endl; } @@ -771,7 +845,11 @@ void SPELLutils::dumpInterpreterInfo( const std::string& id ) dumpFile << "Modules : " << PYSIZE(currIS->modules) << " items." << std::endl; dumpFile << "Sysdict : " << PYSIZE(currIS->sysdict) << " items." << std::endl; dumpFile << "Builtins : " << PYSIZE(currIS->builtins) << " items." << std::endl; +#if PY_MAJOR_VERSION >= 3 + +#else dumpFile << "Reloading : " << PYSIZE(currIS->modules_reloading) << " items." << std::endl; +#endif dumpFile << "Search path : " << PYSIZE(currIS->codec_search_path) << " items." << std::endl; dumpFile << "Search cache: " << PYSIZE(currIS->codec_search_cache) << " items." << std::endl; dumpFile << "Error regst.: " << PYSIZE(currIS->codec_error_registry) << " items." << std::endl; @@ -825,11 +903,22 @@ void SPELLutils::dumpThreadStateInfo( const std::string& id, PyThreadState* ts, dumpFile << "Current exc. type : " << PYREPR(ts->curexc_type) << std::endl; dumpFile << "Current exc. value: " << PYREPR(ts->curexc_value) << std::endl; dumpFile << "Current exc. tback: " << PYREPR(ts->curexc_traceback) << std::endl; +#if PY_MAJOR_VERSION >= 3 + if (ts->exc_info) + { + dumpFile << "Exception type : " << PYREPR(ts->exc_info->exc_type) << std::endl; + dumpFile << "Exception value : " << PYREPR(ts->exc_info->exc_value) << std::endl; + dumpFile << "Exception tback : " << PYREPR(ts->exc_info->exc_traceback) << std::endl; + } +#else dumpFile << "Exception type : " << PYREPR(ts->exc_type) << std::endl; dumpFile << "Exception value : " << PYREPR(ts->exc_value) << std::endl; dumpFile << "Exception tback : " << PYREPR(ts->exc_traceback) << std::endl; +#endif dumpFile << "Main dict : " << PYSIZE(ts->dict) << " items." << std::endl; +#if PY_MAJOR_VERSION < 3 dumpFile << "Tick counter : " << ts->tick_counter << std::endl; +#endif dumpFile << "GIL state counter : " << ts->gilstate_counter << std::endl; dumpFile << "Async. exception : " << PYREPR(ts->async_exc) << std::endl; dumpFile << "Thread id : " << ts->thread_id << std::endl; @@ -866,7 +955,9 @@ void SPELLutils::dumpFrameInfo( const std::string& id, PyFrameObject* frame, int dumpFile << "--------------------------------------" << std::endl; dumpFile << "Address : " << PSTR(frame) << std::endl; dumpFile << "Next address : " << PSTR(frame->f_back) << std::endl; +#if PY_MAJOR_VERSION < 3 dumpFile << "Thread state address : " << PSTR(frame->f_tstate) << std::endl; +#endif dumpFile << "Last instruction : " << frame->f_lasti << std::endl; dumpFile << "Last line : " << frame->f_lineno << std::endl; dumpFile << "Try blocks count : " << frame->f_iblock << std::endl; @@ -875,9 +966,11 @@ void SPELLutils::dumpFrameInfo( const std::string& id, PyFrameObject* frame, int dumpFile << "Stack top : " << PSTR(frame->f_stacktop) << std::endl; dumpFile << "Stack count : " << (frame->f_stacktop - frame->f_valuestack) << std::endl; dumpFile << "Fast locals : " << (frame->f_code->co_nlocals-1) << std::endl; +#if PY_MAJOR_VERSION < 3 dumpFile << "Exception type : " << PYREPR(frame->f_exc_type) << std::endl; dumpFile << "Exception value : " << PYREPR(frame->f_exc_value) << std::endl; dumpFile << "Exception traceback : " << PYREPR(frame->f_exc_traceback) << std::endl; +#endif dumpFile << "Trace function : " << PYREPR(frame->f_trace) << std::endl; dumpFile << "Builtins : " << PYSIZE(frame->f_builtins) << std::endl; dumpFile << "Globals : " << PYSIZE(frame->f_globals) << std::endl; @@ -991,3 +1084,18 @@ SPELLutils::SPELLdateDesc SPELLutils::getSystemDate() return date; } + +//============================================================================ +// FUNCTION: SPELLutils::compareNocase (const std::string& first, const std::string& second) +//============================================================================ +bool SPELLutils::compareNocase (const std::string& first, const std::string& second) +{ + unsigned int i=0; + while ( (itolower(second[i])) return false; + ++i; + } + return ( first.length() < second.length() ); +} diff --git a/lib/SPELL_WRP/Makefile.am b/lib/SPELL_WRP/Makefile.am index dad5810..8967b5a 100644 --- a/lib/SPELL_WRP/Makefile.am +++ b/lib/SPELL_WRP/Makefile.am @@ -1,36 +1,44 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_WRP.la -libSPELL_WRP_la_LDFLAGS = -version-info 1:0:0 -libSPELL_WRP_la_SOURCES = \ - src/SPELLdriverManager.C \ - src/SPELLdatabaseManager.C \ - src/SPELLpyValue.C \ - src/SPELLconfigDict.C \ - src/SPELLregistry.C - + +lib_LTLIBRARIES = libSPELL_WRP_v2.la libSPELL_WRP_v3.la + +libSPELL_WRP_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_WRP_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_WRP_v2_la_SOURCES = src/SPELLdriverManager.C \ + src/SPELLdatabaseManager.C \ + src/SPELLpyValue.C \ + src/SPELLconfigDict.C \ + src/SPELLregistry.C + +libSPELL_WRP_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_WRP_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_WRP_v3_la_SOURCES = src/SPELLdriverManager.C \ + src/SPELLdatabaseManager.C \ + src/SPELLpyValue.C \ + src/SPELLconfigDict.C \ + src/SPELLregistry.C \ No newline at end of file diff --git a/lib/SPELL_WRP/src/SPELLconfigDict.C b/lib/SPELL_WRP/src/SPELLconfigDict.C index 4be6332..72a97f0 100644 --- a/lib/SPELL_WRP/src/SPELLconfigDict.C +++ b/lib/SPELL_WRP/src/SPELLconfigDict.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the configuration dictionary wrapper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WRP/src/SPELLdatabaseManager.C b/lib/SPELL_WRP/src/SPELLdatabaseManager.C index f6bb4b1..774fb3d 100644 --- a/lib/SPELL_WRP/src/SPELLdatabaseManager.C +++ b/lib/SPELL_WRP/src/SPELLdatabaseManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the database manager wrapper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -83,15 +83,26 @@ PyObject* SPELLdatabaseManager::getDatabaseManagerObject() //============================================================================= void SPELLdatabaseManager::loadBuiltinDatabases() { - SPELLsafePythonOperations ops("SPELLdatabaseManager::loadBuiltinDatabases()"); - PyObject* mgr = getDatabaseManagerObject(); - // New reference - PyObject* scdb = SPELLpythonHelper::instance().callMethod( mgr, "loadDatabase", STRPY("SCDB"), NULL); - SPELLpythonHelper::instance().checkError(); - // New reference - PyObject* gdb = SPELLpythonHelper::instance().callMethod( mgr, "loadDatabase", STRPY("GDB"), NULL ); - SPELLpythonHelper::instance().checkError(); - SPELLpythonHelper::instance().install( scdb, "SCDB" ); - SPELLpythonHelper::instance().install( gdb, "GDB" ); - SPELLpythonHelper::instance().checkError(); + try { + PyObject* mgr = getDatabaseManagerObject(); + // New reference + PyObject* scdb = SPELLpythonHelper::instance().callMethod( mgr, "loadDatabase", STRPY("SCDB"), NULL); + SPELLpythonHelper::instance().checkError(); + // New reference + PyObject* gdb = SPELLpythonHelper::instance().callMethod( mgr, "loadDatabase", STRPY("GDB"), NULL ); + SPELLpythonHelper::instance().checkError(); + SPELLpythonHelper::instance().install( scdb, "SCDB" ); + SPELLpythonHelper::instance().install( gdb, "GDB" ); + SPELLpythonHelper::instance().checkError(); + } + catch(SPELLcoreException& ex) + { + LOG_ERROR("loadBuiltinDatabases failed: " + ex.what()); + throw ex; + } + catch(...) + { + LOG_ERROR("loadBuiltinDatabases failed: Unknown!"); + throw; + } } diff --git a/lib/SPELL_WRP/src/SPELLdriverManager.C b/lib/SPELL_WRP/src/SPELLdriverManager.C index cddf1f4..48a14a3 100644 --- a/lib/SPELL_WRP/src/SPELLdriverManager.C +++ b/lib/SPELL_WRP/src/SPELLdriverManager.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the driver manager wrapper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -35,7 +35,6 @@ #include "SPELL_CFG/SPELLconfiguration.H" - SPELLdriverManager* SPELLdriverManager::s_instance = 0; //============================================================================= @@ -73,6 +72,7 @@ PyObject* SPELLdriverManager::getDriverManagerObject() if (m_dManager == 0) { // Borrowed + SPELLsafePythonOperations ops("SPELLdriverManager::setup()"); PyObject* classObj = SPELLpythonHelper::instance().getObject("spell.lib.drivermgr", "DriverManager"); m_dManager = SPELLpythonHelper::instance().callMethod( classObj, "instance", NULL ); Py_XINCREF(m_dManager); @@ -85,17 +85,29 @@ PyObject* SPELLdriverManager::getDriverManagerObject() //============================================================================= void SPELLdriverManager::setup( const std::string& ctxName, const std::string& interfaces ) { - SPELLsafePythonOperations ops("SPELLdriverManager::setup()"); - PyObject* pmgr = getDriverManagerObject(); - if (interfaces.empty()) + try + { + PyObject* pmgr = getDriverManagerObject(); + if (interfaces.empty()) + { + SPELLpythonHelper::instance().callMethod( pmgr, "setup", SSTRPY(ctxName), NULL); + } + else + { + SPELLpythonHelper::instance().callMethod( pmgr, "setup", SSTRPY(ctxName), SSTRPY(interfaces), NULL); + } + SPELLpythonHelper::instance().checkError(); + } + catch(SPELLcoreException& ex) { - SPELLpythonHelper::instance().callMethod( pmgr, "setup", SSTRPY(ctxName), NULL); + LOG_ERROR("Setup failed: " + ex.what()); + throw ex; } - else + catch(...) { - SPELLpythonHelper::instance().callMethod( pmgr, "setup", SSTRPY(ctxName), SSTRPY(interfaces), NULL); + LOG_ERROR("Setup failed: Unknown!"); + throw; } - SPELLpythonHelper::instance().checkError(); } //============================================================================= @@ -103,17 +115,27 @@ void SPELLdriverManager::setup( const std::string& ctxName, const std::string& i //============================================================================= void SPELLdriverManager::cleanup( bool shutdown ) { - SPELLsafePythonOperations ops("SPELLdriverManager::cleanup()"); - PyObject* pmgr = getDriverManagerObject(); - if (shutdown) + try + { + PyObject* pmgr = getDriverManagerObject(); + if (shutdown) + { + SPELLpythonHelper::instance().callMethod( pmgr, "cleanup", Py_False, Py_True, NULL ); + } + else + { + SPELLpythonHelper::instance().callMethod( pmgr, "cleanup", NULL); + } + SPELLpythonHelper::instance().checkError(); + } + catch(SPELLcoreException& ex) { - SPELLpythonHelper::instance().callMethod( pmgr, "cleanup", Py_False, Py_True, NULL ); + LOG_ERROR("Cleanup failed: " + ex.what()); } - else + catch(...) { - SPELLpythonHelper::instance().callMethod( pmgr, "cleanup", NULL); + LOG_ERROR("Cleanup failed: Unknown!"); } - SPELLpythonHelper::instance().checkError(); } //============================================================================= @@ -121,11 +143,12 @@ void SPELLdriverManager::cleanup( bool shutdown ) //============================================================================= void SPELLdriverManager::onCommand( const std::string& commandId ) { - PyObject* pmgr = getDriverManagerObject(); - PyObject* cmd = SSTRPY(commandId); - Py_INCREF(cmd); try { + SPELLsafePythonOperations ops("SPELLdriverManager::onCommand"); + PyObject* pmgr = getDriverManagerObject(); + PyObject* cmd = SSTRPY(commandId); + Py_INCREF(cmd); SPELLpythonHelper::instance().callMethod( pmgr, "onCommand", cmd, NULL ); SPELLpythonHelper::instance().checkError(); } diff --git a/lib/SPELL_WRP/src/SPELLpyValue.C b/lib/SPELL_WRP/src/SPELLpyValue.C index 5da3343..f534302 100644 --- a/lib/SPELL_WRP/src/SPELLpyValue.C +++ b/lib/SPELL_WRP/src/SPELLpyValue.C @@ -5,7 +5,7 @@ // DESCRIPTION: Data variable implementation // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -94,7 +94,7 @@ void SPELLpyValue::set( PyObject* pyValue ) { DEBUG("[PYVAL] Int check"); m_type = LONG; - m_intValue = PyInt_AsLong(pyValue); + m_intValue = PyLong_AsLongLong(pyValue); } else if (PyFloat_Check(pyValue)) { @@ -103,7 +103,7 @@ void SPELLpyValue::set( PyObject* pyValue ) } else if (SPELLpythonHelper::instance().isTime(pyValue)) { - m_timeValue = SPELLpythonHelper::instance().evalTime(PYSSTR(pyValue)); + m_timeValue = SPELLpythonHelper::instance().evalTime(pyValue); if (m_timeValue.isDelta()) { m_type = RELTIME; @@ -173,7 +173,7 @@ PyObject* SPELLpyValue::get() const } else if (m_type == LONG ) { - return PyLong_FromLong(m_intValue); + return PyLong_FromLongLong(m_intValue); } else if (m_type == DOUBLE) { diff --git a/lib/SPELL_WRP/src/SPELLregistry.C b/lib/SPELL_WRP/src/SPELLregistry.C index e682910..7f503a0 100644 --- a/lib/SPELL_WRP/src/SPELLregistry.C +++ b/lib/SPELL_WRP/src/SPELLregistry.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the registry wrapper // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/Makefile.am b/lib/SPELL_WS/Makefile.am index 92b0900..1001f30 100644 --- a/lib/SPELL_WS/Makefile.am +++ b/lib/SPELL_WS/Makefile.am @@ -1,55 +1,71 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ include ${top_srcdir}/common.mk -lib_LTLIBRARIES= libSPELL_WS.la - -libSPELL_WS_la_CPPFLAGS = ${AM_CPPFLAGS} -libSPELL_WS_la_LDFLAGS = -version-info 3:0:0 - -libSPELL_WS_la_SOURCES = \ - src/SPELLwsStorage.C \ - src/SPELLwsDataHandler.C \ - src/SPELLwsDataCodes.C \ - src/SPELLwsDataHandlerFactory.C \ - src/SPELLwsObjectDataHandler.C \ - src/SPELLwsInstanceDataHandler.C \ - src/SPELLwsCustomTypeDataHandler.C \ - src/SPELLwsTmItemDataHandler.C \ - src/SPELLwsClassDataHandler.C \ - src/SPELLwsDictDataHandler.C \ - src/SPELLwsListDataHandler.C \ - src/SPELLwsTupleDataHandler.C \ - src/SPELLwsDbDataHandler.C \ - src/SPELLwsTimeDataHandler.C \ - src/SPELLwsWorkingMode.C \ - src/SPELLwsFrame.C \ - src/SPELLwsDynamicData.C \ - src/SPELLwsStaticData.C \ - src/SPELLwsWarmStartImpl.C - - +lib_LTLIBRARIES = libSPELL_WS_v2.la libSPELL_WS_v3.la + +libSPELL_WS_v2_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +libSPELL_WS_v2_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_WS_v2_la_SOURCES = src/SPELLwsStorage.C \ + src/SPELLwsDataHandler.C \ + src/SPELLwsDataCodes.C \ + src/SPELLwsDataHandlerFactory.C \ + src/SPELLwsObjectDataHandler.C \ + src/SPELLwsInstanceDataHandler.C \ + src/SPELLwsCustomTypeDataHandler.C \ + src/SPELLwsTmItemDataHandler.C \ + src/SPELLwsClassDataHandler.C \ + src/SPELLwsDictDataHandler.C \ + src/SPELLwsListDataHandler.C \ + src/SPELLwsTupleDataHandler.C \ + src/SPELLwsDbDataHandler.C \ + src/SPELLwsTimeDataHandler.C \ + src/SPELLwsWorkingMode.C \ + src/SPELLwsFrame.C \ + src/SPELLwsDynamicData.C \ + src/SPELLwsStaticData.C \ + src/SPELLwsWarmStartImpl.C + +libSPELL_WS_v3_la_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +libSPELL_WS_v3_la_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} + +libSPELL_WS_v3_la_SOURCES = src/SPELLwsStorage.C \ + src/SPELLwsDataHandler.C \ + src/SPELLwsDataCodes.C \ + src/SPELLwsDataHandlerFactory.C \ + src/SPELLwsObjectDataHandler.C \ + src/SPELLwsCustomTypeDataHandler.C \ + src/SPELLwsTmItemDataHandler.C \ + src/SPELLwsDictDataHandler.C \ + src/SPELLwsListDataHandler.C \ + src/SPELLwsTupleDataHandler.C \ + src/SPELLwsDbDataHandler.C \ + src/SPELLwsTimeDataHandler.C \ + src/SPELLwsWorkingMode.C \ + src/SPELLwsFrame.C \ + src/SPELLwsDynamicData.C \ + src/SPELLwsStaticData.C \ + src/SPELLwsWarmStartImpl.C + \ No newline at end of file diff --git a/lib/SPELL_WS/src/SPELLwsClassDataHandler.C b/lib/SPELL_WS/src/SPELLwsClassDataHandler.C index bf455d4..5a9762d 100644 --- a/lib/SPELL_WS/src/SPELLwsClassDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsClassDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for classes // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsCustomTypeDataHandler.C b/lib/SPELL_WS/src/SPELLwsCustomTypeDataHandler.C index f64a3b3..5cd4b9b 100644 --- a/lib/SPELL_WS/src/SPELLwsCustomTypeDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsCustomTypeDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for instances // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsDataCodes.C b/lib/SPELL_WS/src/SPELLwsDataCodes.C index f0a50f7..029cf81 100644 --- a/lib/SPELL_WS/src/SPELLwsDataCodes.C +++ b/lib/SPELL_WS/src/SPELLwsDataCodes.C @@ -5,7 +5,7 @@ // DESCRIPTION: Data code functions // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsDataHandler.C b/lib/SPELL_WS/src/SPELLwsDataHandler.C index 33ebf07..247762f 100644 --- a/lib/SPELL_WS/src/SPELLwsDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the base data handler // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsDataHandlerFactory.C b/lib/SPELL_WS/src/SPELLwsDataHandlerFactory.C index b52fed6..29525c3 100644 --- a/lib/SPELL_WS/src/SPELLwsDataHandlerFactory.C +++ b/lib/SPELL_WS/src/SPELLwsDataHandlerFactory.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler factory // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -27,8 +27,10 @@ // FILES TO INCLUDE //////////////////////////////////////////////////////// // Local includes ---------------------------------------------------------- #include "SPELL_WS/SPELLwsDataHandlerFactory.H" +#if PY_MAJOR_VERSION < 3 #include "SPELL_WS/SPELLwsClassDataHandler.H" #include "SPELL_WS/SPELLwsInstanceDataHandler.H" +#endif #include "SPELL_WS/SPELLwsCustomTypeDataHandler.H" #include "SPELL_WS/SPELLwsTmItemDataHandler.H" #include "SPELL_WS/SPELLwsDictDataHandler.H" @@ -85,6 +87,7 @@ SPELLwsDataHandler* SPELLwsDataHandlerFactory::createDataHandler( PyObject* obje //DEBUG("[DHF] Object is TIME"); handler = new SPELLwsTimeDataHandler(object); } +#if PY_MAJOR_VERSION < 3 else if (PyClass_Check(object)) { DEBUG("[DHF] Object is a class: " + PYCREPR(object)); @@ -95,6 +98,7 @@ SPELLwsDataHandler* SPELLwsDataHandlerFactory::createDataHandler( PyObject* obje //DEBUG("[DHF] Object is a instance: " + PYCREPR(object)); handler = new SPELLwsInstanceDataHandler(object); } +#endif else if (SPELLpythonHelper::instance().isSubclassInstance(object, "TmItemClass", "spell.lib.adapter.tm_item")) { //DEBUG("[DHF] Object is a TM item: " + PYCREPR(object)); @@ -129,12 +133,14 @@ SPELLwsDataHandler* SPELLwsDataHandlerFactory::createDataHandler( SPELLwsData::C case SPELLwsData::DATA_GENERIC: handler = new SPELLwsObjectDataHandler(NULL); break; +#if PY_MAJOR_VERSION < 3 case SPELLwsData::DATA_CLASS: handler = new SPELLwsClassDataHandler(NULL); break; case SPELLwsData::DATA_INSTANCE: handler = new SPELLwsInstanceDataHandler(NULL); break; +#endif case SPELLwsData::DATA_CUSTOM_TYPE: handler = new SPELLwsCustomTypeDataHandler(NULL); break; diff --git a/lib/SPELL_WS/src/SPELLwsDbDataHandler.C b/lib/SPELL_WS/src/SPELLwsDbDataHandler.C index 6878064..974e272 100644 --- a/lib/SPELL_WS/src/SPELLwsDbDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsDbDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for SPELL databases // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsDictDataHandler.C b/lib/SPELL_WS/src/SPELLwsDictDataHandler.C index cf4e6fd..2a84ec6 100644 --- a/lib/SPELL_WS/src/SPELLwsDictDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsDictDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for dictionaries // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsDynamicData.C b/lib/SPELL_WS/src/SPELLwsDynamicData.C index 13ffa10..0bc2b03 100644 --- a/lib/SPELL_WS/src/SPELLwsDynamicData.C +++ b/lib/SPELL_WS/src/SPELLwsDynamicData.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the dynamic runtime data manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -59,13 +59,13 @@ SPELLwsDynamicData::SPELLwsDynamicData( const SPELLwsStartupInfo& info, unsigned : m_startup(info), m_frame(frame) { - DEBUG("[DYN] Created dynamic data manager for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Created dynamic data manager for frame " + PYCREPR(m_frame)); assert(m_frame != NULL); if (info.persistentFile != "") { m_persistentFile = info.persistentFile + "_" + ISTR(depth) + ".wsd"; - DEBUG("DYN Using persistent file: '" + m_persistentFile + "'"); + //DEBUG("DYN Using persistent file: '" + m_persistentFile + "'"); } else { @@ -75,15 +75,15 @@ SPELLwsDynamicData::SPELLwsDynamicData( const SPELLwsStartupInfo& info, unsigned if (info.recoveryFile != "") { m_recoveryFile = info.recoveryFile + "_" + ISTR(depth) + ".wsd"; - DEBUG("DYN Using recovery file: '" + m_recoveryFile + "'"); + //DEBUG("DYN Using recovery file: '" + m_recoveryFile + "'"); } else { m_recoveryFile = ""; } - DEBUG("DYN working mode: " + WorkingModeToString(m_startup.workingMode)); - DEBUG("DYN perform recovery: " + BSTR(m_startup.performRecovery)); + //DEBUG("DYN working mode: " + WorkingModeToString(m_startup.workingMode)); + //DEBUG("DYN perform recovery: " + BSTR(m_startup.performRecovery)); // If we are in recovery mode if (m_startup.performRecovery && m_recoveryFile != "") @@ -102,7 +102,7 @@ SPELLwsDynamicData::SPELLwsDynamicData( const SPELLwsStartupInfo& info, unsigned //============================================================================= SPELLwsDynamicData::~SPELLwsDynamicData() { - DEBUG("[DYN] Destroyed dynamic data manager for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Destroyed dynamic data manager for frame " + PYCREPR(m_frame)); } //============================================================================= @@ -110,7 +110,7 @@ SPELLwsDynamicData::~SPELLwsDynamicData() //============================================================================= void SPELLwsDynamicData::reset() { - DEBUG("[DYN] Reset dynamic data for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Reset dynamic data for frame " + PYCREPR(m_frame)); m_storage->reset(); m_iBlocks.clear(); // IMPORTANT do not DECREF objects in ValueStack or FastLocals, since they @@ -124,7 +124,7 @@ void SPELLwsDynamicData::reset() //============================================================================= void SPELLwsDynamicData::cleanup() { - DEBUG("[DYN] Remove dynamic data file for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Remove dynamic data file for frame " + PYCREPR(m_frame)); if (m_persistentFile != "") { SPELLutils::deleteFile( m_persistentFile ); @@ -136,7 +136,7 @@ void SPELLwsDynamicData::cleanup() //============================================================================= void SPELLwsDynamicData::update() { - DEBUG("[DYN] Update dynamic data for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Update dynamic data for frame " + PYCREPR(m_frame)); // We need to keep a copy of these these data structures, since the Python interpreter code // unwinds the stack and the try-blocks in case of exceptions. updateTryBlocks(); @@ -149,7 +149,7 @@ void SPELLwsDynamicData::update() //============================================================================= void SPELLwsDynamicData::recover() { - DEBUG("[DYN] Recover dynamic data for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Recover dynamic data for frame " + PYCREPR(m_frame)); // We need to re-create these data structures, since the Python interpreter code // unwinds the stack and the try-blocks in case of exceptions. recoverTryBlocks(); @@ -162,13 +162,13 @@ void SPELLwsDynamicData::recover() //============================================================================= void SPELLwsDynamicData::save() { - DEBUG("[DYN] Save dynamic data for frame " + PYCREPR(m_frame)); - DEBUG("[DYN] Pre-check errors..."); + //DEBUG("[DYN] Save dynamic data for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Pre-check errors..."); SPELLpythonHelper::instance().checkError(); // Empty the file. We only want the latest change // NOTE: this can be removed in case we want the full history of // changes, although some mods would need to be done to the load code - DEBUG("[DYN] Resetting storage first"); + //DEBUG("[DYN] Resetting storage first"); m_storage->reset(); // Store current data storeParameters(); @@ -186,7 +186,7 @@ void SPELLwsDynamicData::save() storeDTA("ARGS"); SPELLpythonHelper::instance().checkError(); storeDTA("IVARS"); - DEBUG("[DYN] Save dynamic data for frame " + PYCREPR(m_frame) + " done"); + //DEBUG("[DYN] Save dynamic data for frame " + PYCREPR(m_frame) + " done"); } //============================================================================= @@ -199,7 +199,7 @@ void SPELLwsDynamicData::restore() THROW_EXCEPTION("Failed to restore dynamic data", "Storage not ready", SPELL_ERROR_WSTART); } - DEBUG("[DYN] Restore dynamic data for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Restore dynamic data for frame " + PYCREPR(m_frame)); loadParameters(); loadTryBlocks(); loadValueStack(); @@ -211,13 +211,13 @@ void SPELLwsDynamicData::restore() recover(); - DEBUG("[DYN] Restore dynamic data for frame " + PYCREPR(m_frame) + " done"); + //DEBUG("[DYN] Restore dynamic data for frame " + PYCREPR(m_frame) + " done"); if (m_persistentFile != "") { // Reset the storage to mode write, using now the assigned persistent file, // not the recovery file - DEBUG("[DYN] Switching to saving state mode"); + //DEBUG("[DYN] Switching to saving state mode"); delete m_storage; m_storage = new SPELLwsStorage( m_persistentFile, SPELLwsStorage::MODE_WRITE ); // Re-save the state, so that the persistent file contains the current state @@ -235,14 +235,14 @@ void SPELLwsDynamicData::updateTryBlocks() unsigned int numTryBlocks = m_frame->f_iblock; if (numTryBlocks>0) { - DEBUG("[DYN] Updating try blocks on frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Updating try blocks on frame " + PYCREPR(m_frame)); // If there is a different number of blocks than before, update. We assume they dont change once created... if ( numTryBlocks > m_iBlocks.size()) { PyTryBlock block = m_frame->f_blockstack[m_frame->f_iblock-1]; m_iBlocks.push_back(block); - DEBUG("[DYN] add block: [" + ISTR(block.b_type) + "," + ISTR(block.b_handler) + "," + ISTR(block.b_level) + "]"); + //DEBUG("[DYN] add block: [" + ISTR(block.b_type) + "," + ISTR(block.b_handler) + "," + ISTR(block.b_level) + "]"); } // Less blocks... else if ((unsigned int) m_frame->f_iblock < m_iBlocks.size()) @@ -250,9 +250,9 @@ void SPELLwsDynamicData::updateTryBlocks() // Just throw it away TryBlocks::iterator it = m_iBlocks.end(); it--; m_iBlocks.erase(it); - DEBUG("[DYN] remove block"); + //DEBUG("[DYN] remove block"); } - DEBUG("[DYN] Updating try blocks done"); + //DEBUG("[DYN] Updating try blocks done"); } // Take into account the case of going out loops and try blocks ( the frame // does not have blocks defined, so we must clear ours ) @@ -272,18 +272,18 @@ void SPELLwsDynamicData::recoverTryBlocks() // If there were blocks defined... if (m_iBlocks.size()>0) { - DEBUG("[DYN] Restoring try blocks on frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Restoring try blocks on frame " + PYCREPR(m_frame)); TryBlocks::iterator it; unsigned int count = 0; for( it = m_iBlocks.begin(); it != m_iBlocks.end(); it++) { PyTryBlock& block = (*it); - DEBUG("[DYN] block " + ISTR(count) + ": [" + ISTR(block.b_type) + "," + ISTR(block.b_handler) + "," + ISTR(block.b_level) + "]"); + //DEBUG("[DYN] block " + ISTR(count) + ": [" + ISTR(block.b_type) + "," + ISTR(block.b_handler) + "," + ISTR(block.b_level) + "]"); PyFrame_BlockSetup( m_frame, block.b_type, block.b_handler, block.b_level ); count++; } m_frame->f_iblock = m_iBlocks.size(); - DEBUG("[DYN] Restoring try blocks done"); + //DEBUG("[DYN] Restoring try blocks done"); } } @@ -292,7 +292,7 @@ void SPELLwsDynamicData::recoverTryBlocks() //============================================================================= void SPELLwsDynamicData::storeTryBlocks() { - DEBUG("[DYN] Storing try blocks (" + ISTR(m_iBlocks.size()) + ")" ); + //DEBUG("[DYN] Storing try blocks (" + ISTR(m_iBlocks.size()) + ")" ); // Store tryblocks first. Size of the list: m_storage->storeLong( m_iBlocks.size() ); // Store the blocks now, if any @@ -302,7 +302,7 @@ void SPELLwsDynamicData::storeTryBlocks() m_storage->storeLong( (*bit).b_type ); m_storage->storeLong( (*bit).b_handler ); m_storage->storeLong( (*bit).b_level ); - DEBUG(" - [" + ISTR((*bit).b_type) + "," + ISTR((*bit).b_handler) + "," + ISTR((*bit).b_level) + "]"); + //DEBUG(" - [" + ISTR((*bit).b_type) + "," + ISTR((*bit).b_handler) + "," + ISTR((*bit).b_level) + "]"); } } @@ -312,14 +312,14 @@ void SPELLwsDynamicData::storeTryBlocks() void SPELLwsDynamicData::loadTryBlocks() { int numBlocks = m_storage->loadLong(); - DEBUG("[DYN] Loading try blocks (" + ISTR(numBlocks) + ")" ); + //DEBUG("[DYN] Loading try blocks (" + ISTR(numBlocks) + ")" ); for( int count=0; countb_type = m_storage->loadLong(); block->b_handler = m_storage->loadLong(); block->b_level = m_storage->loadLong(); - DEBUG(" - [" + ISTR(block->b_type) + "," + ISTR(block->b_handler) + "," + ISTR(block->b_level) + "]"); + //DEBUG(" - [" + ISTR(block->b_type) + "," + ISTR(block->b_handler) + "," + ISTR(block->b_level) + "]"); m_iBlocks.push_back(*block); } } @@ -338,17 +338,18 @@ void SPELLwsDynamicData::updateValueStack() // Add a new item to the stack information if ((unsigned int) stackCount > m_valueStack.size()) { - DEBUG("[DYN] Updating value stack on frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Updating value stack on frame " + PYCREPR(m_frame)); PyObject** p; p = m_frame->f_valuestack; - + if (!p || !*p) // Check for Nullpointer on stack + return; PyTypeObject* type = (PyTypeObject*) PyObject_Type(*p); if (STR(type->tp_name)=="listiterator" ) { // Get the iterator characteristics ListIteratorMirror* iterator = (ListIteratorMirror*) (*p); - DEBUG("[DYN] add list iterator: [" + ISTR(iterator->it_index) + "," + PYCREPR(iterator->it_seq) + "]"); + //DEBUG("[DYN] add list iterator: [" + ISTR(iterator->it_index) + "," + PYCREPR(iterator->it_seq) + "]"); // Create a copy PyObject* newIterator = PyObject_GetIter( (PyObject*)iterator->it_seq ); @@ -356,24 +357,24 @@ void SPELLwsDynamicData::updateValueStack() ListIteratorMirror* itm = (ListIteratorMirror*)newIterator; itm->it_index = iterator->it_index; // IMPORTANT this is a borrowed reference - DEBUG("[DYN] stored iterator: [" + ISTR(itm->it_index) + "," + PYCREPR(itm->it_seq) + "]"); + //DEBUG("[DYN] stored iterator: [" + ISTR(itm->it_index) + "," + PYCREPR(itm->it_seq) + "]"); m_valueStack.push_back(newIterator); } else { - DEBUG("[DYN] Adding to valuestack, unprocessed: " + PYREPR(*p)); + //DEBUG("[DYN] Adding to valuestack, unprocessed: " + PYREPR(*p)); // IMPORTANT this is a borrowed reference m_valueStack.push_back(*p); } - DEBUG("[DYN] Updating value stack done"); + //DEBUG("[DYN] Updating value stack done"); } // Less items else if ((unsigned int) stackCount < m_valueStack.size()) { // Throw it away... ObjectList::iterator it = m_valueStack.end(); it--; - DEBUG("[DYN] remove value stack object"); + //DEBUG("[DYN] remove value stack object"); // IMPORTANT do not DECREF objects in ValueStack or FastLocals, since they // are borrowed references managed by Python layer. m_valueStack.erase(it); @@ -386,14 +387,14 @@ void SPELLwsDynamicData::updateValueStack() for( unsigned int index = 0; index < m_valueStack.size(); index++) { if (*p == NULL) break; - DEBUG("[DYN] checking value stack object: " + PYREPR(*p)); + //DEBUG("[DYN] checking value stack object: " + PYREPR(*p)); PyTypeObject* type = (PyTypeObject*) PyObject_Type(*p); if (STR(type->tp_name)=="listiterator" ) { ListIteratorMirror* stackIterator = (ListIteratorMirror*) (*p); ListIteratorMirror* storedIterator = (ListIteratorMirror*) m_valueStack[index]; storedIterator->it_index = stackIterator->it_index; - DEBUG("[DYN] update iterator: [" + ISTR(stackIterator->it_index) + "," + PYCREPR(stackIterator->it_seq) + "]"); + //DEBUG("[DYN] update iterator: [" + ISTR(stackIterator->it_index) + "," + PYCREPR(stackIterator->it_seq) + "]"); } } } @@ -416,19 +417,21 @@ void SPELLwsDynamicData::recoverValueStack() // If there are values to recover if (m_valueStack.size()>0) { - DEBUG("[DYN] Restoring value stack on frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Restoring value stack on frame " + PYCREPR(m_frame)); PyObject** stack_pointer = m_frame->f_valuestack; for( unsigned int count = 0; counttp_name)); + //DEBUG("[DYN] Type is " + STR(type->tp_name)); if (STR(type->tp_name)=="listiterator" ) { ListIteratorMirror* stackIterator = (ListIteratorMirror*) (stackObject); - DEBUG("[DYN] restoring iterator: [" + ISTR(stackIterator->it_index) + "," + PYCREPR(stackIterator->it_seq) + "]"); + //DEBUG("[DYN] restoring iterator: [" + ISTR(stackIterator->it_index) + "," + PYCREPR(stackIterator->it_seq) + "]"); PyObject* newIterator = PyObject_GetIter( (PyObject*)stackIterator->it_seq ); newIterator->ob_refcnt = stackObject->ob_refcnt; *stack_pointer++ = newIterator; @@ -440,7 +443,7 @@ void SPELLwsDynamicData::recoverValueStack() } m_frame->f_stacktop = stack_pointer--; m_frame->f_code->co_stacksize = m_valueStack.size(); - DEBUG("[DYN] Restoring value stack done"); + //DEBUG("[DYN] Restoring value stack done"); } // Otherwise define the top as the same value as the bottom of the stack else @@ -456,7 +459,7 @@ void SPELLwsDynamicData::storeValueStack() { // Store the value stack. Size of the list first: m_storage->storeLong( m_valueStack.size() ); - DEBUG("[DYN] Storing value stack for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Storing value stack for frame " + PYCREPR(m_frame)); ObjectList::iterator it; for( it = m_valueStack.begin(); it != m_valueStack.end(); it++) { @@ -509,7 +512,7 @@ void SPELLwsDynamicData::storeValueStack() void SPELLwsDynamicData::loadValueStack() { long numValues = m_storage->loadLong(); - DEBUG("[DYN] Loading value stack for frame " + PYCREPR(m_frame)); + //DEBUG("[DYN] Loading value stack for frame " + PYCREPR(m_frame)); for( int count = 0; count < numValues; count++) { // Retrieve the marker @@ -551,7 +554,7 @@ void SPELLwsDynamicData::updateFastLocals() { assert(m_frame != NULL); int numLocals = m_frame->f_code->co_nlocals-1; - DEBUG("[DYN] Update fast locals: " + ISTR(numLocals)); + //DEBUG("[DYN] Update fast locals: " + ISTR(numLocals)); if (numLocals<0) numLocals = 0; m_fastLocals.clear(); if ((numLocals>0) && ((unsigned int)numLocals != m_fastLocals.size())) @@ -561,7 +564,7 @@ void SPELLwsDynamicData::updateFastLocals() { if (*lplus) { - DEBUG(" add fast local: " + PYREPR(*lplus)); + //DEBUG(" add fast local: " + PYREPR(*lplus)); // IMPORTANT this is a borrowed reference m_fastLocals.push_back(*lplus); lplus++; @@ -576,13 +579,13 @@ void SPELLwsDynamicData::updateFastLocals() void SPELLwsDynamicData::recoverFastLocals() { assert(m_frame != NULL); - DEBUG("[DYN] Recover fast locals: " + ISTR(m_fastLocals.size())); + //DEBUG("[DYN] Recover fast locals: " + ISTR(m_fastLocals.size())); if (m_fastLocals.size()>0) { for( unsigned int count = 0; countf_localsplus[count] = obj; } m_frame->f_code->co_nlocals = m_fastLocals.size()+1; @@ -594,13 +597,13 @@ void SPELLwsDynamicData::recoverFastLocals() //============================================================================= void SPELLwsDynamicData::storeFastLocals() { - DEBUG("[DYN] Store fast locals: " + ISTR(m_fastLocals.size())); + //DEBUG("[DYN] Store fast locals: " + ISTR(m_fastLocals.size())); // Store the fast locals. Size of list first m_storage->storeLong( m_fastLocals.size() ); ObjectList::iterator it; for( it = m_fastLocals.begin(); it != m_fastLocals.end(); it++) { - DEBUG(" storing fast local " + PYREPR(*it)); + //DEBUG(" storing fast local " + PYREPR(*it)); m_storage->storeObjectOrNone(*it); } } @@ -611,11 +614,11 @@ void SPELLwsDynamicData::storeFastLocals() void SPELLwsDynamicData::loadFastLocals() { long numLocals = m_storage->loadLong(); - DEBUG("[DYN] Load fast locals: " + ISTR(numLocals)); + //DEBUG("[DYN] Load fast locals: " + ISTR(numLocals)); for(int count=0; countloadObject(); - DEBUG(" loaded fast local " + PYREPR(obj)); + //DEBUG(" loaded fast local " + PYREPR(obj)); m_fastLocals.push_back( obj ); } } @@ -625,7 +628,7 @@ void SPELLwsDynamicData::loadFastLocals() //============================================================================= void SPELLwsDynamicData::storeGlobals() { - DEBUG("[DYN] Storing globals"); + //DEBUG("[DYN] Storing globals"); assert( m_frame->f_globals != NULL ); storeDictionary( m_frame->f_globals ); storeConstReferences( m_frame->f_globals ); @@ -676,7 +679,7 @@ void SPELLwsDynamicData::loadGlobals() //============================================================================= void SPELLwsDynamicData::storeLocals() { - DEBUG("[DYN] Storing locals"); + //DEBUG("[DYN] Storing locals"); // IMPORTANT: do not remove this PyFrame calls. Inside functions, local variables MAY be defined in the // fast locals, depending on their contents. These calls are needed so that the storeDictionary() call // is able to store those local variables which were not moved to fast locals. Fast locals and locals @@ -697,7 +700,7 @@ void SPELLwsDynamicData::loadLocals() if (restored != NULL) { - DEBUG("[DYN] Restored locals dictionary"); + //DEBUG("[DYN] Restored locals dictionary"); // IMPORTANT: do not remove this PyFrame calls. Inside functions, local variables MAY be defined in the // fast locals, depending on their contents. These calls are needed so that the storeDictionary() call // is able to store those local variables which were not moved to fast locals. Fast locals and locals @@ -723,7 +726,7 @@ void SPELLwsDynamicData::loadLocals() //============================================================================= void SPELLwsDynamicData::storeConstReferences( PyObject* dictionary ) { - DEBUG("[DYN] Store const references"); + //DEBUG("[DYN] Store const references"); if (dictionary == NULL) { m_storage->storeLong( 0 ); @@ -731,7 +734,7 @@ void SPELLwsDynamicData::storeConstReferences( PyObject* dictionary ) } // Store possibly loaded functions which are ignored by the dictionary // handler, if they are part of the code definition - DEBUG("[DYN] Counting const references"); + //DEBUG("[DYN] Counting const references"); // Store first the amount of code references unsigned int numReferences = 0; int numConsts = 0; @@ -739,7 +742,7 @@ void SPELLwsDynamicData::storeConstReferences( PyObject* dictionary ) if (co_consts != NULL) { numConsts = PyTuple_Size(co_consts); - DEBUG("[DYN] Defined " + ISTR(numConsts) + " constants in code"); + //DEBUG("[DYN] Defined " + ISTR(numConsts) + " constants in code"); for( int index = 0; index < numConsts; index++ ) { PyObject* item = PyTuple_GetItem(co_consts,index); @@ -756,7 +759,7 @@ void SPELLwsDynamicData::storeConstReferences( PyObject* dictionary ) } } - DEBUG("[DYN] Number of code references: " + ISTR(numReferences)); + //DEBUG("[DYN] Number of code references: " + ISTR(numReferences)); m_storage->storeLong( numReferences ); if (numReferences>0) @@ -771,7 +774,7 @@ void SPELLwsDynamicData::storeConstReferences( PyObject* dictionary ) // And the code object has been loaded in the globals dictionary if (PyDict_Contains( dictionary, code->co_name )) { - DEBUG("[DYN] Storing code reference: " + PYSTR(code->co_name)); + //DEBUG("[DYN] Storing code reference: " + PYSTR(code->co_name)); m_storage->storeObject( code->co_name ); } } @@ -789,7 +792,7 @@ void SPELLwsDynamicData::loadConstReferences( PyObject* dictionary ) if (numReferences > 0) { - DEBUG("[DYN] " + ISTR(numReferences) + " global references to load"); + //DEBUG("[DYN] " + ISTR(numReferences) + " global references to load"); for(unsigned int count = 0; count < numReferences; count++ ) { PyObject* codeName = m_storage->loadObject(); @@ -804,7 +807,7 @@ void SPELLwsDynamicData::loadConstReferences( PyObject* dictionary ) PyCodeObject* code = (PyCodeObject*) item; if (PYSTR(code->co_name) == PYSTR(codeName)) { - DEBUG("[DYN] Loading reference to " + PYREPR(codeName)); + //DEBUG("[DYN] Loading reference to " + PYREPR(codeName)); PyDict_SetItem( dictionary, code->co_name, PyFunction_New( (PyObject*) code, m_frame->f_globals) ); } } @@ -813,7 +816,7 @@ void SPELLwsDynamicData::loadConstReferences( PyObject* dictionary ) } else { - DEBUG("[DYN] No global references to load"); + //DEBUG("[DYN] No global references to load"); } } @@ -822,13 +825,13 @@ void SPELLwsDynamicData::loadConstReferences( PyObject* dictionary ) //============================================================================= void SPELLwsDynamicData::storeParameters() { - DEBUG("[DYN] Storing frame parameters"); + //DEBUG("[DYN] Storing frame parameters"); m_storage->storeLong(m_frame->f_lineno); - DEBUG(" - Lineno : " + ISTR(m_frame->f_lineno)); + //DEBUG(" - Lineno : " + ISTR(m_frame->f_lineno)); m_storage->storeLong(m_frame->f_lasti); - DEBUG(" - Instruction: " + ISTR(m_frame->f_lasti)); + //DEBUG(" - Instruction: " + ISTR(m_frame->f_lasti)); m_storage->storeLong(m_frame->f_iblock); - DEBUG(" - IBlocks : " + ISTR(m_frame->f_iblock)); + //DEBUG(" - IBlocks : " + ISTR(m_frame->f_iblock)); } //============================================================================= @@ -836,9 +839,9 @@ void SPELLwsDynamicData::storeParameters() //============================================================================= void SPELLwsDynamicData::loadParameters() { - DEBUG("[DYN] Loading frame parameters"); + //DEBUG("[DYN] Loading frame parameters"); m_frame->f_lineno = m_storage->loadLong(); - DEBUG(" - Lineno : " + ISTR(m_frame->f_lineno)); + //DEBUG(" - Lineno : " + ISTR(m_frame->f_lineno)); m_frame->f_lasti = m_storage->loadLong(); // The last instruction pointer needs to be adjusted. Same as for fixState() algorithm @@ -849,29 +852,29 @@ void SPELLwsDynamicData::loadParameters() THROW_EXCEPTION("Unable to restore frame dynamic data", "Cannot restore frame parameters", SPELL_ERROR_WSTART); } - DEBUG("[DYN] Adjusting offset"); + //DEBUG("[DYN] Adjusting offset"); int offsetForLine = lnotab.offset(m_frame->f_lineno); // If the current offset matches the first offset corresponding to the restored // line in the lnotab, we are in the main proc and NO function call. We need // to decrease the last instruction offset by 1 (see PyEval_EvalFrameEx) if (offsetForLine == m_frame->f_lasti) { - DEBUG("[DYN] Corresponds to normal statement"); + //DEBUG("[DYN] Corresponds to normal statement"); m_frame->f_lasti = offsetForLine-1; } // Otherwise, we need to increase the offset by 2 (+3,-1) in order to position // the interpreter right after the CALL_FUNCTION opcode, in the POP_TOP opcode else { - DEBUG("[DYN] Corresponds to function call"); + //DEBUG("[DYN] Corresponds to function call"); int nextLine = lnotab.lineAfter(m_frame->f_lineno); - DEBUG("[DYN] Next line is " + ISTR(nextLine)); + //DEBUG("[DYN] Next line is " + ISTR(nextLine)); int nextInstruction = lnotab.offset(nextLine); m_frame->f_lasti = nextInstruction -1; // Will position it in the lastLine but POP_TOP instr. } - DEBUG(" - Instruction: " + ISTR(m_frame->f_lasti)); + //DEBUG(" - Instruction: " + ISTR(m_frame->f_lasti)); m_frame->f_iblock = m_storage->loadLong(); - DEBUG(" - IBlocks : " + ISTR(m_frame->f_iblock)); + //DEBUG(" - IBlocks : " + ISTR(m_frame->f_iblock)); } //============================================================================= @@ -900,7 +903,7 @@ PyObject* SPELLwsDynamicData::loadDictionary() //============================================================================= void SPELLwsDynamicData::storeDTA( const std::string& container ) { - DEBUG("[DYN] Store DTA container: " + container); + //DEBUG("[DYN] Store DTA container: " + container); PyObject* pyObj = PyDict_GetItemString(SPELLpythonHelper::instance().getMainDict(), container.c_str() ); @@ -917,7 +920,7 @@ void SPELLwsDynamicData::storeDTA( const std::string& container ) m_storage->storeLong(numKeys); - DEBUG("[DYN] " + ISTR(numKeys) + " items"); + //DEBUG("[DYN] " + ISTR(numKeys) + " items"); for(unsigned int index=0; index < numKeys; index++) { @@ -931,13 +934,13 @@ void SPELLwsDynamicData::storeDTA( const std::string& container ) std::string format = var->getFormat(); std::string type = var->getType(); - DEBUG(" - key: " + var->getName()); + //DEBUG(" - key: " + var->getName()); // 0. Key m_storage->storeObject( SSTRPY(var->getName()) ); // 1. Value - DEBUG(" - value: " + PYREPR(value)); + //DEBUG(" - value: " + PYREPR(value)); SPELLwsDataHandler* handler = SPELLwsDataHandlerFactory::createDataHandler(value); handler->setStorage(m_storage); m_storage->storeLong(handler->getCode()); @@ -946,22 +949,22 @@ void SPELLwsDynamicData::storeDTA( const std::string& container ) // 2. Format, Type and Confirm if (format.empty()) { - DEBUG(" - no format given"); + //DEBUG(" - no format given"); m_storage->storeObject( Py_None ); } else { - DEBUG(" - format: " + format); + //DEBUG(" - format: " + format); m_storage->storeObject( SSTRPY(format) ); } if (type.empty()) { - DEBUG(" - no type given"); + //DEBUG(" - no type given"); m_storage->storeObject( Py_None ); } else { - DEBUG(" - type: " + type); + //DEBUG(" - type: " + type); m_storage->storeObject( SSTRPY(type) ); } m_storage->storeObject( confirmGet ? Py_True : Py_False ); @@ -983,7 +986,7 @@ void SPELLwsDynamicData::storeDTA( const std::string& container ) } } - DEBUG("[DYN] Data container stored"); + //DEBUG("[DYN] Data container stored"); } //============================================================================= @@ -991,7 +994,7 @@ void SPELLwsDynamicData::storeDTA( const std::string& container ) //============================================================================= void SPELLwsDynamicData::loadDTA( const std::string& container ) { - DEBUG("[DYN] Loading data container: " + container); + //DEBUG("[DYN] Loading data container: " + container); PyObject* pyObj = PyDict_GetItemString(SPELLpythonHelper::instance().getMainDict(), container.c_str() ); @@ -1006,14 +1009,14 @@ void SPELLwsDynamicData::loadDTA( const std::string& container ) unsigned int numKeys = m_storage->loadLong(); - DEBUG("[DYN] Items to load: " + ISTR(numKeys)); + //DEBUG("[DYN] Items to load: " + ISTR(numKeys)); for(unsigned int index=0; index < numKeys; index++) { // 0. Key PyObject* pyName = m_storage->loadObject(); - DEBUG(" - key: " + PYREPR(pyName)); + //DEBUG(" - key: " + PYREPR(pyName)); // 1. Value SPELLwsData::Code code = (SPELLwsData::Code) m_storage->loadLong(); @@ -1023,13 +1026,13 @@ void SPELLwsDynamicData::loadDTA( const std::string& container ) PyObject* value = handler->getObject(); - DEBUG(" - value: " + PYREPR(value)); + //DEBUG(" - value: " + PYREPR(value)); // 2. Format, Type and Confirm PyObject* pyFormat = m_storage->loadObject(); - DEBUG(" - format: " + PYREPR(pyFormat)); + //DEBUG(" - format: " + PYREPR(pyFormat)); PyObject* pyType = m_storage->loadObject(); - DEBUG(" - type: " + PYREPR(pyType)); + //DEBUG(" - type: " + PYREPR(pyType)); PyObject* pyConfirm = m_storage->loadObject(); // 3. Expected @@ -1055,7 +1058,7 @@ void SPELLwsDynamicData::loadDTA( const std::string& container ) } // Create the variable - PyObject* varClass = SPELLpythonHelper::instance().getObject("libSPELL_DTA", "Var"); + PyObject* varClass = SPELLpythonHelper::instance().getObject(MOD_NAME(libSPELL_DTA), "Var"); Py_INCREF(varClass); assert(varClass != NULL); PyObject* dict = PyDict_New(); @@ -1090,6 +1093,6 @@ void SPELLwsDynamicData::loadDTA( const std::string& container ) args->setValue(pyName,instance); } - DEBUG("[DYN] Data container loaded"); + //DEBUG("[DYN] Data container loaded"); } diff --git a/lib/SPELL_WS/src/SPELLwsFrame.C b/lib/SPELL_WS/src/SPELLwsFrame.C index bf535d0..21948aa 100644 --- a/lib/SPELL_WS/src/SPELLwsFrame.C +++ b/lib/SPELL_WS/src/SPELLwsFrame.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the frame data manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -139,16 +139,19 @@ void SPELLwsFrame::fixState( PyThreadState* newState, bool isHead ) // Reset the frame values m_frame->f_lasti = m_lastInstruction; m_frame->f_lineno = m_lastLine; +#if PY_MAJOR_VERSION < 3 m_frame->f_tstate = newState; +#endif m_frame->f_stacktop = m_frame->f_valuestack; // Reset exception trace +#if PY_MAJOR_VERSION < 3 Py_XDECREF(m_frame->f_exc_traceback); Py_XDECREF(m_frame->f_exc_type); Py_XDECREF(m_frame->f_exc_value); m_frame->f_exc_traceback = NULL; m_frame->f_exc_type = NULL; m_frame->f_exc_value = NULL; - +#endif // Recover the dynamic data and update the frame DEBUG("[FRM] Recovering dynamic data"); m_dynamic->recover(); diff --git a/lib/SPELL_WS/src/SPELLwsInstanceDataHandler.C b/lib/SPELL_WS/src/SPELLwsInstanceDataHandler.C index f2c3087..e7d92f0 100644 --- a/lib/SPELL_WS/src/SPELLwsInstanceDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsInstanceDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for instances // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsListDataHandler.C b/lib/SPELL_WS/src/SPELLwsListDataHandler.C index ac0ea9c..35cb0b5 100644 --- a/lib/SPELL_WS/src/SPELLwsListDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsListDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for lists // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsObjectDataHandler.C b/lib/SPELL_WS/src/SPELLwsObjectDataHandler.C index 6a9c6ce..a9bc45c 100644 --- a/lib/SPELL_WS/src/SPELLwsObjectDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsObjectDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for objects // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsStaticData.C b/lib/SPELL_WS/src/SPELLwsStaticData.C index f7cbe77..f4841b4 100644 --- a/lib/SPELL_WS/src/SPELLwsStaticData.C +++ b/lib/SPELL_WS/src/SPELLwsStaticData.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the static data manager // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsStorage.C b/lib/SPELL_WS/src/SPELLwsStorage.C index 75b8f08..55ce1bb 100644 --- a/lib/SPELL_WS/src/SPELLwsStorage.C +++ b/lib/SPELL_WS/src/SPELLwsStorage.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the storage model // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -148,7 +148,7 @@ void SPELLwsStorage::storeObject( PyObject* object ) SPELLpythonHelper::instance().checkError(); int marshal_len = PyObject_Length(marshal); DEBUG("Marshal length: " + ISTR(marshal_len)); - char* encoded = PyString_AsString(marshal); + const char* encoded = PyString_AsString(marshal); // FORMAT IN FILE: // COUNT \1 PTYPE \1 MARSHAL LENGTH diff --git a/lib/SPELL_WS/src/SPELLwsTimeDataHandler.C b/lib/SPELL_WS/src/SPELLwsTimeDataHandler.C index a1dafda..220c3c6 100644 --- a/lib/SPELL_WS/src/SPELLwsTimeDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsTimeDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for SPELL TIME // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsTmItemDataHandler.C b/lib/SPELL_WS/src/SPELLwsTmItemDataHandler.C index 4c8a9ca..9735f2e 100644 --- a/lib/SPELL_WS/src/SPELLwsTmItemDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsTmItemDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for tm items // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsTupleDataHandler.C b/lib/SPELL_WS/src/SPELLwsTupleDataHandler.C index 4ebbaa6..9ad43b6 100644 --- a/lib/SPELL_WS/src/SPELLwsTupleDataHandler.C +++ b/lib/SPELL_WS/src/SPELLwsTupleDataHandler.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the data handler for lists // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/lib/SPELL_WS/src/SPELLwsWarmStartImpl.C b/lib/SPELL_WS/src/SPELLwsWarmStartImpl.C index b8b617a..a89e819 100644 --- a/lib/SPELL_WS/src/SPELLwsWarmStartImpl.C +++ b/lib/SPELL_WS/src/SPELLwsWarmStartImpl.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the warm start mechanism controller // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -175,6 +175,7 @@ void SPELLwsWarmStartImpl::saveState() SPELLsafePythonOperations ops("SPELLwsWarmstartImpl::saveState()"); //TODO check, possibly there are several states that need to be saved +#if PY_MAJOR_VERSION < 3 PyThreadState* state = m_topFrame->getFrameObject()->f_tstate; DEBUG("Current thread state: " + PSTR(state)); @@ -188,6 +189,7 @@ void SPELLwsWarmStartImpl::saveState() m_storage->storeLong( state->tick_counter ); DEBUG(" - GIL counter : " + ISTR(state->gilstate_counter)); m_storage->storeLong( state->gilstate_counter ); +#endif DEBUG(" - Number of frames: " + ISTR(m_frames.size())); m_storage->storeLong( m_frames.size() ); @@ -233,8 +235,10 @@ PyFrameObject* SPELLwsWarmStartImpl::restoreState() tstate->recursion_depth = m_storage->loadLong(); DEBUG(" - Recursion depth : " + ISTR(tstate->recursion_depth)); +#if PY_MAJOR_VERSION < 3 tstate->tick_counter = m_storage->loadLong(); DEBUG(" - Tcik counter : " + ISTR(tstate->tick_counter)); +#endif tstate->gilstate_counter = m_storage->loadLong(); DEBUG(" - GIL counter : " + ISTR(tstate->gilstate_counter)); int numFrames = m_storage->loadLong(); @@ -338,7 +342,9 @@ PyFrameObject* SPELLwsWarmStartImpl::fixState() newState->recursion_depth = oldState->recursion_depth; newState->tracing = oldState->tracing; newState->use_tracing = oldState->use_tracing; +#if PY_MAJOR_VERSION < 3 newState->tick_counter = oldState->tick_counter; +#endif newState->gilstate_counter = oldState->gilstate_counter; newState->dict = PyDict_Copy(oldState->dict); @@ -349,7 +355,7 @@ PyFrameObject* SPELLwsWarmStartImpl::fixState() for( unsigned int index = 0; index < frameCount; index++) { bool isHead = (index == (frameCount-1)); - DEBUG("[WS] Fix state on frame index " + ISTR(index) + " frame=" + PYCREPR(m_frames[index])); + DEBUG("[WS] Fix state on frame index " + ISTR(index) + " frame=" + PYCREPR(m_frames[index]->getFrameObject())); m_topFrame = m_frames[index]; m_topFrame->fixState(newState, isHead); } diff --git a/lib/SPELL_WS/src/SPELLwsWorkingMode.C b/lib/SPELL_WS/src/SPELLwsWorkingMode.C index 2abc876..004d1ae 100644 --- a/lib/SPELL_WS/src/SPELLwsWorkingMode.C +++ b/lib/SPELL_WS/src/SPELLwsWorkingMode.C @@ -5,7 +5,7 @@ // DESCRIPTION: Implementation of the warmstart working mode // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 0000000..94e6f26 --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,5 @@ +/libtool.m4 +/ltoptions.m4 +/ltsugar.m4 +/ltversion.m4 +/lt~obsolete.m4 diff --git a/m4/ax_compare_version.m4 b/m4/ax_compare_version.m4 new file mode 100644 index 0000000..9c8e208 --- /dev/null +++ b/m4/ax_compare_version.m4 @@ -0,0 +1,177 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_compare_version.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_COMPARE_VERSION(VERSION_A, OP, VERSION_B, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) +# +# DESCRIPTION +# +# This macro compares two version strings. Due to the various number of +# minor-version numbers that can exist, and the fact that string +# comparisons are not compatible with numeric comparisons, this is not +# necessarily trivial to do in a autoconf script. This macro makes doing +# these comparisons easy. +# +# The six basic comparisons are available, as well as checking equality +# limited to a certain number of minor-version levels. +# +# The operator OP determines what type of comparison to do, and can be one +# of: +# +# eq - equal (test A == B) +# ne - not equal (test A != B) +# le - less than or equal (test A <= B) +# ge - greater than or equal (test A >= B) +# lt - less than (test A < B) +# gt - greater than (test A > B) +# +# Additionally, the eq and ne operator can have a number after it to limit +# the test to that number of minor versions. +# +# eq0 - equal up to the length of the shorter version +# ne0 - not equal up to the length of the shorter version +# eqN - equal up to N sub-version levels +# neN - not equal up to N sub-version levels +# +# When the condition is true, shell commands ACTION-IF-TRUE are run, +# otherwise shell commands ACTION-IF-FALSE are run. The environment +# variable 'ax_compare_version' is always set to either 'true' or 'false' +# as well. +# +# Examples: +# +# AX_COMPARE_VERSION([3.15.7],[lt],[3.15.8]) +# AX_COMPARE_VERSION([3.15],[lt],[3.15.8]) +# +# would both be true. +# +# AX_COMPARE_VERSION([3.15.7],[eq],[3.15.8]) +# AX_COMPARE_VERSION([3.15],[gt],[3.15.8]) +# +# would both be false. +# +# AX_COMPARE_VERSION([3.15.7],[eq2],[3.15.8]) +# +# would be true because it is only comparing two minor versions. +# +# AX_COMPARE_VERSION([3.15.7],[eq0],[3.15]) +# +# would be true because it is only comparing the lesser number of minor +# versions of the two values. +# +# Note: The characters that separate the version numbers do not matter. An +# empty string is the same as version 0. OP is evaluated by autoconf, not +# configure, so must be a string, not a variable. +# +# The author would like to acknowledge Guido Draheim whose advice about +# the m4_case and m4_ifvaln functions make this macro only include the +# portions necessary to perform the specific comparison specified by the +# OP argument in the final configure script. +# +# LICENSE +# +# Copyright (c) 2008 Tim Toolan +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 12 + +dnl ######################################################################### +AC_DEFUN([AX_COMPARE_VERSION], [ + AC_REQUIRE([AC_PROG_AWK]) + + # Used to indicate true or false condition + ax_compare_version=false + + # Convert the two version strings to be compared into a format that + # allows a simple string comparison. The end result is that a version + # string of the form 1.12.5-r617 will be converted to the form + # 0001001200050617. In other words, each number is zero padded to four + # digits, and non digits are removed. + AS_VAR_PUSHDEF([A],[ax_compare_version_A]) + A=`echo "$1" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + AS_VAR_PUSHDEF([B],[ax_compare_version_B]) + B=`echo "$3" | sed -e 's/\([[0-9]]*\)/Z\1Z/g' \ + -e 's/Z\([[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/Z\([[0-9]][[0-9]][[0-9]]\)Z/Z0\1Z/g' \ + -e 's/[[^0-9]]//g'` + + dnl # In the case of le, ge, lt, and gt, the strings are sorted as necessary + dnl # then the first line is used to determine if the condition is true. + dnl # The sed right after the echo is to remove any indented white space. + m4_case(m4_tolower($2), + [lt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [gt],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/false/;s/x${B}/true/;1q"` + ], + [le],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ], + [ge],[ + ax_compare_version=`echo "x$A +x$B" | sed 's/^ *//' | sort -r | sed "s/x${A}/true/;s/x${B}/false/;1q"` + ],[ + dnl Split the operator from the subversion count if present. + m4_bmatch(m4_substr($2,2), + [0],[ + # A count of zero means use the length of the shorter version. + # Determine the number of characters in A and B. + ax_compare_version_len_A=`echo "$A" | $AWK '{print(length)}'` + ax_compare_version_len_B=`echo "$B" | $AWK '{print(length)}'` + + # Set A to no more than B's length and B to no more than A's length. + A=`echo "$A" | sed "s/\(.\{$ax_compare_version_len_B\}\).*/\1/"` + B=`echo "$B" | sed "s/\(.\{$ax_compare_version_len_A\}\).*/\1/"` + ], + [[0-9]+],[ + # A count greater than zero means use only that many subversions + A=`echo "$A" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + B=`echo "$B" | sed "s/\(\([[0-9]]\{4\}\)\{m4_substr($2,2)\}\).*/\1/"` + ], + [.+],[ + AC_WARNING( + [illegal OP numeric parameter: $2]) + ],[]) + + # Pad zeros at end of numbers to make same length. + ax_compare_version_tmp_A="$A`echo $B | sed 's/./0/g'`" + B="$B`echo $A | sed 's/./0/g'`" + A="$ax_compare_version_tmp_A" + + # Check for equality or inequality as necessary. + m4_case(m4_tolower(m4_substr($2,0,2)), + [eq],[ + test "x$A" = "x$B" && ax_compare_version=true + ], + [ne],[ + test "x$A" != "x$B" && ax_compare_version=true + ],[ + AC_WARNING([illegal OP parameter: $2]) + ]) + ]) + + AS_VAR_POPDEF([A])dnl + AS_VAR_POPDEF([B])dnl + + dnl # Execute ACTION-IF-TRUE / ACTION-IF-FALSE. + if test "$ax_compare_version" = "true" ; then + m4_ifvaln([$4],[$4],[:])dnl + m4_ifvaln([$5],[else $5])dnl + fi +]) dnl AX_COMPARE_VERSION diff --git a/m4/ax_python_devel.m4 b/m4/ax_python_devel.m4 index 92054e6..c6bf445 100644 --- a/m4/ax_python_devel.m4 +++ b/m4/ax_python_devel.m4 @@ -23,7 +23,7 @@ # version number. Don't use "PYTHON_VERSION" for this: that environment # variable is declared as precious and thus reserved for the end-user. # -# This macro should work for all versions of Python >= 2.1.0. As an end +# This macro should work fior all versions of Python >= 2.1.0. As an end # user, you can disable the check for the python version by setting the # PYTHON_NOVERSIONCHECK environment variable to something else than the # empty string. @@ -68,31 +68,31 @@ #serial 3 -AU_ALIAS([AC_PYTHON_DEVEL], [AX_PYTHON_DEVEL]) -AC_DEFUN([AX_PYTHON_DEVEL],[ +AU_ALIAS([AC_PYTHON_DEVEL_V2], [AX_PYTHON_DEVEL_V2]) +AC_DEFUN([AX_PYTHON_DEVEL_V2],[ # # Allow the use of a (user set) custom python version # - AC_ARG_VAR([PYTHON_VERSION],[The installed Python + AC_ARG_VAR([PYTHON2_VERSION],[The installed Python version to use, for example '2.3'. This string will be appended to the Python interpreter canonical name.]) - AC_PATH_PROG([PYTHON],[python[$PYTHON_VERSION]]) - if test -z "$PYTHON"; then - AC_MSG_ERROR([Cannot find python$PYTHON_VERSION in your system path]) - PYTHON_VERSION="" + AC_PATH_PROG([PYTHON2],[python[$PYTHON2_VERSION]]) + if test -z "$PYTHON2"; then + AC_MSG_ERROR([Cannot find python$PYTHON2_VERSION in your system path]) + PYTHON2_VERSION="" fi # # Check for a version of Python >= 2.5.0 # AC_MSG_CHECKING([for a version of Python >= '2.5.0']) - ac_supports_python_ver=`$PYTHON -c "import sys; \ + ac_supports_python_ver=`$PYTHON2 -c "import sys; \ ver = sys.version.split ()[[0]]; \ print (ver >= '2.5.0')"` if test "$ac_supports_python_ver" != "True"; then - if test -z "$PYTHON_NOVERSIONCHECK"; then + if test -z "$PYTHON2_NOVERSIONCHECK"; then AC_MSG_RESULT([no]) AC_MSG_FAILURE([ This version of the AC@&t@_PYTHON_DEVEL macro @@ -115,9 +115,9 @@ to something else than an empty string. # if test -n "$1"; then AC_MSG_CHECKING([for a version of Python $1]) - ac_supports_python_ver=`$PYTHON -c "import sys; \ - ver = sys.version.split ()[[0]]; \ - print (ver $1)"` + ac_supports_python_ver=`$PYTHON2 -c "import sys; \ + ver = sys.version.split()[[0]]; \ + print (ver >= '$1')"` if test "$ac_supports_python_ver" = "True"; then AC_MSG_RESULT([yes]) else @@ -127,7 +127,7 @@ If you have it installed, but it isn't the default Python interpreter in your system path, please pass the PYTHON_VERSION variable to configure. See ``configure --help'' for reference. ]) - PYTHON_VERSION="" + PYTHON2_VERSION="" fi fi @@ -135,7 +135,7 @@ variable to configure. See ``configure --help'' for reference. # Check if you have distutils, else fail # AC_MSG_CHECKING([for the distutils Python package]) - ac_distutils_result=`$PYTHON -c "import distutils" 2>&1` + ac_distutils_result=`$PYTHON2 -c "import distutils" 2>&1` if test -z "$ac_distutils_result"; then AC_MSG_RESULT([yes]) else @@ -143,32 +143,32 @@ variable to configure. See ``configure --help'' for reference. AC_MSG_ERROR([cannot import Python module "distutils". Please check your Python installation. The error was: $ac_distutils_result]) - PYTHON_VERSION="" + PYTHON2_VERSION="" fi # # Check for Python include path # AC_MSG_CHECKING([for Python include path]) - if test -z "$PYTHON_CPPFLAGS"; then - python_path=`$PYTHON -c "import distutils.sysconfig; \ + if test -z "$PYTHON2_CPPFLAGS"; then + python2_path=`$PYTHON2 -c "import distutils.sysconfig; \ print (distutils.sysconfig.get_python_inc ());"` - if test -n "${python_path}"; then - python_path="-I$python_path" + if test -n "${python2_path}"; then + python2_path="-I$python2_path" fi - PYTHON_CPPFLAGS=$python_path + PYTHON2_CPPFLAGS=$python2_path fi - AC_MSG_RESULT([$PYTHON_CPPFLAGS]) - AC_SUBST([PYTHON_CPPFLAGS]) + AC_MSG_RESULT([$PYTHON2_CPPFLAGS]) + AC_SUBST([PYTHON2_CPPFLAGS]) # # Check for Python library path # AC_MSG_CHECKING([for Python library path]) - if test -z "$PYTHON_LDFLAGS"; then + if test -z "$PYTHON2_LDFLAGS"; then # (makes two attempts to ensure we've got a version number # from the interpreter) - ac_python_version=`cat<]], @@ -316,7 +316,7 @@ EOD` for your distribution. The exact name of this package varies among them. ============================================================================ ]) - PYTHON_VERSION="" + PYTHON2_VERSION="" fi # diff --git a/m4/ax_python_devel_v3.m4 b/m4/ax_python_devel_v3.m4 new file mode 100644 index 0000000..e770632 --- /dev/null +++ b/m4/ax_python_devel_v3.m4 @@ -0,0 +1,325 @@ +# =========================================================================== +# http://www.nongnu.org/autoconf-archive/ax_python_devel.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PYTHON_DEVEL([version]) +# +# DESCRIPTION +# +# Note: Defines as a precious variable "PYTHON_VERSION". Don't override it +# in your configure.ac. +# +# This macro checks for Python and tries to get the include path to +# 'Python.h'. It provides the $(PYTHON_CPPFLAGS) and $(PYTHON_LDFLAGS) +# output variables. It also exports $(PYTHON_EXTRA_LIBS) and +# $(PYTHON_EXTRA_LDFLAGS) for embedding Python in your code. +# +# You can search for some particular version of Python by passing a +# parameter to this macro, for example ">= '2.3.1'", or "== '2.4'". Please +# note that you *have* to pass also an operator along with the version to +# match, and pay special attention to the single quotes surrounding the +# version number. Don't use "PYTHON_VERSION" for this: that environment +# variable is declared as precious and thus reserved for the end-user. +# +# This macro should work for all versions of Python >= 2.1.0. As an end +# user, you can disable the check for the python version by setting the +# PYTHON_NOVERSIONCHECK environment variable to something else than the +# empty string. +# +# If you need to use this macro for an older Python version, please +# contact the authors. We're always open for feedback. +# +# LICENSE +# +# Copyright (c) 2009 Sebastian Huber +# Copyright (c) 2009 Alan W. Irwin +# Copyright (c) 2009 Rafael Laboissiere +# Copyright (c) 2009 Andrew Collier +# Copyright (c) 2009 Matteo Settenvini +# Copyright (c) 2009 Horst Knorr +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program 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 General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 3 + +AU_ALIAS([AC_PYTHON_DEVEL_V3], [AX_PYTHON_DEVEL_V3]) +AC_DEFUN([AX_PYTHON_DEVEL_V3],[ + # + # Allow the use of a (user set) custom python version + # + AC_ARG_VAR([PYTHON2_VERSION],[The installed Python + version to use, for example '2.3'. This string + will be appended to the Python interpreter + canonical name.]) + + AC_PATH_PROG([PYTHON3],[python[$PYTHON3_VERSION]]) + if test -z "$PYTHON3"; then + AC_MSG_ERROR([Cannot find python$PYTHON3_VERSION in your system path]) + PYTHON3_VERSION="" + fi + + # + # Check for a version of Python >= 2.5.0 + # + AC_MSG_CHECKING([for a version of Python >= '2.5.0']) + ac_supports_python_ver=`$PYTHON3 -c "import sys; \ + ver = sys.version.split ()[[0]]; \ + print (ver >= '2.5.0')"` + if test "$ac_supports_python_ver" != "True"; then + if test -z "$PYTHON3_NOVERSIONCHECK"; then + AC_MSG_RESULT([no]) + AC_MSG_FAILURE([ +This version of the AC@&t@_PYTHON_DEVEL macro +doesn't work properly with versions of Python before +2.1.0. You may need to re-run configure, setting the +variables PYTHON_CPPFLAGS, PYTHON_LDFLAGS, PYTHON_SITE_PKG, +PYTHON_EXTRA_LIBS and PYTHON_EXTRA_LDFLAGS by hand. +Moreover, to disable this check, set PYTHON_NOVERSIONCHECK +to something else than an empty string. +]) + else + AC_MSG_RESULT([skip at user request]) + fi + else + AC_MSG_RESULT([yes]) + fi + + # + # if the macro parameter ``version'' is set, honour it + # + if test -n "$1"; then + AC_MSG_CHECKING([for a version of Python $1]) + ac_supports_python_ver=`$PYTHON3 -c "import sys; \ + ver = sys.version.split()[[0]]; \ + print (ver >= '$1')"` + if test "$ac_supports_python_ver" = "True"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([this package requires Python $1. +If you have it installed, but it isn't the default Python +interpreter in your system path, please pass the PYTHON_VERSION +variable to configure. See ``configure --help'' for reference. +]) + PYTHON3_VERSION="" + fi + fi + + # + # Check if you have distutils, else fail + # + AC_MSG_CHECKING([for the distutils Python package]) + ac_distutils_result=`$PYTHON3 -c "import distutils" 2>&1` + if test -z "$ac_distutils_result"; then + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot import Python module "distutils". +Please check your Python installation. The error was: +$ac_distutils_result]) + PYTHON3_VERSION="" + fi + + # + # Check for Python include path + # + AC_MSG_CHECKING([for Python include path]) + if test -z "$PYTHON3_CPPFLAGS"; then + python_path=`$PYTHON3 -c "import distutils.sysconfig; \ + print (distutils.sysconfig.get_python_inc ());"` + if test -n "${python_path}"; then + python_path="-I$python_path" + fi + PYTHON3_CPPFLAGS=$python_path + fi + AC_MSG_RESULT([$PYTHON3_CPPFLAGS]) + AC_SUBST([PYTHON3_CPPFLAGS]) + + # + # Check for Python library path + # + AC_MSG_CHECKING([for Python library path]) + if test -z "$PYTHON3_LDFLAGS"; then + # (makes two attempts to ensure we've got a version number + # from the interpreter) + ac_python_version=`cat<]], + [[Py_Initialize();]]) + ],[pythonexists=yes],[pythonexists=no]) + AC_LANG_POP([C]) + # turn back to default flags + CPPFLAGS="$ac_save_CPPFLAGS" + LIBS="$ac_save_LIBS" + + AC_MSG_RESULT([$pythonexists]) + + if test ! "x$pythonexists" = "xyes"; then + AC_MSG_FAILURE([ + Could not link test program to Python. Maybe the main Python library has been + installed in some non-standard library path. If so, pass it to configure, + via the LDFLAGS environment variable. + Example: ./configure LDFLAGS="-L/usr/non-standard-path/python/lib" + ============================================================================ + ERROR! + You probably have to install the development version of the Python package + for your distribution. The exact name of this package varies among them. + ============================================================================ + ]) + PYTHON3_VERSION="" + fi + + # + # all done! + # +]) diff --git a/proto/IBus.proto b/proto/IBus.proto new file mode 100644 index 0000000..05a16e1 --- /dev/null +++ b/proto/IBus.proto @@ -0,0 +1,61 @@ +syntax = "proto3"; + +package IBus; + +enum MessageType { + MSG_UNDEF = 0; + MSG_INIT = 1; + MSG_ACK = 2; + MSG_EOT = 3; + MSG_STATUS = 4; + MSG_RESUME = 5; + MSG_NAK = 6; +} + +enum StatusType { + STATUS_UNDEF = 0; + STATUS_OK = 1; + STATUS_NOT_FOUND = 2; +} + +enum ClientType { + CLIENT_UNDEF = 0; + CLIENT_DUMB = 1; +} + +message Type { + MessageType msgType = 1; // message type + string id = 2; // client identifier +} + +// Initial message +message InitMsg { + MessageType msgType = 1; // msgType=MSG_INIT + string id = 2; // client identifier + ClientType clientType = 3; // client type + uint64 ts = 4; // start time with historical=True, ts=0 means "everything" + bool live = 5; // goes live after historical (or immediately) + bool historical = 6; // request historical, makes startTime meaningful + bool reset = 7; // start afresh, deleting previous client information if any +} + +// Packet acknowledged message +message AckMsg { + MessageType msgType = 1; // msgType=MSG_ACK + string id = 2; // client identifier + uint64 ts = 3; // represents the acknowledged timestamp +} + +// End-of-transmission message +message EotMsg { + MessageType msgType = 1; // msgType=MSG_EOT + string id = 2; // client identifier +} + +// Status message +message StatusMsg { + MessageType msgType = 1; // msgType=MSG_STATUS + string id = 2; // client identifier + StatusType status = 3; // status +} + diff --git a/proto/OBus.proto b/proto/OBus.proto new file mode 100644 index 0000000..f93e3c6 --- /dev/null +++ b/proto/OBus.proto @@ -0,0 +1,215 @@ +syntax="proto3"; + +package OBus; + +option go_package = ".;service"; +option java_package = "com.ses.grpc.obus"; + +// Not implemented. +service FleetTMControlService { + rpc LoadReferenceLimits (Satellite) returns (Empty){} + rpc SelectOverlay (Overlay) returns (Empty){} + rpc SubmitLimitUpdate (LimitUpdate) returns (Empty){} +} + +// FleetTMService provides (streaming) functions to retrieve telemetry incl. limit information. +service FleetTMService { + // Waits for an update and returns the sample of the requested mnemonic or pid. + // Set requestCurrentValue to True in the request to return the current value instead. + rpc GetTelemetrySample (TelemetrySampleRequest) returns (TelemetrySample){} + // Streams sample updates of the subscribed mnemonics or pids. + // Set requestCurrentValue to True in the request to return the current values at the beginning. + // During runtime, the clients must keep the subscription thread running and update subscriptions as necessary. + rpc GetTelemetrySamples (stream TelemetryStreamRequest) returns (stream TelemetrySamples){} + // Returns the current value table (CVT) of all known samples. + rpc GetTelemetryCvt (TelemetryCvtRequest) returns (TelemetrySamples){} + + // Returns the current value table (CVT) of all known samples with limitVal > INFO. + rpc GetLimitCvt (TelemetryCvtRequest) returns (TelemetrySamples){} + // Streams sample updates where limit information is changing. + rpc GetLimitChanges (LimitStreamRequest) returns (stream TelemetrySamples){} + + // Not implemented. + rpc GetLimitDefinition (TelemetrySampleRequest) returns (LimitDefinition){} + rpc GetLimitDefinitions (stream TelemetryStreamRequest) returns (stream LimitDefinitions){} + + // Returns the status of available TM streams. + // A stream is flagged as stalled after 5 seconds of inactivity. + rpc GetTelemetryStatus(TelemetryStatusRequest) returns (TelemetryStatus){} +} + +enum STATEV { + VALID = 0; + INVALID = 1; + CRC_ERROR = 2; + INVALID_AND_CRC_ERROR = 3; + EXPIRED = 4; + INVALID_AND_EXPIRED = 5; + CRC_ERROR_AND_EXPIRED = 6; + INVALID_AND_CRC_ERROR_AND_EXPIRED = 7; +} + +enum TM_TYPE { + UNKNOWN = 0; + DOUBLE = 1; + LONG = 2; + STRING = 3; + UNSIGNED_LONG = 4; +} + +enum ERROR_CODES { + NONE = 0; + NOT_FOUND = 1; +} + +enum LIMIT { + NORMAL = 0; + INFO = 1; + WARNING = 2; + HAZARDOUS = 3; + CRITICAL = 4; + OOR = 5; +} + +message TelemetrySampleRequest { + string satelliteName = 1; // Mandatory + string seatName = 2; // Leave empty for real S/C + string mnemonic = 3; // Mandatory if pid is not set + bool requestCurrentValue = 4; // Default: False + uint32 pid = 5; // Mandatory if mnemonic is not set +} + +message TelemetryStreamRequest { + string satelliteName = 1; // Mandatory + string seatName = 2; // Leave empty for real S/C + // (subscribeMnemonics /\ unsubscribeMnemonics != {}) => (subscribePids /\ unsubscribePids == {}) + repeated string subscribeMnemonics = 3; + repeated string unsubscribeMnemonics = 4; + bool requestCurrentValue = 5; // Default: False + // (subscribePids /\ unsubscribePids != {}) => (subscribeMnemonics /\ unsubscribeMnemonics == {}) + repeated uint32 subscribePids = 6; + repeated uint32 unsubscribePids = 7; +} + +message TelemetryCvtRequest { + string satelliteName = 1; // Mandatory + string seatName = 2; // Leave empty for real S/C +} + +message LimitStreamRequest { + string satelliteName = 1; // Mandatory + string seatName = 2; // Leave empty for real S/C +} + +message TelemetryStatusRequest { +} + +message TelemetrySamples { + repeated TelemetrySample samples = 1; +} + +message AnalogLimit { + uint32 limitSet = 1; + uint32 hysteresis = 2; + double low = 3; + double high = 4; + LIMIT level = 5; + string conditionName = 6; + string conditionAlgo = 7; + int64 version = 8; +} + +message StatusLimit { + uint32 limitSet = 1; + uint32 hysteresis = 2; + int64 limitVal = 3; + LIMIT level = 4; + string conditionName = 5; + string conditionAlgo = 6; + int64 version = 7; +} + +message LimitDefinition { + repeated string mnemonic = 1; + map entries = 2; +} + +message LimitDefinitions { + repeated LimitDefinition definitions = 1; +} + +message LimitDefinitionEntry { + uint64 limitTS = 1; + string overlayName = 2; + + oneof LimitValues { + AnalogLimit analogLimit = 3; + StatusLimit statusLimit = 4; + } +} + +message TelemetrySample { + repeated string mnemonic = 1; + uint64 onboardTS = 2; + uint64 groundTS = 3; + double engV = 4; + int64 rawV = 5; + string calV = 6; + STATEV stateV = 7; + int32 strmID = 8; + int32 pktID = 9; + ERROR_CODES error = 10; + TM_TYPE tmType = 11; + oneof LimitValues { + AnalogLimit analogLimit = 12; + StatusLimit statusLimit = 13; + } + uint32 pid = 14; +} + +message Empty { +} + +message Overlay { + string satelliteName = 1; + string overlayName = 2; +} + +message Satellite { + string satelliteName = 1; + string seatName = 2; +} + +message LimitUpdate { + message AnalogLimit { + } + message StatusLimit { + } + string satelliteName = 1; + string mnemonic = 2; + repeated AnalogLimit updatedAnalogLimit = 3; + repeated StatusLimit updatedStatusLimit = 4; +} + +message Reply { + enum RETURN_CODE { + SUCCESS = 0; + ERROR = 1; + } + RETURN_CODE errorCode = 1; +} + +message TelemetryStreamInfo { + enum STREAM_STATUS { + NORMAL = 0; + STALLED = 1; // No updates within 5 seconds + } + string satelliteName = 1; + string seatName = 2; + STREAM_STATUS status = 3; + uint64 time = 4; // Last known reception time +} + +message TelemetryStatus { + repeated TelemetryStreamInfo info = 1; +} diff --git a/proto/SpacecraftInfo.proto b/proto/SpacecraftInfo.proto new file mode 100644 index 0000000..1af4727 --- /dev/null +++ b/proto/SpacecraftInfo.proto @@ -0,0 +1,8 @@ +syntax = "proto3"; + +message SpacecraftInfo { + uint32 msgType = 1; + uint32 scID = 2; + uint64 period = 3; // time between two frames at highest speed (nanoseconds) + uint32 totalFrames = 4; // number of minor frames in a major frame +} diff --git a/proto/TelemetryFrame.proto b/proto/TelemetryFrame.proto new file mode 100644 index 0000000..fd365a0 --- /dev/null +++ b/proto/TelemetryFrame.proto @@ -0,0 +1,57 @@ +syntax = "proto3"; + +message TelemetryPacket { + uint32 vcID = 1; + uint32 apID = 2; + uint32 sequenceCounter = 3; + uint32 length = 4; + bool pecfValid = 5; + bool pusHeader = 6; + uint32 pusService = 7; + uint32 pusSubservice = 8; + uint32 destination = 9; + uint32 subCounter = 10; + uint64 onboardTS = 11; + bytes rawPacket = 100; +} + +message TelemetryPoint { + uint32 pID = 1; + uint64 onboardTS = 2; + double engV = 3; + int64 rawV = 4; + string calV = 5; + + enum STATEV { + VALID = 0; + INVALID = 1; + CRC_ERROR = 2; + INVALID_AND_CRC_ERROR = 3; + EXPIRED = 4; + INVALID_AND_EXPIRED = 5; + CRC_ERROR_AND_EXPIRED = 6; + INVALID_AND_CRC_ERROR_AND_EXPIRED = 7; + } + + STATEV stateV = 6; + int32 pktID = 7; +} + +message TelemetryFrame { + uint32 msgType = 1; + uint32 scID = 2; + + enum STREAMID { + STREAM1 = 0; + STREAM2 = 1; + } + + STREAMID streamID = 3; + uint32 frameCounter = 4; + uint64 groundTS = 5; + uint32 familyID = 6; + + repeated bytes rawFrame = 98; + repeated TelemetryPacket packet = 99; + repeated TelemetryPoint point = 100; +} diff --git a/proto/TelemetryFrameLt.proto b/proto/TelemetryFrameLt.proto new file mode 100644 index 0000000..10249be --- /dev/null +++ b/proto/TelemetryFrameLt.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +message TelemetryFrame { + uint32 msgType = 1; + uint32 scID = 2; + + enum STREAMID { + STREAM1 = 0; + STREAM2 = 1; + } + + STREAMID streamID = 3; + uint32 frameCounter = 4; + uint64 groundTS = 5; + uint32 familyID = 6; +} diff --git a/proto/alerts.proto b/proto/alerts.proto new file mode 100644 index 0000000..42d66ac --- /dev/null +++ b/proto/alerts.proto @@ -0,0 +1,31 @@ +syntax = "proto3"; + +import "header.proto"; + +package Alerts; + +enum SeverityType { + SEV_NORMAL = 0; + SEV_INFO = 1; + SEV_WARNING = 2; + SEV_HAZARDOUS = 3; + SEV_CRITICAL = 4; +}; + +message Alert { + string machineName = 1; // short name (lowercase) of the machine (p1anal01, p1cntl, opsb-ses10tec1, etc.) + uint64 eventTS = 2; // timestamp of the event, represents the number of Nano-seconds since Unix epoch + string appName = 3; // identifier of the application that generated the message + SeverityType severity = 4; // severity of the alert (refer to SeverityType definition) + string label = 5; // sentence to describe the alert, one line (no linefeed) + string eventData = 6; // additional data as per application defined pattern (no free text, strict template to be published) +}; + +message Item { + Messages.Header header = 1; + + oneof these { + Alert alert = 8; + }; +}; + diff --git a/proto/header.proto b/proto/header.proto new file mode 100644 index 0000000..873a3dd --- /dev/null +++ b/proto/header.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package Messages; + +message Header { + string topic = 1; // topic identifier + uint64 publisherID = 2; // unique identifier of the publisher, 0 if not publishing + uint32 scID = 3; // satellite identifier as per SATDB + uint32 familyID = 4; // family identifier as per SATDB + string key = 5; // key +}; + diff --git a/proto/limits.proto b/proto/limits.proto new file mode 100644 index 0000000..895cc7f --- /dev/null +++ b/proto/limits.proto @@ -0,0 +1,28 @@ +syntax = "proto3"; + +package Limits; + +import "header.proto"; + +message DefinitionUpdate { + string url = 1; // URL of the new overlay or reference database + string filename = 2; // filename, e.g. main + uint32 version = 3; // version number + uint32 scID = 4; // SCID as per SATDB + uint64 ts = 5; // update timestamp +}; + +message Fileset { + uint64 ts = 1; // update timestamp + repeated string urls = 2; // Ordered list of URLs of the databases +}; + +message Item { + Messages.Header header = 1; + + oneof these { + DefinitionUpdate limit_definition_update = 6; + Fileset fileset = 7; + }; +}; + diff --git a/proto/messages.proto b/proto/messages.proto new file mode 100644 index 0000000..b22af4c --- /dev/null +++ b/proto/messages.proto @@ -0,0 +1,24 @@ +syntax = "proto3"; + +import "header.proto"; +import "alerts.proto"; +import "limits.proto"; +import "msgbus.proto"; + +package Messages; + +message Item { + Header header = 1; + + oneof these { + MsgBus.CreateNewTopic newtopic = 2; + MsgBus.RequestValues request_values = 3; + MsgBus.KeyList keylist = 4; + MsgBus.Status status = 5; + Limits.DefinitionUpdate limit_definition_update = 6; + Limits.Fileset fileset = 7; + Alerts.Alert alert = 8; + MsgBus.RequestType request = 9; + }; +}; + diff --git a/proto/msgbus.proto b/proto/msgbus.proto new file mode 100644 index 0000000..70e4fb7 --- /dev/null +++ b/proto/msgbus.proto @@ -0,0 +1,47 @@ +syntax = "proto3"; + +import "header.proto"; + +package MsgBus; + +enum StatusType { + STATUS_UNDEF = 0; // DO NOT USE + STATUS_TOPIC_NOT_FOUND = 2; + STATUS_KEY_NOT_FOUND = 3; + STATUS_EOT = 4; +}; + +enum RequestType { + REQ_KEYLIST = 0; +} + +message KeyList { + repeated string keys = 1; // key list +}; + +message Status { + StatusType status = 1; + string info = 2; +}; + +// Topic creation +message CreateNewTopic { + uint32 queueSize = 1; // retention size (1 for last value only, more for history) +}; + +// Request value(s) from a topic +message RequestValues { + repeated string keys = 1; // keys +}; + +message Item { + Messages.Header header = 1; + + oneof these { + CreateNewTopic newtopic = 2; + RequestValues request_values = 3; + KeyList keylist = 4; + Status status = 5; + RequestType request = 9; + }; +}; diff --git a/proto/tmt.proto b/proto/tmt.proto new file mode 100644 index 0000000..a4bdb68 --- /dev/null +++ b/proto/tmt.proto @@ -0,0 +1,139 @@ +syntax = "proto3"; + +package tmtdata; + +option java_package = "com.ses.tmt.data.codec.protoc"; +option java_outer_classname = "TmTProtocCodec"; + +enum MessageType { + UNDEF_TYPE = 0; + REQ = 1; + DATA = 2; + END = 3; + HBEAT = 4; + JOIN = 5; + PROCESSING_DONE = 6; + RECONNECT = 7; +} + +enum STATEV { + VALID = 0; + INVALID = 1; + CRC_ERROR = 2; + INVALID_AND_CRC_ERROR = 3; + EXPIRED = 4; + INVALID_AND_EXPIRED = 5; + CRC_ERROR_AND_EXPIRED = 6; + INVALID_AND_CRC_ERROR_AND_EXPIRED = 7; +} + +enum ErrorCode { + UNDEF_ERROR = 0; + NO_ERROR = 1; + UNKNOWN_SAT = 2; + UNKNOWN_SEAT = 3; + MAX_PIDS_EXCEEDED = 4; + INVALID_PID = 5; + START_AFTER_END = 6; + START_AFTER_NOW = 7; + WRONG_LEVELID = 8; + INVALID_PROTOCOL = 9; + SERVER_ERROR = 10; + HBEAT_TIMEOUT = 11; + BAD_REQUEST = 12; + CANCELED = 13; + REQUEST_EXPIRED = 14; + UNKNOWN_SAT_DRIVER_MAPPING = 15; +} + +enum ReqCode { + NO_REQ_CODE = 0; + NO_UPDATE_VALUE = 1; + INITIAL_VALUES = 2; + INITIAL_VALUES_VALID = 3; +} + +enum LIMIT { + NORMAL = 0; + INFO = 1; + WARNING = 2; + HAZARDOUS = 3; + CRITICAL = 4; + OOR = 5; +} + +message Header { + MessageType msgType = 1; +} + +message ReadyWorker { + string workerName = 1; + string workerHost = 2; + string workerState = 3; + string workerData = 4; + string workerRequest = 5; + string workerHb = 6; +} + +message ProcessingDone { + string workerName = 1; + string clientId = 2; +} + +message HeartBeat { + string workerName = 1; + uint32 hbCount = 2; + float cpuLoad = 3; +} + +message HisRequest { + string satName = 1; + string seatName = 2; + ReqCode reqCode = 3; + repeated uint32 pids = 4; + int64 startTime = 5; + int64 endTime = 6; + int32 level = 7; + bool withOol = 8; + bool withRaw = 9; +} + +message End { + ErrorCode errorCode = 1; +} + +message Ool { + LIMIT level = 1; + uint32 limitSet = 2; +} + +message TmSample { + int64 rawV = 1; + double engV = 2; + Ool ool = 3; +} + +message TmStats { + double mean = 1; + double min = 2; + double max = 3; +} + +message TmData { + uint64 time = 1; + uint32 pid = 2; + STATEV stateV = 3; + oneof data { + TmSample tmSample = 4; + TmStats tmStats = 5; + } +} + +message TmDataSet { + repeated TmData tmData = 1; +} + +message TmDataSetPacket { + repeated TmDataSet tmDataSet = 1; +} + diff --git a/protos/SPELLDriver.proto b/protos/SPELLDriver.proto new file mode 100644 index 0000000..7b3d2f9 --- /dev/null +++ b/protos/SPELLDriver.proto @@ -0,0 +1,566 @@ +syntax = "proto3"; + +package spell.driver; + +import "google/protobuf/empty.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/wrappers.proto"; + +option java_multiple_files = true; +option java_package = "lu.ses.engineering.spell.grpc.driver"; +option java_outer_classname = "SPELLDriverProto"; +option go_package = "driver"; + +service SPELLDriverInterface { + rpc GetDriverInfo (DriverInfo) returns (DriverInfo); + rpc Ping (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Close (google.protobuf.Empty) returns (google.protobuf.Empty); +} + +service SPELLDriverInterface_TM { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc DatabaseLookup (LookupRequest) returns (LookupAnswer); + //Load a set of limits definitions for a given parameter, from a given file. + // + //param: telemetry parameter name + //limits: file with limit definitions + //config: configuration parameters + // returns True/False for Success +// rpc LoadLimits (google.protobuf.Empty) returns (google.protobuf.BoolValue); + //Modify an Out Of Limits definition for a given telemetry parameter + // + //param: the telemetry parameter name + //limits: dictionary containing the limit parameters + //config: configuration dictionary with modifiers + // + //Relevant modifiers are: + // + //- Select=ACTIVE/ALL/: indicates which definitions should be affected + // (all, only the active ones, or the one indicated by + // the string) + // + //Limit parameters dictionary may contain the following: + // + //LoRed,LoYel,HiRed,HiYel: indicate the hard and soft limit values + // + //Midpoint,Tolerance: indicate a middle limit point and a tolerance that is + // used to calculate the upper and lower limits (M+T, M-T) + // Hard and soft limits are considered equal. + // + //Nominal,Earning,Error,Ignore: the values are lists containing the values + // assigned to each category, e.g. Nominal:['ValueA','ValueB'] + // + //Delta: set a step or spike limit + rpc SetLimits (LimitSetRequest) returns (google.protobuf.BoolValue); + //Obtain an Out Of Limits definition for a given telemetry parameter + // + //param: the telemetry parameter name + //config: configuration dictionary with modifiers + // + //Relevant modifiers are: + // + //- Select=ACTIVE/ALL/: indicates which definitions should be considered + // (all, only the active ones, or the one indicated by + // the string) + // + //The limit definition to be returned may be one of the following: + // + //{LoRed,LoYel,HiRed,HiYel}: indicate the hard and soft limit values + // + //{Nominal,Earning,Error,Ignore}: the values are lists containing the values + // assigned to each category, e.g. Nominal:['ValueA','ValueB'] + // + //{Delta}: step or spike limit + rpc GetLimits (TmItemValue) returns (LimitSet); + rpc IsAlarmed (TmItemValue) returns (google.protobuf.BoolValue); + //Inject a ground value for a TM parameter. + // + //tmItem : instance of TmItemClass with the parameter information + //value : contains the required value + //config : configuration dictionary with modifiers. + // returns True/False for success + rpc InjectItem (TmItemValue) returns (google.protobuf.BoolValue); + //Acquire the value for a TM parameter. + // + //tmItem : instance of TmItemClass with the parameter information + //config : configuration dictionary with modifiers. + // + //Relevant modifiers are + // + //- Wait=True/False: when True, the system shall block the caller until + //a new sample of the required parameter arrives to the GCS. If False, + //the last recorded value of the parameter shall be returned immediately. + // + //- ValueFormat=RAW/ENG: if RAW is given, the raw or uncalibrated value + //of the parameter shall be given. If ENG is used, the engineering or + //calibrated value shall be returned. + // + //- Timeout=(TIME): maximum amount of time to wait for new samples, + //before the parameter is declared as impossible to acquire. It only + //makes sense when Wait=True. + // + //Returned value: a list with the value and True/False indicating the validity + //of the parameter + rpc RefreshItem (TmItemValue) returns (TmItemValue); + rpc RestoreNormalLimits (google.protobuf.Any) returns (google.protobuf.BoolValue); + //Instantiate a telemetry parameter (usually taking the information from GCS) + // + //The item shall be derived from TmItemClass class. + // + //If not overriden, the adapter class provides a generic TM item structure. + rpc CreateTmItem (TmItemDesc) returns (TmItemDesc); +} + + +message LookupRequest { + string name = 1; + string type = 2; + string source = 3; +} + +message LookupAnswer { + google.protobuf.Any lookup = 1; +} + +message LimitSetRequest { + TmItemDesc item = 1; + LimitSet set = 2; +} + +message LimitSet { + google.protobuf.Any limitset = 1; +} + +message TmItemValue { + TmItemDesc item = 1; + bool validity = 3; + google.protobuf.Any engValue = 4; + google.protobuf.Any rawValue = 5; + string timestamp = 6; + google.protobuf.Any config = 7; +} + +message TmItemDesc { + string mnemonic = 1; + string description = 2; + string id = 3; +} + +service SPELLDriverInterface_TC { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc CreateTcItem (TcItemDesc) returns (TcItemDesc); + + //Inject a block of TC items into the GCS. Blocked items are injected + //and transmitted as a single TC frame. + // + //tcItemList : list of TcItemClass instance with the command information. + //config : configuration dictionary + rpc SendBlock (CommandList) returns (google.protobuf.BoolValue); + //Inject a group of TC items into the GCS. + // + //tcItemList : list of TcItemClass instance with the command information. + //config : configuration dictionary + rpc SendList (CommandList) returns (google.protobuf.BoolValue); + //Inject a TC item into the GCS. + // + //tcItem : TcItemClass instance with the command information. + //config : configuration dictionary + rpc SendCommand (CommandDesc) returns (google.protobuf.BoolValue); + +// rpc CheckCriticalCommands (google.protobuf.Empty) returns (google.protobuf.Empty); + +} + +message CommandList { + repeated CommandDesc commands = 1; + google.protobuf.Any config = 2; +} + +message TcItemDesc { + string mnemonic = 1; + string description = 2; + string id = 3; +} + +message CommandDesc { + TcItemDesc command = 1; + google.protobuf.Any args = 2; + google.protobuf.Any config = 3; +} + +service SPELLDriverInterface_EV { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + // Invoked by the system in order to inject an event into the GCS. + // + // message: string containing the event message + // config: dictionary containing the event parameters: + // - Severity modifier (INFORMATION/WARNING/ERROR/FATAL) + // - Scope (SCOPE_PROC,SCOPE_SYS,SCOPE_CFG) + rpc RaiseEvent (Event) returns (google.protobuf.Empty); + //NOT USED AT THE MOMENT! + rpc RegisterForEvents (google.protobuf.Empty) returns (stream google.protobuf.Empty); + //NOT USED AT THE MOMENT! + rpc UnRegisterForEvents (google.protobuf.Empty) returns (google.protobuf.Empty); + //NOT USED AT THE MOMENT! + rpc PullEvents (google.protobuf.Empty) returns (google.protobuf.Empty); +} + +message Event { + string message = 1; + google.protobuf.Any config = 2; +} + +service SPELLDriverInterface_PCS { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc CreatePtcItem (PtcItemDesc) returns (PtcItemDesc); + + rpc PCS_GetStatus (google.protobuf.Any) returns (google.protobuf.Any); + rpc PCS_IsArqEnabled (google.protobuf.Any) returns (google.protobuf.BoolValue); + rpc PCS_IsVerifyModeEnabled (google.protobuf.Any) returns (google.protobuf.BoolValue); + rpc PCS_ReadyToCommand (google.protobuf.Any) returns (google.protobuf.StringValue); + rpc PCS_AbortPtc (google.protobuf.Any) returns (google.protobuf.BoolValue); + + rpc SendPtcCommand (PtcDesc) returns (PtcResult); + rpc SendPtcList (PtcList) returns (PtcResult); + rpc PCS_Control (PtcDesc) returns (PtcResult); + +} +message PtcItemDesc { + string mnemonic = 1; + string description = 2; + string id = 3; +} +message PtcList { + repeated PtcDesc commands = 1; + google.protobuf.Any config = 2; +} + +message PtcDesc { + PtcItemDesc command = 1; + google.protobuf.Any args = 2; + google.protobuf.Any config = 3; +} + +message PtcResult { + repeated int64 code = 1; + repeated bool success = 2; + repeated string stage = 3; + repeated string data = 4; + repeated string error = 5; +} + +service SPELLDriverInterface_MEM { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc GenerateReport (MemoryReportRequest) returns (google.protobuf.BoolValue); + rpc CompareImages (CompareMemoryReportRequest) returns (google.protobuf.BoolValue); + rpc CreateGoldenImage (GoldenImageRequest) returns (google.protobuf.BoolValue); + rpc MemoryLookup (MemoryLookupRequest) returns (google.protobuf.Any); + rpc DataObjectLookup (DataObjectLookupRequest) returns (google.protobuf.Any); + rpc DataItemLookup (DataItemLookupRequest) returns (google.protobuf.Any); +} + +message MemoryReportRequest { + string image = 1; + string report_type = 2; + string begin = 3; + string end = 4; + string source = 5; + google.protobuf.Any config = 6; +} + +message CompareMemoryReportRequest { + string image1 = 1; + string image2 = 2; + string report_type = 3; + string begin = 4; + string end = 5; + string source = 6; + google.protobuf.Any config = 7; +} + +message GoldenImageRequest { + string scp = 1; + string filename = 2; + google.protobuf.Any config = 3; +} + +message MemoryLookupRequest { + string name = 1; + string address = 2; + string image = 3; + string report_type = 4; + string source = 5; + google.protobuf.Any config = 6; +} + +message DataObjectLookupRequest { + string name = 1; + string type = 2; + google.protobuf.Any config = 3; +} + +message DataItemLookupRequest { + string name = 1; + string type = 2; + string source = 3; + string valformat = 4; + google.protobuf.Any config = 5; +} + +service SPELLDriverInterface_RSC { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + + // Enable or disable a GCS link (TM, TC or others) + // + // name: link identifier + // enable: True/False, indicates if the link needs to be enabled or disabled + // config: configuration modifiers + rpc SetLink (LinkRequest) returns (google.protobuf.BoolValue); + // Check if a GCS link (TM, TC or others) is enabled or disabled + // + // name: link identifier + // config: configuration modifiers + rpc CheckLink (LinkRequest) returns (google.protobuf.BoolValue); + // Assign the value of one resource or configuration parameter in the GCS + // + // name: resource identifier + // value: resource value + // config: configuration modifiers + rpc SetResource (ResourceRequest) returns (google.protobuf.BoolValue); + // Acquire the value of one resource or configuration parameter in the GCS + // + // name: resource identifier + // config: configuration modifiers + rpc GetResource (ResourceRequest) returns (ResourceRequest); + // Acquire the name-value map of all resource or configuration parameters + // in the GCS + // + // name: resource identifier + // config: configuration modifiers + rpc GetResources (ResourcesRequest) returns (ResourcesRequest); + // Acquire the current status of one resource or configuration parameter + // in the GCS + // + // name: resource identifier + // config: configuration modifiers + rpc GetResourceStatus (ResourceRequest) returns (ResourceRequest); + // Check if the current value of one resource or configuration parameter + // in the GCS is correct or incorrect + // + // name: resource identifier + // config: configuration modifiers + rpc IsResourceOK (ResourceRequest) returns (google.protobuf.BoolValue); +} + +message LinkRequest { + string name = 1; + bool status = 2; + google.protobuf.Any config = 3; +} + +message ResourceRequest { + string name = 1; + google.protobuf.Any value = 2; + google.protobuf.Any config = 3; +} + +message ResourcesRequest { + google.protobuf.Any value = 2; + google.protobuf.Any config = 3; +} + +service SPELLDriverInterface_TASK { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + // Invoked by the system in order to stop a GCS task. + // + // name: string containing the task name + // config: configuration dictionary with modifiers. + rpc StartTask (Task) returns (google.protobuf.BoolValue); + // Invoked by the system in order to stop a GCS task. + // + // name: string containing the task name + // config: configuration dictionary with modifiers. + rpc StopTask (Task) returns (google.protobuf.BoolValue); + // Invoked by the system in order to check the status of a GCS task. + // + // name: string containing the task name + // config: configuration dictionary with modifiers. + rpc CheckTask (Task) returns (google.protobuf.Int32Value); + // Invoked by the system in order to open a telemetry display on the GCS + // + // displayName: name or identifier of the display + // config: configuration dictionary with modifiers. + // + // Relevant modifiers are: + // + // Host: the machine name where the display should be open. + rpc OpenDisplay (Display) returns (google.protobuf.Empty); + // Invoked by the system in order to print a telemetry display from the GCS + // + // displayName: name or identifier of the display + // config: configuration dictionary with modifiers. + // + // Relevant modifiers are: + // + // Printer: the printer name + // Format: PS (postcript) or VECTOR (ASCII text, for alphanumeric displays only) + rpc PrintDisplay (Display) returns (google.protobuf.Empty); + // Invoked by the system in order to close a telemetry display on the GCS + // + // displayName: name or identifier of the display + // config: configuration dictionary with modifiers. + // + // Relevant modifiers are: + // + // Host: the machine name where the display should be closed. + rpc CloseDisplay (Display) returns (google.protobuf.Empty); +} + +message Task { + string name = 1; + google.protobuf.Any args = 2; + google.protobuf.Any config = 3; +} + +message Display { + string name = 1; + google.protobuf.Any config = 2; +} + +service SPELLDriverInterface_RNG { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + + rpc EnableRanging (Ranging) returns (google.protobuf.BoolValue); + rpc DisableRanging (Ranging) returns (google.protobuf.BoolValue); + rpc AbortRanging (Ranging) returns (google.protobuf.BoolValue); + rpc StartRanging (Ranging) returns (google.protobuf.BoolValue); + rpc StartCalibration (Ranging) returns (google.protobuf.BoolValue); + + rpc GetBasebandConfig (BaseBandConfig) returns (BaseBandConfig); + rpc SetBasebandConfig (BaseBandConfig) returns (google.protobuf.BoolValue); + + rpc GetAntennaNames (google.protobuf.Any) returns (Names); + rpc GetRangingPaths (google.protobuf.Any) returns (Names); + rpc GetBasebandNames (google.protobuf.Any) returns (Names); + rpc GetRangingStatus (google.protobuf.Any) returns (google.protobuf.StringValue); + rpc UpdateCortexConf (google.protobuf.Any) returns (google.protobuf.BoolValue); +} + +message Ranging { + string bbe = 1; + string antenna = 2; + google.protobuf.Any config = 3; +} + +message BaseBandConfig { + string bbe = 1; + string param = 2; + string value = 4; + google.protobuf.Any config = 3; +} + +message Names { + repeated string names = 1; +} + +service SPELLDriverInterface_USER { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + // Login the given user in the GCS system. + // + // username: user name + // password: the user password + // config: configuration dictionary with modifiers. + rpc Login (LoginCred) returns (google.protobuf.BoolValue); + // Logout the given user from the GCS system. + // + // username: user name + // config: configuration dictionary with modifiers. + rpc Logout (LoginCred) returns (google.protobuf.BoolValue); + // Check if the given user is logged in the GCS system. + // + // username: user name + // config: configuration dictionary with modifiers. + rpc IsLoggedIn (LoginCred) returns (google.protobuf.BoolValue); +} + +message LoginCred { + string username = 1; + string password = 2; + google.protobuf.Any config = 3; +} + +service SPELLDriverInterface_TIME { + rpc Setup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Cleanup (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Interrupt (google.protobuf.Empty) returns (google.protobuf.Empty); + // Obtain the current UTC time according to the GCS time. If the present + // example is not change, the interface provices the current system local + // time. + // + // config: SPELL configuration dictionary. + rpc GetUTC (google.protobuf.Any) returns (Time); +} + +message Interfaces { + // Dummy message just for the namespace + enum DriverInterface { + TM = 0; + TC = 1; + EV = 2; + PCS = 3; + MEM = 4; + RSC = 5; + TASK = 6; + RNG = 7; + USER = 8; + CONFIG = 9; + TIME = 10; + } +} + +message DriverInfo { + string host = 1; + uint32 port = 2; + string driverName = 3; + repeated Interfaces.DriverInterface interfaces = 4; +} + +message List { + repeated google.protobuf.Any entries = 1; +} + +message Dict { + repeated DictEntry entries = 1; +} + +message Time { + string timeMsg = 1; +} + +message DictEntry { + google.protobuf.Any key = 1; + google.protobuf.Any value = 2; +} diff --git a/protos/SPELLExecutor.proto b/protos/SPELLExecutor.proto new file mode 100644 index 0000000..63db07b --- /dev/null +++ b/protos/SPELLExecutor.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; + +package spell.core; + +import "SPELLDriver.proto"; +import "google/protobuf/empty.proto"; +import "google/protobuf/wrappers.proto"; + +option java_multiple_files = true; +option java_package = "lu.ses.engineering.spell.grpc.executor"; +option java_outer_classname = "SPELLExecutorProto"; +option go_package = "driver"; + +service SPELLExecutor { + rpc DriverLogin(driver.DriverInfo) returns (ExecutorCapability); + + rpc Ping (google.protobuf.Empty) returns (google.protobuf.Empty); + rpc Close (google.protobuf.Empty) returns (google.protobuf.Empty); +} + +message ExecutorCapability { + bool providesSpellAccess = 1; + bool providesControlAccess = 2; + bool providesMonitorAccess = 3; +} diff --git a/protos/SPELLService.proto b/protos/SPELLService.proto new file mode 100644 index 0000000..012a7fd --- /dev/null +++ b/protos/SPELLService.proto @@ -0,0 +1,16 @@ +syntax = "proto3"; + +package spell.service; + +import "google/protobuf/empty.proto"; +import "google/protobuf/any.proto"; +import "google/protobuf/wrappers.proto"; + +option java_multiple_files = true; +option java_package = "lu.ses.engineering.spell.service"; +option java_outer_classname = "SPELLServiceProto"; +option go_package = "service"; + +service SPELLServiceRegistry { + +} diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 4242a27..f020941 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -1,29 +1,36 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ -include ${top_srcdir}/common.mk +SRC=${srcdir}/src +DST=${exec_prefix}/bin -SUBDIRS=linux +install-exec-hook: + mkdir -p ${exec_prefix}/bin + cp -u ${SRC}/setup-environment.sh ${DST} + cp -u ${SRC}/SPELL-Driver ${DST} + cp -u ${SRC}/SPELL-Server ${DST} + cp -u ${SRC}/SPELL-Server-Docker ${DST} + cp -u ${SRC}/Kill-Server ${DST} + cp -u ${SRC}/Shell ${DST} +if WITH_PACE + cp -u ${SRC}/PACE-Server ${DST} + cp -u ${SRC}/Kill-PACE ${DST} +endif diff --git a/scripts/src/Kill-PACE b/scripts/src/Kill-PACE new file mode 100644 index 0000000..12b0903 --- /dev/null +++ b/scripts/src/Kill-PACE @@ -0,0 +1,74 @@ +#!/bin/bash +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +function terminate_gracefully() { + PID=$1 + TRIES=10 + echo "Terminating process $PID" + # terminate + kill $PID > /dev/null 2>&1 + for i in $(seq 1 $TRIES); do + sleep 1 + if ps -p $PID > /dev/null; then + echo "Process $PID is still running (attempt #$i out of $TRIES)" + else + echo "Process $PID exited gracefully" + break + fi + done + # kill it + if ps -p $PID > /dev/null; then + echo "Could not terminate process $PID gracefully, killing it" + kill -9 $PID > /dev/null 2>&1 + fi +} + +# Satellite +SATELLITE=$1 +[ -z $SATELLITE ] && echo "Must provide the spacecraft: $0 " && exit 1 + +# SPELL home +if [ -z $SPELL_HOME ]; then + export SPELL_HOME=/home/spell/SPELL +fi +[ ! -d $SPELL_HOME ] && echo "ERROR: cannot find SPELL home directory: $SPELL_HOME" && exit 1 + +# Set the server configuration file to be used +if [ -z $SPELL_CONFIG ]; then + SPELL_CONFIG=/home/spell/SPELL_CONFIG +fi +CFG_FILE=$SPELL_CONFIG/server/server_$SATELLITE.xml +[ ! -f $CFG_FILE ] && echo "ERROR: cannot find configuration file: $CFG_FILE" && exit 1 + +echo "Terminating all PACE processes started with config ${CFG_FILE}" + +PROCESSES="PACE" +for P in $PROCESSES; do + PIDS=(`pgrep -u $USER -f "$P .*-c ${CFG_FILE}.*"`) + if [ ${#PIDS[@]} == 0 ]; then + continue + fi + echo "Terminating ${#PIDS[@]} $P instance(s)" + for PID in ${PIDS[@]}; do + terminate_gracefully $PID + done +done diff --git a/scripts/src/Kill-Server b/scripts/src/Kill-Server new file mode 100644 index 0000000..010095d --- /dev/null +++ b/scripts/src/Kill-Server @@ -0,0 +1,77 @@ +#!/bin/bash +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +function terminate_gracefully() { + PID=$1 + TRIES=10 + echo "Terminating process $PID" + # terminate + kill $PID > /dev/null 2>&1 + for i in $(seq 1 $TRIES); do + sleep 1 + if ps -p $PID > /dev/null; then + echo "Process $PID is still running (attempt #$i out of $TRIES)" + else + echo "Process $PID exited gracefully" + break + fi + done + # kill it + if ps -p $PID > /dev/null; then + echo "Could not terminate process $PID gracefully, killing it" + kill -9 $PID > /dev/null 2>&1 + fi +} + +# Satellite +SATELLITE=$1 +[ -z $SATELLITE ] && echo "Must provide the spacecraft: $0 " && exit 1 + +# SPELL home +if [ -z $SPELL_HOME ]; then + export SPELL_HOME=/home/spell/SPELL +fi +[ ! -d $SPELL_HOME ] && echo "ERROR: cannot find SPELL home directory: $SPELL_HOME" && exit 1 + +# Set the server configuration file to be used +if [ -z $SPELL_CONFIG ]; then + SPELL_CONFIG=/home/spell/SPELL_CONFIG +fi +CFG_FILE=$SPELL_CONFIG/server/server_$SATELLITE.xml +[ ! -f $CFG_FILE ] && echo "ERROR: cannot find configuration file: $CFG_FILE" && exit 1 + +echo "Terminating SPELL processes started with config ${CFG_FILE}" + +PROCESSES="SPELL-Executor SPELL-ExecutorCmd SPELL-Shell SPELL-Executor_v3 SPELL-ExecutorCmd_v3 SPELL-Shell_v3 SPELL-Context SPELL-Listener" +if [[ "$2" != "skipservice" ]]; then + PROCESSES="SPELL-Service $PROCESSES" +fi +for P in $PROCESSES; do + PIDS=(`pgrep -u $USER -f "$P .*-c ${CFG_FILE}.*"`) + if [ ${#PIDS[@]} == 0 ]; then + continue + fi + echo "Terminating ${#PIDS[@]} $P instance(s)" + for PID in ${PIDS[@]}; do + terminate_gracefully $PID + done +done diff --git a/scripts/src/PACE-Server b/scripts/src/PACE-Server new file mode 100644 index 0000000..4d1c25b --- /dev/null +++ b/scripts/src/PACE-Server @@ -0,0 +1,70 @@ +#!/bin/bash +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +#=============================================================================== +# Setup main environment variables +#=============================================================================== + +# Satellite +SATELLITE=$1 +[ -z $SATELLITE ] && echo "Must provide the spacecraft: $0 " && exit 1 + +# Set SPELL home +if [ -z $SPELL_HOME ]; then + export SPELL_HOME=/home/spell/SPELL +fi +[ ! -d $SPELL_HOME ] && echo "ERROR: cannot find SPELL home directory: $SPELL_HOME" && exit 1 + +#=============================================================================== +# Setup environment +#=============================================================================== +SETUP_SCRIPT=$SPELL_HOME/bin/setup-environment.sh +[ ! -f $SETUP_SCRIPT ] && echo "ERROR: cannot find environment setup script: $SETUP_SCRIPT" && exit 1 +! source $SETUP_SCRIPT && echo "ERROR: SPELL environment setup failed" && exit 1 + +#=============================================================================== +# Launcher configuration +#=============================================================================== +APP_PATH=$SPELL_HOME/bin/PACE +CFG_PATH=$SPELL_CONFIG/server/server_$SATELLITE.xml +[ ! -f $CFG_PATH ] && echo "ERROR: could not find configuration file: $CFG_PATH" && exit 1 +[ ! -f $APP_PATH ] && echo "ERROR: could not find application: $APP_PATH" && exit 1 + +#=============================================================================== +# Pre-execution cleanup +#=============================================================================== +echo "Cleanup of previous PACE instances" +$SPELL_HOME/bin/Kill-PACE $SATELLITE + +#=============================================================================== +# Execute application +#=============================================================================== +VERSION=$($APP_PATH -version) +echo "Starting $VERSION server for $SATELLITE" +if [[ ( "$USER" == "hifly_admin" ) && ( -f $BIN_DIR/APPLInotify ) ]] +then + echo "Notifying hifly..." + $BIN_DIR/APPLInotify start $$ + exec $APP_PATH -c $CFG_PATH -n $SATELLITE && $BIN_DIR/APPLInotify stop $$ +else + nohup $APP_PATH -c $CFG_PATH -n $SATELLITE >> $SPELL_LOG/pace_$SATELLITE.log 2>&1 & +fi diff --git a/scripts/src/SPELL-Compile b/scripts/src/SPELL-Compile new file mode 100644 index 0000000..ad18786 --- /dev/null +++ b/scripts/src/SPELL-Compile @@ -0,0 +1,37 @@ +#!/usr/bin/env python +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +import py_compile, sys + +if len(sys.argv) not in [2, 3]: + print("please provide an input file and optionally an output file") + sys.exit(1) + +outFile = '/tmp/ignore.py' +if len(sys.argv) == 3: + outFile = sys.argv[2] + +try: + py_compile.compile(sys.argv[1], cfile=outFile, doraise=True) +except Exception as e: + sys.stderr.write('### Python compile error:\n' + str(e) + '\n') + raise diff --git a/scripts/src/SPELL-Disassemble b/scripts/src/SPELL-Disassemble new file mode 100644 index 0000000..38fccf4 --- /dev/null +++ b/scripts/src/SPELL-Disassemble @@ -0,0 +1,100 @@ +#!/usr/bin/env python +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +import dis, marshal, struct, sys, time, types, os + +def show_file(fname): + if os.path.exists(fname): + f = open(fname, "rb") + magic = f.read(4) + moddate = f.read(4) + modtime = time.asctime(time.localtime(struct.unpack('L', moddate)[0])) + print("magic %s" % (magic.encode('hex'))) + print("moddate %s (%s)" % (moddate.encode('hex'), modtime)) + code = marshal.load(f) + code_tree(code) + show_code(code) + +def show_code(code, indent=''): + print("%scode" % indent) + indent += ' ' + print("%sargcount %d" % (indent, code.co_argcount)) + print("%snlocals %d" % (indent, code.co_nlocals)) + print("%sstacksize %d" % (indent, code.co_stacksize)) + print("%sflags %04x" % (indent, code.co_flags)) + show_hex("code", code.co_code, indent=indent) + dis.disassemble(code) + print("%sconsts" % indent) + for const in code.co_consts: + if type(const) == types.CodeType: + show_code(const, indent+' ') + else: + print(" %s%r" % (indent, const)) + print("%snames %r" % (indent, code.co_names)) + print("%svarnames %r" % (indent, code.co_varnames)) + print("%sfreevars %r" % (indent, code.co_freevars)) + print("%scellvars %r" % (indent, code.co_cellvars)) + print("%sfilename %r" % (indent, code.co_filename)) + print("%sname %r" % (indent, code.co_name)) + print("%sfirstlineno %d" % (indent, code.co_firstlineno)) + show_hex("lnotab", code.co_lnotab, indent=indent) + show_table(code.co_lnotab,indent=indent) + +def code_tree(code, indent=''): + print("%scode object: %s" % (indent,code)) + for const in code.co_consts: + if type(const) == types.CodeType: + code_tree(const, indent+' ') + +def show_table( lnotab, indent ): + lnotab = lnotab.encode('hex') + if len(lnotab) < 32: + print(indent, end=' ') + for idx in range(0,len(lnotab),4): + offset = int(lnotab[idx:idx+2], 16) + sline = int(lnotab[idx+2:idx+4], 16) + print("(%s,%s) " % (offset,sline), end=' ') + print() + else: + for i in range(0, len(lnotab), 32): + line = lnotab[i:i+32] + print(indent, end=' ') + for idx in range(0,len(line),4): + offset = int(lnotab[idx:idx+2], 16) + sline = int(lnotab[idx+2:idx+4], 16) + print("(%s,%s) " % (offset,sline), end=' ') + print() + +def show_hex(label, h, indent): + h = h.encode('hex') + if len(h) < 60: + print("%s%s %s" % (indent, label, h)) + else: + print("%s%s" % (indent, label)) + for i in range(0, len(h), 60): + print("%s %s" % (indent, h[i:i+60])) + +if len(sys.argv) != 2: + print("please provide an input file") + sys.exit(1) + +show_file(sys.argv[1]) diff --git a/scripts/src/SPELL-Driver b/scripts/src/SPELL-Driver new file mode 100644 index 0000000..555b3d3 --- /dev/null +++ b/scripts/src/SPELL-Driver @@ -0,0 +1,75 @@ +#!/usr/bin/env python + +# Prototype for a standalone driver execution with an gRPC interface +# Author: Raimondas Sasnauskas +# Date: 2018-03-09 + +import os, sys +import argparse + +# parse command line arguments +parser = argparse.ArgumentParser() +parser.add_argument("context", type=str, help="context name") +group = parser.add_argument_group() +group.add_argument("--host", type=str, help="executor service host") +group.add_argument("--port", type=int, help="executor service port") +group.add_argument("--debug", dest='debug', action='store_true') +parser.set_defaults(debug=False) +args = parser.parse_args() + +if (args.host and not args.port) or (not args.host and args.port): + parser.error("both executor host and port must be specified") + +# check necessary environment variables +for e in ["SPELL_HOME", "SPELL_CONFIG", "SPELL_COTS"]: + if os.getenv(e): + continue + parser.error("%s environment variable not set" % e) + +# check if the config file exists +SPELL_HOME = os.getenv("SPELL_HOME") +SPELL_CONFIG = os.getenv("SPELL_CONFIG") +config = SPELL_CONFIG + os.sep + "server" + os.sep + "server_" + args.context + ".xml" +if not os.path.isfile(config): + parser.error("Configuration file does not exist: " + config) + +# Extend PYTHONPATH +sys.path.append(SPELL_HOME + os.sep + "spell") +sys.path.append(SPELL_HOME + os.sep + "spell" + os.sep + "spell" + os.sep + "stubs") +sys.path.append(SPELL_HOME + os.sep + "lib") +sys.path.append(SPELL_HOME + os.sep + "drivers" + os.sep + "service") + +# always load grpc imports first +import driverserver +from exec_client import * +from cif_client import * + +from spell.utils.log import LOG +# set the log file +if not args.debug: + LOG.setLogFile('SPELL-Driver-%s' % args.context) + LOG.setShowLog(False) + +# spell imports +from spell.config.reader import Config +from spell.lib.drivermgr import DriverManager +from spell.lib.registry import REGISTRY + +def run(): + # load config file + Config.instance().load(config) + + # load the driver + mgr = DriverManager.instance() + mgr.setup(args.context, skipSetup=True) + # always setup config interface + REGISTRY['CONFIG'].setup(mgr.contextConfig, mgr.driverConfig) + + # start gRPC server + driverserver.serve(args.host, args.port) + + # cleanup the driver + mgr.cleanup(True, True) + +if __name__ == "__main__": + run() diff --git a/scripts/src/SPELL-Server b/scripts/src/SPELL-Server new file mode 100644 index 0000000..4af2a50 --- /dev/null +++ b/scripts/src/SPELL-Server @@ -0,0 +1,69 @@ +#!/bin/bash +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +#=============================================================================== +# Setup main environment variables +#=============================================================================== + +# Satellite +SATELLITE=$1 +[ -z $SATELLITE ] && echo "Must provide the spacecraft: $0 " && exit 1 + +# Set SPELL home +if [ -z $SPELL_HOME ]; then + export SPELL_HOME=/home/spell/SPELL +fi +[ ! -d $SPELL_HOME ] && echo "ERROR: cannot find SPELL home directory: $SPELL_HOME" && exit 1 + +#=============================================================================== +# Setup environment +#=============================================================================== +SETUP_SCRIPT=$SPELL_HOME/bin/setup-environment.sh +[ ! -f $SETUP_SCRIPT ] && echo "ERROR: cannot find environment setup script: $SETUP_SCRIPT" && exit 1 +! source $SETUP_SCRIPT && echo "ERROR: SPELL environment setup failed" && exit 1 + +#=============================================================================== +# Launcher configuration +#=============================================================================== +APP_PATH=$SPELL_HOME/bin/SPELL-Listener +CFG_PATH=$SPELL_CONFIG/server/server_$SATELLITE.xml +[ ! -f $CFG_PATH ] && echo "ERROR: could not find configuration file: $CFG_PATH" && exit 1 +[ ! -f $APP_PATH ] && echo "ERROR: could not find application: $APP_PATH" && exit 1 + +#=============================================================================== +# Pre-execution cleanup +#=============================================================================== +echo "Cleanup of previous server instances except of running SPELL services" +$SPELL_HOME/bin/Kill-Server $SATELLITE skipservice + +#=============================================================================== +# Execute application +#=============================================================================== +echo "Starting SPELL server for $SATELLITE" +if [[ ( "$USER" == "hifly_admin" ) && ( -f $BIN_DIR/APPLInotify ) ]] +then + echo "Notifying hifly..." + $BIN_DIR/APPLInotify start $$ + exec $APP_PATH -c $CFG_PATH && $BIN_DIR/APPLInotify stop $$ +else + nohup $APP_PATH -c $CFG_PATH >> $SPELL_LOG/server_$SATELLITE.log 2>&1 & +fi diff --git a/scripts/src/SPELL-Server-Docker b/scripts/src/SPELL-Server-Docker new file mode 100644 index 0000000..0f1d087 --- /dev/null +++ b/scripts/src/SPELL-Server-Docker @@ -0,0 +1,80 @@ +#!/bin/bash +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +#=============================================================================== +# Setup main environment variables +#=============================================================================== + +# Satellite +SATELLITE=$1 +[ -z $SATELLITE ] && echo "Must provide the spacecraft: $0 " && exit 1 + +# Set SPELL home +if [ -z $SPELL_HOME ]; then + export SPELL_HOME=/home/spell/SPELL +fi +[ ! -d $SPELL_HOME ] && echo "ERROR: cannot find SPELL home directory: $SPELL_HOME" && exit 1 + +#=============================================================================== +# Setup environment +#=============================================================================== +SETUP_SCRIPT=$SPELL_HOME/bin/setup-environment.sh +[ ! -f $SETUP_SCRIPT ] && echo "ERROR: cannot find environment setup script: $SETUP_SCRIPT" && exit 1 +! source $SETUP_SCRIPT && echo "ERROR: SPELL environment setup failed" && exit 1 + +#=============================================================================== +# Launcher configuration +#=============================================================================== +APP_PATH=$SPELL_HOME/bin/SPELL-Listener +CFG_PATH=$SPELL_CONFIG/server/server_$SATELLITE.xml +[ ! -f $CFG_PATH ] && echo "ERROR: could not find configuration file: $CFG_PATH" && exit 1 +[ ! -f $APP_PATH ] && echo "ERROR: could not find application: $APP_PATH" && exit 1 + +#=============================================================================== +# Pre-execution cleanup +#=============================================================================== +echo "Cleanup of previous server instances except of running SPELL services" +$SPELL_HOME/bin/Kill-Server $SATELLITE skipservice + +#============================================================================== +# SPELL COTS +#============================================================================== + +export PATH=$SPELL_COTS/bin:$PATH +export LD_LIBRARY_PATH=$SPELL_COTS/lib:$SPELL_HOME/lib:$SPELL_DRIVER/lib:$LD_LIBRARY_PATH +export BACKWARD_CXX_SOURCE_PREFIXES=/home/spell/src/lib/SPELL_CFG/src + + +#=============================================================================== +# Execute application +#=============================================================================== +echo "Starting SPELL server for $SATELLITE" +if [[ ( -f $BIN_DIR/APPLInotify ) ]] +then + echo "Notifying hifly..." + $BIN_DIR/APPLInotify start $$ + $APP_PATH -c $CFG_PATH && $BIN_DIR/APPLInotify stop $$ +else + $APP_PATH -c $CFG_PATH 2>&1 | tee -a $SPELL_LOG/server_$SATELLITE.log +fi + +#tail -f $SPELL_LOG/server_$SATELLITE.log \ No newline at end of file diff --git a/scripts/src/Shell b/scripts/src/Shell new file mode 100644 index 0000000..0b88a15 --- /dev/null +++ b/scripts/src/Shell @@ -0,0 +1,40 @@ +#!/bin/bash +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +#=============================================================================== +# Setup main environment variables +#=============================================================================== + +# SPELL home +if [ -z $SPELL_HOME ]; then + export SPELL_HOME=/home/spell/SPELL +fi +[ ! -d $SPELL_HOME ] && echo "ERROR: cannot find SPELL home directory: $SPELL_HOME" && exit 1 + +#=============================================================================== +# Setup environment +#=============================================================================== +SETUP_SCRIPT=$SPELL_HOME/bin/setup-environment.sh +[ ! -f $SETUP_SCRIPT ] && echo "ERROR: cannot find SPELL environment setup script: $SETUP_SCRIPT" && exit 1 +! source $SETUP_SCRIPT && echo "ERROR: SPELL environment setup failed" && exit 1 + +$SPELL_HOME/bin/SPELL-Shell $@ diff --git a/scripts/src/generate-release-nfo.sh b/scripts/src/generate-release-nfo.sh new file mode 100644 index 0000000..85c18f4 --- /dev/null +++ b/scripts/src/generate-release-nfo.sh @@ -0,0 +1,59 @@ +#!/bin/bash +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +if [ -f "/etc/centos-release" ]; then + PLATFORM=$(cat /etc/centos-release) +elif [ `which lsb_release` ]; then + PLATFORM=$(lsb_release -a | grep Description | cut -d: -f2 | tr -d '\t') +elif [ -f "/etc/lsb_release" ]; then + PLATFORM=$(cat /etc/lsb-release | grep -Po '.*DISTRIB_DESCRIPTION="\K([^"]*)') +else + PLATFORM="Unknown" +fi + +if [ -z "${VERSION_TAG}" ]; then + REVISION=$(git describe --tags --always --long 2> /dev/null) +else + REVISION="${VERSION_TAG}" +fi + +echo "\ +--------- +CHANGELOG +--------- + +$(cat $1 2> /dev/null) + +================================================================================ +Generation date : $(date +%Y-%m-%d) +Revision : $REVISION +Version : $2 +Build host : $(hostname) +Build user : $(whoami) +Platform : $PLATFORM +Installation path : $3 +GCC : $(gcc --version | head -1) +Configure log : + +$(head -8 config.log) +================================================================================\ +" diff --git a/scripts/src/setup-environment.sh b/scripts/src/setup-environment.sh new file mode 100644 index 0000000..ecf197f --- /dev/null +++ b/scripts/src/setup-environment.sh @@ -0,0 +1,122 @@ +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +#============================================================================== +# HELPER FUNCTIONS +#============================================================================== + +function check_env_variable() { + DST=${!1} + if [ -z $DST ]; then + echo "$1 is not set, defaulting to $2" + export $1=$2 + fi + DST=${!1} + if [ ! -d $DST ]; then + echo "ERROR: cannot find $1 directory: $DST" + return 1 + else + return 0 + fi +} + +#============================================================================== +# ENVIRONMENT VARIABLES FOR SPELL +#============================================================================== + +echo ">>> SPELL environment setup begin" +! check_env_variable SPELL_LOG $SPELL_HOME/log && mkdir -p $SPELL_LOG + +# check/set environment variables +! check_env_variable SPELL_HOME /home/spell/SPELL && return 1 +! check_env_variable SPELL_DATA /home/spell/SPELL_PROCS && return 1 +! check_env_variable SPELL_CONFIG /home/spell/SPELL_CONFIG && return 1 +! check_env_variable SPELL_LOG $SPELL_HOME/log && return 1 +! check_env_variable SPELL_COTS /home/spell/spell_cots && return 1 + +# parse release.nfo +RELEASENFO=$SPELL_HOME/release.nfo +[ ! -f $RELEASENFO ] && echo "ERROR: $RELEASENFO not found, check installation" && return 1 +VERSION=`grep ^Version $RELEASENFO | cut -d: -f 2 | tr -d ' '` +[ -z $VERSION ] && echo "ERROR: $RELEASENFO does not contain SPELL version" && return 1 + +[ -z "$USER" ] && export USER=spell + +echo "*** SPELL version $VERSION" +echo "*** SPELL_HOME=$SPELL_HOME" +echo "*** SPELL_DATA=$SPELL_DATA" +echo "*** SPELL_CONFIG=$SPELL_CONFIG" +echo "*** SPELL_LOG=$SPELL_LOG" +echo "*** SPELL_COTS=$SPELL_COTS" +echo "*** current User: $USER" + +#============================================================================== +# SPELL ulimits +#============================================================================== + +echo "*** Setting ulimits" +ulimit -c unlimited > /dev/null 2>&1 +ulimit -s unlimited > /dev/null 2>&1 +ulimit -u 30000 > /dev/null 2>&1 +ulimit -a | grep "core file size" +ulimit -a | grep "stack size" +ulimit -a | grep "max user processes" + +#============================================================================== +# SPELL COTS +#============================================================================== + +export PATH=$SPELL_COTS/bin:$PATH +export LD_LIBRARY_PATH=$SPELL_COTS/lib:$SPELL_HOME/lib:$LD_LIBRARY_PATH + +#============================================================================== +# PYTHON CHECKS +#============================================================================== + +PYTHON=`which python` +[ -z $PYTHON ] && echo "ERROR: no python executable in PATH" && return 1 + +# is the python version supported? +PYVERSION=`$PYTHON -V 2>&1 | cut -d' ' -f 2` +PYREL=`echo $PYVERSION | cut -d'.' -f 1` +PYMAJOR=`echo $PYVERSION | cut -d'.' -f 2` +(( $PYREL != 2 )) && echo "ERROR: python > 2.X.Y required" && return 1 +(( $PYMAJOR < 5 )) && echo "ERROR: python > 2.5.Y required" && return 1 + +echo "*** Using python version $PYVERSION in $PYTHON" + +# is the python executable coming from COTS? +[[ "$(dirname $PYTHON)" != "$SPELL_COTS/bin" ]] \ + && echo "ERROR: python executable must be in COTS, check installation" && return 1 + +# is the python interpreter shared library matching? +EXC=$SPELL_HOME/bin/SPELL-Executor +[ ! -f $EXC ] && echo "ERROR: SPELL-Executor not found $SPELL_HOME/bin, check installation" && return 1 +EXPECTED=`readelf -d $EXC | grep ".*NEEDED.*Shared.*libpython.*" | cut -d[ -f 2 | cut -d] -f1` +AVAILABLE=`readelf -d $PYTHON | grep ".*NEEDED.*Shared.*libpython.*" | cut -d[ -f 2 | cut -d] -f1` +[[ "$EXPECTED" != "$AVAILABLE" ]] \ + && echo "ERROR: shared library mismatch, SPELL was compiled with $EXPECTED, got $AVAILABLE" && return 1 + +# print python path for validation +$PYTHON -c "import sys; print sys.path" + +echo ">>> SPELL environment setup and validation done" +echo diff --git a/scripts/src/version.sh b/scripts/src/version.sh new file mode 100644 index 0000000..67771d1 --- /dev/null +++ b/scripts/src/version.sh @@ -0,0 +1,29 @@ +#!/bin/bash +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +if [ -z "${VERSION_TAG}" ]; then + REVISION=$(git describe --tags --always --long 2> /dev/null) +else + REVISION="${VERSION_TAG}" +fi + +echo -ne "$REVISION" diff --git a/src/Makefile.am b/src/Makefile.am index a4004d5..bde773d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,52 +1,23 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2012 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### -AUTOMAKE_OPTIONS = foreign -SUBDIRS=spell - -if WITH_EXECUTOR -SUBDIRS += executor -endif - -if WITH_EXECUTOR_CMD -SUBDIRS += executorcmd -endif - -if WITH_SHELL -SUBDIRS += shell -endif - -if WITH_LISTENER -SUBDIRS += listener -endif - -if WITH_CONTEXT -SUBDIRS += context -endif - -if WITH_GUI -SUBDIRS += spel-gui -endif +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ +SUBDIRS = context executor executorcmd listener service shell ping spell + \ No newline at end of file diff --git a/src/context/Makefile.am b/src/context/Makefile.am index e2f215c..c10aa73 100644 --- a/src/context/Makefile.am +++ b/src/context/Makefile.am @@ -1,50 +1,40 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ -include $(top_srcdir)/common.mk +include ${top_srcdir}/common.mk bin_PROGRAMS = SPELL-Context SPELL_Context_SOURCES = src/SPELLcontextMain.C -SPELL_Context_LDFLAGS = -lxerces-c -lpthread -lrt -lutil -ldl -lz -lcrypto $(AM_LDFLAGS) - -SPELL_Context_LDADD = ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL.la \ - ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC.la \ - ${top_builddir}/lib/SPELL_WS/libSPELL_WS.la \ - ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN.la \ - ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP.la \ - ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG.la \ - ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD.la \ - ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC.la \ - ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF.la \ - ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA.la \ - ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB.la \ - ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS.la \ - ${top_builddir}/lib/SPELL_PRC/libSPELL_PRC.la \ - ${top_builddir}/lib/SPELL_CTX/libSPELL_CTX.la +SPELL_Context_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} +SPELL_Context_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} +SPELL_Context_LDADD = ${top_builddir}/lib/SPELL_CTX/libSPELL_CTX_v2.la \ + ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB_v2.la \ + ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP_v2.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v2.la \ + ${top_builddir}/lib/SPELL_PRC/libSPELL_PRC_v2.la \ + ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD_v2.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v2.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v2.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v2.la diff --git a/src/context/src/SPELLcontextMain.C b/src/context/src/SPELLcontextMain.C index 7c2b300..c8e7662 100644 --- a/src/context/src/SPELLcontextMain.C +++ b/src/context/src/SPELLcontextMain.C @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL context main program // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -33,6 +33,12 @@ #include "SPELL_UTIL/SPELLerror.H" // Local includes ---------------------------------------------------------- #include "SPELL_CTX/SPELLcontext.H" +#ifdef WITH_KUBERNETES +#include +#endif +#ifdef WITH_BACKTRACE +#include +#endif // GLOBALS /////////////////////////////////////////////////////////////////// @@ -45,7 +51,6 @@ static std::string context = ""; // For POST // STATIC //////////////////////////////////////////////////////////////////// - //============================================================================ // Show usage //============================================================================ @@ -116,6 +121,13 @@ int parseArgs( int argc, char** argv ) //============================================================================ int main( int argc, char** argv ) { + #ifdef WITH_KUBERNETES + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + #endif + #ifdef WITH_BACKTRACE + backward::SignalHandling sh; + std::cout << "Singal Handlers loaded: " << sh.loaded() << std::endl; + #endif if ( parseArgs(argc,argv) != 0 ) return 1; // The time identifier is used for unique log file names diff --git a/src/executor/Makefile.am b/src/executor/Makefile.am index 1193feb..82ed20b 100644 --- a/src/executor/Makefile.am +++ b/src/executor/Makefile.am @@ -1,48 +1,61 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### - -include $(top_srcdir)/common.mk - -bin_PROGRAMS = SPELL-Executor +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +include ${top_srcdir}/common.mk + +bin_PROGRAMS = SPELL-Executor SPELL-Executor_v3 + +SPELL_Executor_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +SPELL_Executor_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} SPELL_Executor_SOURCES = src/SPELLexecutorMain.C -SPELL_Executor_LDFLAGS = -lxerces-c -lpthread -lrt -lutil -ldl -lz -lcrypto $(AM_LDFLAGS) +SPELL_Executor_LDADD = ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC_v2.la \ + ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS_v2.la \ + ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA_v2.la \ + ${top_builddir}/lib/SPELL_WS/libSPELL_WS_v2.la \ + ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP_v2.la \ + ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF_v2.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v2.la \ + ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD_v2.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v2.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v2.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v2.la + +SPELL_Executor_v3_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} -SPELL_Executor_LDADD = ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL.la \ - ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC.la \ - ${top_builddir}/lib/SPELL_WS/libSPELL_WS.la \ - ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN.la \ - ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP.la \ - ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG.la \ - ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD.la \ - ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC.la \ - ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA.la \ - ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB.la \ - ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS.la \ - ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF.la +SPELL_Executor_v3_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} +SPELL_Executor_v3_SOURCES = src/SPELLexecutorMain.C +SPELL_Executor_v3_LDADD = ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC_v3.la \ + ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS_v3.la \ + ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA_v3.la \ + ${top_builddir}/lib/SPELL_WS/libSPELL_WS_v3.la \ + ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP_v3.la \ + ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF_v3.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v3.la \ + ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD_v3.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v3.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v3.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v3.la + \ No newline at end of file diff --git a/src/executor/src/SPELLexecutorMain.C b/src/executor/src/SPELLexecutorMain.C index 27bfd0c..e3ad630 100644 --- a/src/executor/src/SPELLexecutorMain.C +++ b/src/executor/src/SPELLexecutorMain.C @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL executor main program // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -34,7 +34,12 @@ #include "SPELL_EXC/SPELLinterpreter.H" #include "SPELL_CIFS/SPELLserverCif.H" #include "SPELL_UTIL/SPELLpythonHelper.H" - +#ifdef WITH_KUBERNETES +#include +#endif +#ifdef WITH_BACKTRACE +#include +#endif // GLOBALS /////////////////////////////////////////////////////////////////// // Initialization/configuration variables @@ -48,6 +53,7 @@ static std::string procId = ""; static std::string persis = ""; static std::string timeId = ""; + // STATIC //////////////////////////////////////////////////////////////////// //============================================================================ @@ -140,8 +146,15 @@ int parseArgs( int argc, char** argv ) //============================================================================ int main( int argc, char** argv ) { + #ifdef WITH_KUBERNETES + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + #endif + #ifdef WITH_BACKTRACE + backward::SignalHandling sh; + std::cout << "Singal Handlers loaded: " << sh.loaded() << std::endl; + #endif if ( parseArgs(argc,argv) != 0 ) return 1; - + // Create an instance of the interpreter. SPELLinterpreter& interp = SPELLinterpreter::instance(); @@ -154,10 +167,6 @@ int main( int argc, char** argv ) // Setup the logging capabilities SPELLlog::instance().setLogFile("_Executor_" + procId, timeId); -#ifdef WITH_DEBUG - LOG_ENABLE -#endif - // Configuration of the interpreter SPELLinterpreterConfig config; config.warmstart = (warm == 1); // Enable warm-start and error recovery capabilities diff --git a/src/executorcmd/Makefile.am b/src/executorcmd/Makefile.am index 0ff57c2..d53a87d 100644 --- a/src/executorcmd/Makefile.am +++ b/src/executorcmd/Makefile.am @@ -1,49 +1,65 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Mar 17, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### - -include $(top_srcdir)/common.mk - -bin_PROGRAMS = SPELL-ExecutorCmd +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +include ${top_srcdir}/common.mk + +bin_PROGRAMS = SPELL-ExecutorCmd SPELL-ExecutorCmd_v3 SPELL_ExecutorCmd_SOURCES = src/SPELLexecutorMain.C -SPELL_ExecutorCmd_LDFLAGS = -lxerces-c -lpthread -lrt -lncurses -lmenu -lutil -ldl -lz -lcrypto $(AM_LDFLAGS) +SPELL_ExecutorCmd_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +SPELL_ExecutorCmd_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} -lmenu + +SPELL_ExecutorCmd_LDADD = ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC_v2.la \ + ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS_v2.la \ + ${top_builddir}/lib/SPELL_CIFC/libSPELL_CIFC_v2.la \ + ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB_v2.la \ + ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA_v2.la \ + ${top_builddir}/lib/SPELL_WS/libSPELL_WS_v2.la \ + ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP_v2.la \ + ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF_v2.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v2.la \ + ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD_v2.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v2.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v2.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v2.la + +SPELL_ExecutorCmd_v3_SOURCES = src/SPELLexecutorMain.C -SPELL_ExecutorCmd_LDADD = ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL.la \ - ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC.la \ - ${top_builddir}/lib/SPELL_WS/libSPELL_WS.la \ - ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN.la \ - ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP.la \ - ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG.la \ - ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD.la \ - ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC.la \ - ${top_builddir}/lib/SPELL_CIFC/libSPELL_CIFC.la \ - ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA.la \ - ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB.la \ - ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS.la \ - ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF.la +SPELL_ExecutorCmd_v3_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} +SPELL_ExecutorCmd_v3_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} -lmenu +SPELL_ExecutorCmd_v3_LDADD = ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC_v3.la \ + ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS_v3.la \ + ${top_builddir}/lib/SPELL_CIFC/libSPELL_CIFC_v3.la \ + ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB_v3.la \ + ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA_v3.la \ + ${top_builddir}/lib/SPELL_WS/libSPELL_WS_v3.la \ + ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP_v3.la \ + ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF_v3.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v3.la \ + ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD_v3.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v3.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v3.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v3.la + \ No newline at end of file diff --git a/src/executorcmd/src/SPELLexecutorMain.C b/src/executorcmd/src/SPELLexecutorMain.C index 705ddda..dff2b56 100644 --- a/src/executorcmd/src/SPELLexecutorMain.C +++ b/src/executorcmd/src/SPELLexecutorMain.C @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL command line executor main program // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -35,8 +35,9 @@ #include "SPELL_EXC/SPELLinterpreter.H" #include "SPELL_CIFC/SPELLstandaloneCif.H" #include "SPELL_CIFC/SPELLautomaticCif.H" - - +#ifdef WITH_BACKTRACE +#include +#endif // GLOBALS /////////////////////////////////////////////////////////////////// // Initialization/configuration variables @@ -54,7 +55,6 @@ static std::string arguments = ""; // STATIC //////////////////////////////////////////////////////////////////// - //============================================================================ // Show usage //============================================================================ @@ -156,6 +156,10 @@ int parseArgs( int argc, char** argv ) //============================================================================ int main( int argc, char** argv ) { + #ifdef WITH_BACKTRACE + backward::SignalHandling sh; + std::cout << "Singal Handlers loaded: " << sh.loaded() << std::endl; + #endif if ( parseArgs(argc,argv) != 0 ) return 1; // Show the environment configuration diff --git a/src/listener/Makefile.am b/src/listener/Makefile.am index 693b505..3d1bca6 100644 --- a/src/listener/Makefile.am +++ b/src/listener/Makefile.am @@ -1,43 +1,38 @@ -################################################################################### -## FILE : Makefile.am -## DATE : Jul 26, 2011 -## PROJECT : SPELL -## DESCRIPTION: Automake rules -## -------------------------------------------------------------------------------- -## -## Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. -## -## This file is part of SPELL. -## -## SPELL is free software: you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published by -## the Free Software Foundation, either version 3 of the License, or -## (at your option) any later version. -## -## SPELL 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 General Public License for more details. -## -## You should have received a copy of the GNU General Public License -## along with SPELL. If not, see . -## -################################################################################### +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ +AUTOMAKE_OPTIONS = subdir-objects -include $(top_srcdir)/common.mk +include ${top_srcdir}/common.mk bin_PROGRAMS = SPELL-Listener SPELL_Listener_SOURCES = src/SPELLlistenerMain.C -SPELL_Listener_LDFLAGS = -lxerces-c -lpthread -lrt -lutil -ldl -lz -lcrypto $(AM_LDFLAGS) - -SPELL_Listener_LDADD = \ - ${top_builddir}/lib/SPELL_LST/libSPELL_LST.la \ - ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC.la \ - ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL.la \ - ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN.la \ - ${top_builddir}/lib/SPELL_PRC/libSPELL_PRC.la \ - ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG.la +SPELL_Listener_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} +SPELL_Listener_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} +SPELL_Listener_LDADD = ${top_builddir}/lib/SPELL_LST/libSPELL_LST_v2.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v2.la \ + ${top_builddir}/lib/SPELL_PRC/libSPELL_PRC_v2.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v2.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v2.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v2.la diff --git a/src/listener/src/SPELLlistenerMain.C b/src/listener/src/SPELLlistenerMain.C index fd2ea41..87aa320 100644 --- a/src/listener/src/SPELLlistenerMain.C +++ b/src/listener/src/SPELLlistenerMain.C @@ -5,7 +5,7 @@ // DESCRIPTION: SPELL listener main program // -------------------------------------------------------------------------------- // -// Copyright (C) 2008, 2015 SES ENGINEERING, Luxembourg S.A.R.L. +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. // // This file is part of SPELL. // @@ -32,6 +32,12 @@ #include "SPELL_LST/SPELLlistener.H" // System includes ----------------------------------------------------------- #include +#ifdef WITH_KUBERNETES +#include +#endif +#ifdef WITH_BACKTRACE +#include +#endif // Local includes ------------------------------------------------------------ // GLOBALS /////////////////////////////////////////////////////////////////// @@ -116,9 +122,9 @@ int parseArgs( int argc, char** argv ) //============================================================================ void signal_handler(int signum) { - if (signum < NumSignals) + if ((unsigned)signum < NumSignals) { - std::cerr << "[***] Captured signal " << signalList[signum].name << std::endl; + std::cerr << "[***] Captured signal " << signum << " "<< signalList[signum].name << std::endl; } else { @@ -162,6 +168,13 @@ void setupSignals() //============================================================================ int main( int argc, char** argv ) { + #ifdef WITH_KUBERNETES + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0); + #endif + #ifdef WITH_BACKTRACE + backward::SignalHandling sh; + std::cout << "Singal Handlers loaded: " << sh.loaded() << std::endl; + #endif std::string timeId = SPELLutils::fileTimestamp(); SPELLlog::instance().setLogFile("_Listener", timeId); diff --git a/src/ping/Makefile.am b/src/ping/Makefile.am new file mode 100644 index 0000000..f3ec786 --- /dev/null +++ b/src/ping/Makefile.am @@ -0,0 +1,44 @@ +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + +include ${top_srcdir}/common.mk + +bin_PROGRAMS = SPELL-Ping + +SPELL_Ping_SOURCES = src/SPELLPing.C + +SPELL_Ping_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +SPELL_Ping_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} -lmenu + +SPELL_Ping_LDADD = ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC_v2.la \ + ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS_v2.la \ + ${top_builddir}/lib/SPELL_CIFC/libSPELL_CIFC_v2.la \ + ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB_v2.la \ + ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA_v2.la \ + ${top_builddir}/lib/SPELL_WS/libSPELL_WS_v2.la \ + ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP_v2.la \ + ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF_v2.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v2.la \ + ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD_v2.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v2.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v2.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v2.la diff --git a/src/ping/src/SPELLPing.C b/src/ping/src/SPELLPing.C new file mode 100644 index 0000000..6d7cb9e --- /dev/null +++ b/src/ping/src/SPELLPing.C @@ -0,0 +1,133 @@ +// ################################################################################ +// FILE : SPELLPing.C +// DATE : Mar 18, 2011 +// PROJECT : SPELL +// DESCRIPTION: SPELL command line executor main program +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE ////////////////////////////////////////////////////////// +// System includes ----------------------------------------------------------- +// Local includes ------------------------------------------------------------ +// Project includes ---------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" +#include "SPELL_UTIL/SPELLpythonHelper.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLutils.H" +#include "SPELL_WRP/SPELLconstants.H" +#include "SPELL_IPC/SPELLipcClientInterface.H" +#include "SPELL_IPC/SPELLipc.H" +#include "SPELL_IPC/SPELLipc_Context.H" + +// GLOBALS /////////////////////////////////////////////////////////////////// + +// Initialization/configuration variables + +static int logging = 0; +static std::string context = ""; +static std::string configFile = ""; +static int port = 19888; + +// STATIC //////////////////////////////////////////////////////////////////// + + +//============================================================================ +// Show usage +//============================================================================ +void usage( char** argv ) +{ + std::cerr << "Syntax:" << std::endl; + std::cerr << " " << argv[0] << " -p [-d]" << std::endl; + std::cerr << std::endl; + std::cerr << " - p : context port" << std::endl; + std::cerr << " - d : enable debug traces" << std::endl; + std::cerr << std::endl; +} + +//============================================================================ +// Parse program arguments +//============================================================================ +int parseArgs( int argc, char** argv ) +{ + int code; + while( ( code = getopt(argc, argv, "dp:n:c:")) != -1) + { + switch(code) + { + case 'd': + logging = 1; + break; + case 'p': + port = atoi(optarg); + std::cout << "* Port " << port << std::endl; + break; + } + } + + return 0; +} + +//============================================================================ +// MAIN PROGRAM +//============================================================================ +int main( int argc, char** argv ) +{ + if ( parseArgs(argc,argv) != 0 ) return 1; + + // Show the environment configuration + SPELLutils::showEnvironment(); + // The time identifier is used to create unique file names + + SPELLipcClientInterface* ipc = new SPELLipcClientInterface("Ping","localhost",port); + + std::cout << "SPELL-Ping ### Connecting to Context" << std::endl; + ipc->connect(); + bool connectSuccessful = ipc->isConnected(); + std::cout << "SPELL-Ping ### Connection Successful: " << (connectSuccessful ? "yes" : "no") << std::endl; + if (connectSuccessful) { + SPELLipcMessage* msg = new SPELLipcMessage(ContextMessages::REQ_CURRENT_TIME); + msg->setType(MSG_TYPE_REQUEST); + msg->setSender("PING"); + msg->setReceiver("CTX"); + std::cout << "SPELL-Ping ### Sending Ping." << std::endl; + SPELLipcMessage answer = ipc->sendRequest(*msg,5000); + std::cout << "SPELL-Ping ### Ping was sent." << std::endl; + bool errorFound = answer.hasField(MessageField::FIELD_FATAL); + + std::cout << "SPELL-Ping ### Sending EOC" << std::endl; + SPELLipcMessage* eoc = new SPELLipcMessage("EOC"); + eoc->setType( MSG_TYPE_EOC ); + + ipc->sendMessage(*eoc); + + ipc->disconnect(); + if (errorFound){ + std::cout << "SPELL-Ping ### Ping not successful." << std::endl; + return -2; + } else { + std::cout << "SPELL-Ping ### Ping successful." << std::endl; + return 0; + } + } else { + std::cout << "SPELL-Ping ### Connection failed." << std::endl; + return -1; + } +} diff --git a/src/service/Makefile.am b/src/service/Makefile.am new file mode 100644 index 0000000..406575d --- /dev/null +++ b/src/service/Makefile.am @@ -0,0 +1,65 @@ +################################################################################ +# +# Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +# +# This file is part of SPELL. +# +# SPELL is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# SPELL 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with SPELL. If not, see . +# +################################################################################ + + +include ${top_srcdir}/common.mk + +bin_PROGRAMS = SPELL-Service SPELL-Service_v3 + +SPELL_Service_SOURCES = src/SPELLserviceMain.C + +SPELL_Service_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON2_CPPFLAGS} + +SPELL_Service_LDFLAGS = ${AM_LDFLAGS} ${PYTHON2_LDFLAGS} ${PYTHON2_EXTRA_LIBS} ${PYTHON2_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} -lmenu + +SPELL_Service_LDADD = ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC_v2.la \ + ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS_v2.la \ + ${top_builddir}/lib/SPELL_CIFC/libSPELL_CIFC_v2.la \ + ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB_v2.la \ + ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA_v2.la \ + ${top_builddir}/lib/SPELL_WS/libSPELL_WS_v2.la \ + ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP_v2.la \ + ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF_v2.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v2.la \ + ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD_v2.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v2.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v2.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v2.la + +SPELL_Service_v3_SOURCES = src/SPELLserviceMain.C + +SPELL_Service_v3_CPPFLAGS = ${AM_CPPFLAGS} ${PYTHON3_CPPFLAGS} + +SPELL_Service_v3_LDFLAGS = ${AM_LDFLAGS} ${PYTHON3_LDFLAGS} ${PYTHON3_EXTRA_LIBS} ${PYTHON3_EXTRA_LDFLAGS} ${RELEASE_LDFLAG} -lmenu + +SPELL_Service_v3_LDADD = ${top_builddir}/lib/SPELL_EXC/libSPELL_EXC_v3.la \ + ${top_builddir}/lib/SPELL_CIFS/libSPELL_CIFS_v3.la \ + ${top_builddir}/lib/SPELL_CIFC/libSPELL_CIFC_v3.la \ + ${top_builddir}/lib/SPELL_SDB/libSPELL_SDB_v3.la \ + ${top_builddir}/lib/SPELL_DTA/libSPELL_DTA_v3.la \ + ${top_builddir}/lib/SPELL_WS/libSPELL_WS_v3.la \ + ${top_builddir}/lib/SPELL_WRP/libSPELL_WRP_v3.la \ + ${top_builddir}/lib/SPELL_CIF/libSPELL_CIF_v3.la \ + ${top_builddir}/lib/SPELL_IPC/libSPELL_IPC_v3.la \ + ${top_builddir}/lib/SPELL_PRD/libSPELL_PRD_v3.la \ + ${top_builddir}/lib/SPELL_SYN/libSPELL_SYN_v3.la \ + ${top_builddir}/lib/SPELL_CFG/libSPELL_CFG_v3.la \ + ${top_builddir}/lib/SPELL_UTIL/libSPELL_UTIL_v3.la diff --git a/src/service/src/SPELLserviceMain.C b/src/service/src/SPELLserviceMain.C new file mode 100644 index 0000000..7b0cec8 --- /dev/null +++ b/src/service/src/SPELLserviceMain.C @@ -0,0 +1,213 @@ +// ################################################################################ +// FILE : SPELLserviceMain.C +// DATE : Mar 18, 2011 +// PROJECT : SPELL +// DESCRIPTION: SPELL command line executor main program +// -------------------------------------------------------------------------------- +// +// Copyright (C) 2008, 2018 SES ENGINEERING, Luxembourg S.A.R.L. +// +// This file is part of SPELL. +// +// SPELL is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// SPELL 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 General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with SPELL. If not, see . +// +// ################################################################################ + +// FILES TO INCLUDE ////////////////////////////////////////////////////////// +// System includes ----------------------------------------------------------- +// Local includes ------------------------------------------------------------ +// Project includes ---------------------------------------------------------- +#include "SPELL_UTIL/SPELLbase.H" +#include "SPELL_UTIL/SPELLpythonHelper.H" +#include "SPELL_UTIL/SPELLlog.H" +#include "SPELL_UTIL/SPELLutils.H" +#include "SPELL_EXC/SPELLserviceInterpreter.H" +#include "SPELL_CIFS/SPELLserverCif.H" +#ifdef WITH_KUBERNETES +#include +#endif +#ifdef WITH_BACKTRACE +#include +#endif +// GLOBALS /////////////////////////////////////////////////////////////////// + +// Initialization/configuration variables +static int warm = 0; +static int recover = 0; +static int script = 0; +static int interactive = 0; +static int logging = 0; +static int port = 0; +static std::string context = ""; +static std::string configFile = ""; +static std::string procId = ""; +static std::string persis = ""; +static std::string promptFile = ""; +static std::string arguments = ""; + +// STATIC //////////////////////////////////////////////////////////////////// + + +//============================================================================ +// Show usage +//============================================================================ +void usage( char** argv ) +{ + std::cerr << "Syntax:" << std::endl; + std::cerr << " " << argv[0] << " {-p |-f