From 8089d5145e08950224308356f63a425e684bd02d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 14 Jun 2023 20:16:15 +0300 Subject: [PATCH 001/114] Initial commit --- .gitattributes | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..dfe07704 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto From 9913c8dcd39da3788bf088f76ea3fa9e204b9921 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Wed, 14 Jun 2023 20:23:46 +0300 Subject: [PATCH 002/114] clone iofog Agent 3.0.1 --- CHANGELOG.md | 173 ++ CONTRIBUTING | 55 + Dockerfile | 86 + LICENSE | 278 +++ NOTICE | 42 + README.md | 105 + azure-pipelines.yml | 240 +++ build.gradle | 26 + docs/Project-Microculture.md | 28 + docs/base64test.jpg | Bin 0 -> 6714 bytes docs/ioFog-Architecture-Diagram.png | Bin 0 -> 56432 bytes docs/ioFog-Architecture.md | 318 +++ docs/ioFog-Command-Line-Specification.md | 299 +++ docs/ioFog-Configuration-Specification.md | 27 + docs/ioFog-Container-SDK-Requirements.md | 72 + docs/ioFog-Debug-Console-Specification.md | 104 + docs/ioFog-Device-Connections.md | 163 ++ ...Fog-Fabric-Controller-API-Specification.md | 528 +++++ docs/ioFog-Installation-Specification.md | 82 + docs/ioFog-Local-API-Specification.md | 366 ++++ docs/ioFog-Packaging-Instructions.md | 96 + docs/ioFog-QA-Test-Cases.md | 139 ++ .../ioFog-Status-Information-Specification.md | 65 + docs/ioFog-Stream-Viewer-Specification.md | 75 + docs/ioFog-System-Container-Requirements.md | 171 ++ docs/ioFog-Test-Message-Generator.md | 52 + docs/ioFog-Testing-Strategy.md | 35 + docs/ioFog-ioMessage-Specification.md | 302 +++ docs/ioMessage-Formats-Specification.md | 94 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 55616 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 188 ++ gradlew.bat | 100 + iofog-agent-client/build.gradle | 38 + .../main/java/org/eclipse/iofog/Client.java | 280 +++ .../src/main/resources/version.properties | 1 + iofog-agent-daemon/build.gradle | 91 + .../main/java/org/eclipse/iofog/Daemon.java | 199 ++ .../java/org/eclipse/iofog/IOFogModule.java | 46 + .../iofog/command_line/CommandLineAction.java | 401 ++++ .../command_line/CommandLineConfigParam.java | 111 + .../iofog/command_line/CommandLineParser.java | 37 + .../util/CommandShellExecutor.java | 169 ++ .../util/CommandShellResultSet.java | 66 + .../diagnostics/ImageDownloadManager.java | 92 + .../strace/MicroserviceStraceData.java | 96 + .../strace/StraceDiagnosticManager.java | 150 ++ .../eclipse/iofog/edge_resources/Display.java | 45 + .../iofog/edge_resources/EdgeEndpoints.java | 108 + .../iofog/edge_resources/EdgeInterface.java | 47 + .../iofog/edge_resources/EdgeResource.java | 121 ++ .../edge_resources/EdgeResourceManager.java | 75 + .../iofog/exception/AgentException.java | 33 + .../iofog/exception/AgentSystemException.java | 32 + .../iofog/exception/AgentUserException.java | 32 + .../eclipse/iofog/field_agent/FieldAgent.java | 1802 +++++++++++++++++ .../iofog/field_agent/FieldAgentStatus.java | 76 + .../iofog/field_agent/VersionHandler.java | 212 ++ .../iofog/field_agent/enums/RequestType.java | 9 + .../field_agent/enums/VersionCommand.java | 68 + .../UnknownVersionCommandException.java | 21 + .../java/org/eclipse/iofog/gps/GpsMode.java | 31 + .../org/eclipse/iofog/gps/GpsWebHandler.java | 80 + .../iofog/local_api/ApiHandlerHelpers.java | 101 + .../iofog/local_api/BluetoothApiHandler.java | 112 + .../local_api/CommandLineApiHandler.java | 104 + .../iofog/local_api/ConfigApiHandler.java | 145 ++ .../iofog/local_api/ConfigurationMap.java | 29 + .../local_api/ControlSignalSentInfo.java | 53 + .../local_api/ControlWebsocketHandler.java | 204 ++ .../local_api/ControlWebsocketWorker.java | 82 + .../local_api/DeprovisionApiHandler.java | 83 + .../iofog/local_api/EdgeResourceHandler.java | 75 + .../local_api/GetConfigurationHandler.java | 127 ++ .../iofog/local_api/GpsApiHandler.java | 123 ++ .../iofog/local_api/InfoApiHandler.java | 94 + .../org/eclipse/iofog/local_api/LocalApi.java | 128 ++ .../iofog/local_api/LocalApiServer.java | 83 + .../local_api/LocalApiServerHandler.java | 380 ++++ .../LocalApiServerPipelineFactory.java | 58 + .../iofog/local_api/LocalApiStatus.java | 60 + .../iofog/local_api/LogApiHandler.java | 91 + .../iofog/local_api/MessageCallback.java | 38 + .../local_api/MessageReceiverHandler.java | 129 ++ .../iofog/local_api/MessageSenderHandler.java | 179 ++ .../iofog/local_api/MessageSentInfo.java | 77 + .../local_api/MessageWebsocketHandler.java | 240 +++ .../local_api/MessageWebsocketWorker.java | 96 + .../iofog/local_api/ProvisionApiHandler.java | 112 + .../QueryMessageReceiverHandler.java | 188 ++ .../iofog/local_api/StatusApiHandler.java | 85 + .../iofog/local_api/VersionApiHandler.java | 84 + .../eclipse/iofog/local_api/WebSocketMap.java | 57 + .../iofog/local_api/WebsocketUtil.java | 79 + ...MessageReceiverWebSocketClientHandler.java | 125 ++ .../MessageSenderWebSocketClientHandler.java | 213 ++ .../local_api/test/MessageSocketTestMain.java | 103 + .../test/MessageWebsocketReceiverClient.java | 109 + .../test/MessageWebsocketSenderClient.java | 111 + .../iofog/local_api/test/RestApiDriver.java | 29 + .../iofog/local_api/test/RestPublishTest.java | 71 + .../iofog/local_api/test/RestReceiveTest.java | 64 + .../test/WebSocketClientControl.java | 97 + .../test/WebSocketClientHandlerControl.java | 113 ++ .../iofog/message_bus/IOMessageListener.java | 61 + .../eclipse/iofog/message_bus/Message.java | 722 +++++++ .../iofog/message_bus/MessageArchive.java | 262 +++ .../eclipse/iofog/message_bus/MessageBus.java | 469 +++++ .../iofog/message_bus/MessageBusServer.java | 270 +++ .../iofog/message_bus/MessageBusStatus.java | 87 + .../iofog/message_bus/MessageBusUtil.java | 101 + .../iofog/message_bus/MessageIdGenerator.java | 110 + .../iofog/message_bus/MessagePublisher.java | 121 ++ .../iofog/message_bus/MessageReceiver.java | 150 ++ .../eclipse/iofog/microservice/EnvVar.java | 51 + .../iofog/microservice/Microservice.java | 209 ++ .../microservice/MicroserviceManager.java | 162 ++ .../iofog/microservice/MicroserviceState.java | 31 + .../microservice/MicroserviceStatus.java | 198 ++ .../iofog/microservice/PortMapping.java | 67 + .../eclipse/iofog/microservice/Registry.java | 159 ++ .../org/eclipse/iofog/microservice/Route.java | 63 + .../iofog/microservice/VolumeMapping.java | 77 + .../iofog/microservice/VolumeMappingType.java | 6 + .../iofog/network/IOFogNetworkInterface.java | 193 ++ .../network/IOFogNetworkInterfaceManager.java | 126 ++ .../process_manager/ContainerManager.java | 271 +++ .../iofog/process_manager/ContainerTask.java | 80 + .../iofog/process_manager/DockerUtil.java | 879 ++++++++ .../iofog/process_manager/ProcessManager.java | 475 +++++ .../process_manager/ProcessManagerStatus.java | 160 ++ .../process_manager/RestartStuckChecker.java | 51 + .../iofog/process_manager/StatsCallback.java | 55 + .../iofog/process_manager/TaskComparator.java | 31 + .../iofog/process_manager/TaskManager.java | 54 + .../eclipse/iofog/proxy/SshConnection.java | 121 ++ .../iofog/proxy/SshConnectionStatus.java | 26 + .../eclipse/iofog/proxy/SshProxyManager.java | 193 ++ .../iofog/proxy/SshProxyManagerStatus.java | 65 + .../iofog/pruning/DockerPruningManager.java | 159 ++ .../ResourceConsumptionManager.java | 350 ++++ .../ResourceConsumptionManagerStatus.java | 126 ++ .../resource_manager/ResourceManager.java | 66 + .../ResourceManagerStatus.java | 40 + .../iofog/status_reporter/StatusReporter.java | 232 +++ .../status_reporter/StatusReporterStatus.java | 53 + .../eclipse/iofog/supervisor/Supervisor.java | 158 ++ .../iofog/supervisor/SupervisorStatus.java | 76 + .../org/eclipse/iofog/utils/BytesUtil.java | 114 ++ .../eclipse/iofog/utils/CmdProperties.java | 93 + .../org/eclipse/iofog/utils/Constants.java | 135 ++ .../org/eclipse/iofog/utils/Orchestrator.java | 430 ++++ .../utils/configuration/Configuration.java | 1514 ++++++++++++++ .../ConfigurationItemException.java | 28 + .../utils/device_info/ArchitectureType.java | 81 + .../iofog/utils/functional/Either.java | 146 ++ .../iofog/utils/functional/Function3.java | 23 + .../iofog/utils/functional/Function4.java | 23 + .../iofog/utils/functional/Functions.java | 101 + .../eclipse/iofog/utils/functional/Left.java | 64 + .../eclipse/iofog/utils/functional/Pair.java | 250 +++ .../eclipse/iofog/utils/functional/Right.java | 64 + .../eclipse/iofog/utils/functional/Unit.java | 27 + .../iofog/utils/logging/LogFormatter.java | 60 + .../iofog/utils/logging/LoggingService.java | 261 +++ .../utils/trustmanager/TrustManagers.java | 111 + .../main/resources/cmd_messages.properties | 34 + .../src/main/resources/version.properties | 1 + .../command_line/CommandLineActionTest.java | 456 +++++ .../CommandLineConfigParamTest.java | 213 ++ .../command_line/CommandLineParserTest.java | 97 + .../util/CommandShellExecutorTest.java | 123 ++ .../util/CommandShellResultSetTest.java | 115 ++ .../diagnostics/ImageDownloadManagerTest.java | 219 ++ .../strace/MicroserviceStraceDataTest.java | 178 ++ .../strace/StraceDiagnosticManagerTest.java | 349 ++++ .../EdgeResourceManagerTest.java | 101 + .../field_agent/FieldAgentStatusTest.java | 83 + .../iofog/field_agent/FieldAgentTest.java | 1706 ++++++++++++++++ .../iofog/field_agent/VersionHandlerTest.java | 335 +++ .../field_agent/enums/VersionCommandTest.java | 114 ++ .../local_api/ApiHandlerHelpersTest.java | 321 +++ .../local_api/BluetoothApiHandlerTest.java | 146 ++ .../local_api/CommandLineApiHandlerTest.java | 306 +++ .../iofog/local_api/ConfigApiHandlerTest.java | 271 +++ .../local_api/ControlSignalSentInfoTest.java | 81 + .../ControlWebsocketHandlerTest.java | 347 ++++ .../local_api/ControlWebsocketWorkerTest.java | 126 ++ .../local_api/DeprovisionApiHandlerTest.java | 191 ++ .../GetConfigurationHandlerTest.java | 212 ++ .../iofog/local_api/GpsApiHandlerTest.java | 221 ++ .../iofog/local_api/InfoApiHandlerTest.java | 180 ++ .../local_api/LocalApiServerHandlerTest.java | 30 + .../LocalApiServerPipelineFactoryTest.java | 110 + .../iofog/local_api/LocalApiServerTest.java | 138 ++ .../iofog/local_api/LocalApiStatusTest.java | 63 + .../iofog/local_api/LogApiHandlerTest.java | 230 +++ .../iofog/local_api/MessageCallbackTest.java | 63 + .../message_bus/IOMessageListenerTest.java | 98 + .../iofog/message_bus/MessageArchiveTest.java | 188 ++ .../message_bus/MessageBusServerTest.java | 305 +++ .../message_bus/MessageBusStatusTest.java | 124 ++ .../iofog/message_bus/MessageBusTest.java | 355 ++++ .../iofog/message_bus/MessageBusUtilTest.java | 291 +++ .../message_bus/MessageIdGeneratorTest.java | 71 + .../message_bus/MessagePublisherTest.java | 193 ++ .../message_bus/MessageReceiverTest.java | 284 +++ .../iofog/message_bus/MessageTest.java | 484 +++++ .../process_manager/ContainerManagerTest.java | 564 ++++++ .../process_manager/ContainerTaskTest.java | 107 + .../iofog/process_manager/DockerUtilTest.java | 1139 +++++++++++ .../ProcessManagerStatusTest.java | 175 ++ .../process_manager/ProcessManagerTest.java | 565 ++++++ .../RestartStuckCheckerTest.java | 52 + .../process_manager/StatsCallbackTest.java | 90 + .../pruning/DockerPruningManagerTest.java | 233 +++ .../ResourceConsumptionManagerStatusTest.java | 81 + .../ResourceConsumptionManagerTest.java | 576 ++++++ .../ResourceManagerStatusTest.java | 51 + .../resource_manager/ResourceManagerTest.java | 81 + .../StatusReporterStatusTest.java | 65 + .../status_reporter/StatusReporterTest.java | 110 + .../supervisor/SupervisorStatusTest.java | 96 + .../iofog/supervisor/SupervisorTest.java | 132 ++ .../iofog/utils/CmdPropertiesTest.java | 227 +++ .../eclipse/iofog/utils/OrchestratorTest.java | 683 +++++++ .../configuration/ConfigurationTest.java | 1618 +++++++++++++++ .../device_info/ArchitectureTypeTest.java | 57 + .../iofog/utils/logging/LogFormatterTest.java | 72 + .../utils/logging/LoggingServiceTest.java | 289 +++ iofog-version-controller/build.gradle | 14 + .../iofog_version_controller/Main.java | 25 + .../util/CommandShellExecutor.java | 81 + .../util/CommandShellResultSet.java | 66 + packaging/iofog-agent/debian.sh | 104 + .../etc/bash_completion.d/iofog-agent | 22 + packaging/iofog-agent/etc/init.d/iofog-agent | 41 + .../iofog-agent/etc/iofog-agent/cert_new.crt | 29 + .../etc/iofog-agent/config-bck_new.xml | 70 + .../iofog-agent/config-development_new.xml | 70 + .../etc/iofog-agent/config-production_new.xml | 71 + .../etc/iofog-agent/config-switcher_new.xml | 17 + .../etc/iofog-agent/config_new.xml | 71 + packaging/iofog-agent/remove.sh | 13 + packaging/iofog-agent/rpm.sh | 108 + packaging/iofog-agent/upgrade.sh | 3 + packaging/iofog-agent/usr/bin/iofog-agent | 4 + .../usr/share/iofog-agent/rollback.sh | 64 + .../usr/share/iofog-agent/upgrade.sh | 76 + rest-api.yaml | 270 +++ scripts/bootstrap.sh | 32 + scripts/utils.sh | 46 + settings.gradle | 4 + test/deploy_ecn.bash | 59 + test/int_test.bats | 17 + test/resources/env.sh | 4 + test/run.bash | 14 + 257 files changed, 42367 insertions(+) create mode 100644 CHANGELOG.md create mode 100644 CONTRIBUTING create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 NOTICE create mode 100644 README.md create mode 100644 azure-pipelines.yml create mode 100644 build.gradle create mode 100644 docs/Project-Microculture.md create mode 100644 docs/base64test.jpg create mode 100644 docs/ioFog-Architecture-Diagram.png create mode 100755 docs/ioFog-Architecture.md create mode 100644 docs/ioFog-Command-Line-Specification.md create mode 100644 docs/ioFog-Configuration-Specification.md create mode 100644 docs/ioFog-Container-SDK-Requirements.md create mode 100644 docs/ioFog-Debug-Console-Specification.md create mode 100644 docs/ioFog-Device-Connections.md create mode 100644 docs/ioFog-Fabric-Controller-API-Specification.md create mode 100644 docs/ioFog-Installation-Specification.md create mode 100644 docs/ioFog-Local-API-Specification.md create mode 100644 docs/ioFog-Packaging-Instructions.md create mode 100644 docs/ioFog-QA-Test-Cases.md create mode 100644 docs/ioFog-Status-Information-Specification.md create mode 100644 docs/ioFog-Stream-Viewer-Specification.md create mode 100644 docs/ioFog-System-Container-Requirements.md create mode 100644 docs/ioFog-Test-Message-Generator.md create mode 100644 docs/ioFog-Testing-Strategy.md create mode 100644 docs/ioFog-ioMessage-Specification.md create mode 100644 docs/ioMessage-Formats-Specification.md create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 iofog-agent-client/build.gradle create mode 100644 iofog-agent-client/src/main/java/org/eclipse/iofog/Client.java create mode 100644 iofog-agent-client/src/main/resources/version.properties create mode 100644 iofog-agent-daemon/build.gradle create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/Daemon.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/IOFogModule.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineAction.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineConfigParam.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineParser.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellExecutor.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellResultSet.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/ImageDownloadManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceData.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/Display.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeEndpoints.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeInterface.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResource.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResourceManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentException.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentSystemException.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentUserException.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgent.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgentStatus.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/VersionHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/RequestType.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/VersionCommand.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/exceptions/UnknownVersionCommandException.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsMode.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsWebHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ApiHandlerHelpers.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/BluetoothApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/CommandLineApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigurationMap.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlSignalSentInfo.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketWorker.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/DeprovisionApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/EdgeResourceHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GetConfigurationHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GpsApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/InfoApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApi.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServer.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactory.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LogApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageCallback.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageReceiverHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSenderHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSentInfo.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketWorker.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ProvisionApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/QueryMessageReceiverHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/StatusApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/VersionApiHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebSocketMap.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebsocketUtil.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageReceiverWebSocketClientHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSenderWebSocketClientHandler.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSocketTestMain.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketReceiverClient.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketSenderClient.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestApiDriver.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestPublishTest.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestReceiveTest.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientControl.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientHandlerControl.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/IOMessageListener.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/Message.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageArchive.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusServer.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusUtil.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageIdGenerator.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessagePublisher.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageReceiver.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/EnvVar.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Microservice.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceState.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/PortMapping.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Registry.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Route.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMapping.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMappingType.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterface.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterfaceManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerTask.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/DockerUtil.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManagerStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/RestartStuckChecker.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/StatsCallback.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskComparator.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnection.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnectionStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManagerStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/pruning/DockerPruningManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManager.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManagerStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporter.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporterStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/Supervisor.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/SupervisorStatus.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/BytesUtil.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/CmdProperties.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Constants.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Orchestrator.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/Configuration.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/ConfigurationItemException.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/device_info/ArchitectureType.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Either.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function3.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function4.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Functions.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Left.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Pair.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Right.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Unit.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LogFormatter.java create mode 100755 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LoggingService.java create mode 100644 iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/trustmanager/TrustManagers.java create mode 100644 iofog-agent-daemon/src/main/resources/cmd_messages.properties create mode 100644 iofog-agent-daemon/src/main/resources/version.properties create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineActionTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineConfigParamTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineParserTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellExecutorTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellResultSetTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/ImageDownloadManagerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceDataTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManagerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/edge_resources/EdgeResourceManagerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentStatusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/VersionHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/enums/VersionCommandTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ApiHandlerHelpersTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/BluetoothApiHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/CommandLineApiHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ConfigApiHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlSignalSentInfoTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketWorkerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/DeprovisionApiHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GetConfigurationHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GpsApiHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/InfoApiHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactoryTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiStatusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LogApiHandlerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/MessageCallbackTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/IOMessageListenerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageArchiveTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusServerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusStatusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusUtilTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageIdGeneratorTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessagePublisherTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageReceiverTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerManagerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerTaskTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/DockerUtilTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerStatusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/RestartStuckCheckerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/StatsCallbackTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/pruning/DockerPruningManagerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerStatusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterStatusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorStatusTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/CmdPropertiesTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/OrchestratorTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/configuration/ConfigurationTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/device_info/ArchitectureTypeTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LogFormatterTest.java create mode 100644 iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LoggingServiceTest.java create mode 100644 iofog-version-controller/build.gradle create mode 100644 iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/Main.java create mode 100755 iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellExecutor.java create mode 100644 iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellResultSet.java create mode 100644 packaging/iofog-agent/debian.sh create mode 100644 packaging/iofog-agent/etc/bash_completion.d/iofog-agent create mode 100644 packaging/iofog-agent/etc/init.d/iofog-agent create mode 100644 packaging/iofog-agent/etc/iofog-agent/cert_new.crt create mode 100644 packaging/iofog-agent/etc/iofog-agent/config-bck_new.xml create mode 100644 packaging/iofog-agent/etc/iofog-agent/config-development_new.xml create mode 100644 packaging/iofog-agent/etc/iofog-agent/config-production_new.xml create mode 100644 packaging/iofog-agent/etc/iofog-agent/config-switcher_new.xml create mode 100644 packaging/iofog-agent/etc/iofog-agent/config_new.xml create mode 100644 packaging/iofog-agent/remove.sh create mode 100644 packaging/iofog-agent/rpm.sh create mode 100644 packaging/iofog-agent/upgrade.sh create mode 100755 packaging/iofog-agent/usr/bin/iofog-agent create mode 100644 packaging/iofog-agent/usr/share/iofog-agent/rollback.sh create mode 100644 packaging/iofog-agent/usr/share/iofog-agent/upgrade.sh create mode 100644 rest-api.yaml create mode 100644 scripts/bootstrap.sh create mode 100644 scripts/utils.sh create mode 100644 settings.gradle create mode 100644 test/deploy_ecn.bash create mode 100644 test/int_test.bats create mode 100644 test/resources/env.sh create mode 100644 test/run.bash diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..2b104fcf --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,173 @@ +# Changelog + +## [unreleased] + +## [v3.0.1] - 16- May 2022 +* Declared Agent dependency i.e. java and docker. + +## [v3.0.0] - 09- May 2022 +* A new config called TZ (timeZone) which is configurable is added to inject the same timezone as the device in the microservices. +* Removed java debugger. + +## [v3.0.0-beta7] -29 Mar 2022 +### Bugs +* Fixed the issue with adding extra-host while creating containers. +* Added a retry mechanism for fetching device ip address on creating containers. + +## [v3.0.0-beta6] -22 Feb 2022 +* Fixed the issue with updating yum package repository + +## [v3.0.0-beta5] -17 Feb 2022 +### Features +* Removed iofog-agent support for specific distros and Added support for any/any package which handles different distros. +### Bugs +* Fixed issue where microservice container was not spinning again if docker container was killed. +* Fixed the copyright to 2022 + +## [v3.0.0-beta4] -16 Dec 2021 +### Features +* Added support for raspbian/bullseye +### Bugs +* Revert fix for jdk 11 remote debugging + +## [v3.0.0-beta3] -1 Dec 2021 +### Bugs +* Send error message back to controller on failure to pull docker image +* Fixed issue with remote debugging with jdk 11 + +## [v3.0.0-beta2] - 27 Oct 2021 +#### Bugs +* Reset microservice container error message on successful microservice deployment + +## [v3.0.0-beta1] - 31 Aug 2021 +### Features +* Added support for Centos 8 + +## [v3.0.0-alpha2] - 16 July 2021 + +### Features +* Support trusted CAs for HTTPS to ioFog Controller + +## [v3.0.0-alpha1] - 24 March 2021 + +### Features +* Support for EdgeResources +* Dev mode added for enabling and disabling sentry notification on error +* Log format updated to Json +* Docker pull stats with percentage completion +* Send error back to controller on failure of volume mount on microservice + +## [v2.0.7] - 2021-06-30 +* Fixed the bug when config.xml file gets truncated and results in agent crash on re-start. +* Fixed the bug of resetting logger on every configuration update. Now logger resets only on log configuration update. + +## [v2.0.6] - 2021-04-28 + +### Bugs +* Removed configurable pruning scheduler + +## [v2.0.5] - 2021-02-25 + +### Bugs +* Fix upgrade version issue + +## [v2.0.4] - 2021-02-16 + +### Features +* Support for udp docker ports + +### Bugs +* Fix pruning bug when containers are not alive and updated logs +* Logs updated +* Microservice update container bug fix (Rebuild flag issue) + +## [v2.0.3] - 2020-11-24 + +### Bugs +* Fix bug of pruning live images on agent reboot + +## [v2.0.2] - 2020-10-23 + +### Features +* Added dir /var/log/iofog-microservices on install for microservice logs + +### Bugs +* Fixed docker pruning dead loop +* Fixed Gps mode null pointer +* Fixed rollback version issue + +## [v2.0.1] - 2020-09-10 + +### Bugs +* Default available disk threshold updated to 20 + +## [v2.0.0] -2020-23-10 + +### Bug fixes +* Stop and delete microservices when deprovision and delete agent respectively +* Enable deletion of other agents microservices +* Fixed microservice move bug +* Fixed deprovisioning and routing issues +* Fixed message routing + +## [v2.0.0-beta2] - 2020-04-06 + +### Features + +* Changed remote debugger port to 54322 +* Added extra hosts support + +## [v2.0.0-beta] - 2020-03-24 + +### Features + +* Skupper integration +* Agent docker pruning + +### Bug fixes + +* Stop and delete microservices when deprovision and delete agent respectively +* Enable deletion of other agents microservices +* Fixed microservice move bug +* Fixed deprovisioning and routing issues +* Fixed message routing + +## [v1.3.0] - 2020-11-21 + +### Features +* Report microservices lifecycle to Controller +* Showing available disk percentage in `iofog-agent info` output +* Volume mapping types +* Added extra hosts support +### Bug fixes +* Keep previous GPS coordinates when getting new one fails +* Remove microservices when Agent is moved to another ECN + +### Bugs + +* Create bound directories if do not exist +* Fixed incorrect content type error on cli commands +* Fixed NullPointer exception on microservice status +* Fixed the issue with microservice status + +[Unreleased]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-beta3..HEAD +[v3.0.0]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-beta7..v3.0.0 +[v3.0.0-beta7]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-beta6..v3.0.0-beta7 +[v3.0.0-beta6]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-beta5..v3.0.0-beta6 +[v3.0.0-beta5]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-beta4..v3.0.0-beta5 +[v3.0.0-beta4]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-beta3..v3.0.0-beta4 +[v3.0.0-beta3]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-beta2..v3.0.0-beta3 +[v3.0.0-beta2]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-beta1..v3.0.0-beta2 +[v3.0.0-beta1]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-alpha2..v3.0.0-beta1 +[v3.0.0-alpha2]: https://github.com/eclipse-iofog/agent/compare/v3.0.0-alpha1..v3.0.0-alpha2 +[v3.0.0-alpha1]: https://github.com/eclipse-iofog/agent/compare/v2.0.6..v3.0.0-alpha1 +[v2.0.7]: https://github.com/eclipse-iofog/agent/compare/v2.0.7..v2.0.7 +[v2.0.6]: https://github.com/eclipse-iofog/agent/compare/v2.0.5..v2.0.6 +[v2.0.5]: https://github.com/eclipse-iofog/agent/compare/v2.0.4..v2.0.5 +[v2.0.4]: https://github.com/eclipse-iofog/agent/compare/v2.0.3..v2.0.4 +[v2.0.3]: https://github.com/eclipse-iofog/agent/compare/v2.0.2..v2.0.3 +[v2.0.2]: https://github.com/eclipse-iofog/agent/compare/v2.0.1..v2.0.2 +[v2.0.1]: https://github.com/eclipse-iofog/agent/compare/v2.0.0-beta2..v2.0.1 +[v2.0.0-beta2]: https://github.com/eclipse-iofog/agent/compare/v2.0.0-beta..v2.0.0-beta2 +[v2.0.0-beta]: https://github.com/eclipse-iofog/agent/compare/v1.3.0..v2.0.0-beta +[v1.3.0]: https://github.com/eclipse-iofog/agent/tree/v1.3.0 \ No newline at end of file diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 00000000..ce66472e --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,55 @@ +# Contributing to Eclipse ioFog-Agent + +Thanks for your interest in this project. + +## Project description + +The Eclipse ioFog-Agent set of technologies is a fog computing layer that can be +installed on any hardware running Linux. Once installed, it provides a universal +runtime for microservices to run on the edge. In addition to a common runtime, +ioFog-Agent also provides a set of useful services including a message bus, dynamic +configuration of the microservices, and remote debugging. + +* https://projects.eclipse.org/projects/iot.iofog + +## Developer resources + +Information regarding source code management, builds, coding standards, and +more. + +* https://projects.eclipse.org/projects/iot.iofog/developer + +The project maintains the following source code repositories + +* https://github.com/ioFog +* http://git.eclipse.org/c/iofog/org.eclipse.iofog.git + +This project uses Bugzilla to track ongoing development and issues. + +* Search for issues: https://eclipse.org/bugs/buglist.cgi?product=IoFog +* Create a new report: https://eclipse.org/bugs/enter_bug.cgi?product=IoFog + +Be sure to search for existing bugs before you create another one. Remember that +contributions are always welcome! + +## Eclipse Contributor Agreement + +Before your contribution can be accepted by the project team contributors must +electronically sign the Eclipse Contributor Agreement (ECA). + +* http://www.eclipse.org/legal/ECA.php + +Commits that are provided by non-committers must have a Signed-off-by field in +the footer indicating that the author is aware of the terms by which the +contribution has been provided to the project. The non-committer must +additionally have an Eclipse Foundation account and must have a signed Eclipse +Contributor Agreement (ECA) on file. + +For more information, please see the Eclipse Committer Handbook: +https://www.eclipse.org/projects/handbook/#resources-commit + +## Contact + +Contact the project developers via the project's "dev" list. + +* https://dev.eclipse.org/mailman/listinfo/iofog-dev diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..2226df92 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,86 @@ +FROM docker.io/library/ubuntu:20.04 AS builder + +RUN apt-get update && \ + apt-get install -y unzip apt-utils curl openjdk-8-jdk && \ + apt-get clean + +# 1- Define a constant with the version of gradle you want to install +ARG GRADLE_VERSION=5.4 + +# 2- Define the URL where gradle can be downloaded from +ARG GRADLE_BASE_URL=https://services.gradle.org/distributions + +# 3- Define the SHA key to validate the gradle download +# obtained from here https://gradle.org/release-checksums/ +ARG GRADLE_SHA=c8c17574245ecee9ed7fe4f6b593b696d1692d1adbfef425bef9b333e3a0e8de + +# 4- Create the directories, download gradle, validate the download, install it, remove downloaded file and set links +RUN mkdir -p /usr/share/gradle /usr/share/gradle/ref \ + && echo "Downlaoding gradle hash" \ + && curl -fsSL -o /tmp/gradle.zip ${GRADLE_BASE_URL}/gradle-${GRADLE_VERSION}-bin.zip \ + \ + && echo "Checking download hash" \ + && echo "${GRADLE_SHA} /tmp/gradle.zip" | sha256sum -c - \ + \ + && echo "Unziping gradle" \ + && unzip -d /usr/share/gradle /tmp/gradle.zip \ + \ + && echo "Cleaning and setting links" \ + && rm -f /tmp/gradle.zip \ + && ln -s /usr/share/gradle/gradle-${GRADLE_VERSION} /usr/bin/gradle + +# 5- Define environmental variables required by gradle +ENV GRADLE_VERSION 5.4 +ENV GRADLE_HOME /usr/bin/gradle +ENV GRADLE_USER_HOME /cache +ENV PATH $PATH:$GRADLE_HOME/bin + +VOLUME $GRADLE_USER_HOME + +COPY . . + +RUN gradle build copyJar -x test --no-daemon + +FROM registry.access.redhat.com/ubi8/ubi-minimal:latest + +RUN true && \ + microdnf install -y curl ca-certificates java-11-openjdk-headless sudo shadow-utils && \ + microdnf clean all && \ + true + +RUN echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/lib/security/java.security + +COPY --from=builder packaging/iofog-agent/etc /etc +COPY --from=builder packaging/iofog-agent/usr /usr + +RUN true && \ + useradd -r -U -s /usr/bin/nologin iofog-agent && \ + usermod -aG root,wheel iofog-agent && \ + mv /etc/iofog-agent/config_new.xml /etc/iofog-agent/config.xml && \ + mv /etc/iofog-agent/config-development_new.xml /etc/iofog-agent/config-development.xml && \ + mv /etc/iofog-agent/config-production_new.xml /etc/iofog-agent/config-production.xml && \ + mv /etc/iofog-agent/config-switcher_new.xml /etc/iofog-agent/config-switcher.xml && \ + mv /etc/iofog-agent/cert_new.crt /etc/iofog-agent/cert.crt && \ + /etc/iofog-agent/local-api && \ + mkdir -p /var/backups/iofog-agent && \ + mkdir -p /var/log/iofog-agent && \ + mkdir -p /var/lib/iofog-agent && \ + mkdir -p /var/run/iofog-agent && \ + chown -R :iofog-agent /etc/iofog-agent && \ + chown -R :iofog-agent /var/log/iofog-agent && \ + chown -R :iofog-agent /var/lib/iofog-agent && \ + chown -R :iofog-agent /var/run/iofog-agent && \ + chown -R :iofog-agent /var/backups/iofog-agent && \ + chown -R :iofog-agent /usr/share/iofog-agent && \ + chmod 774 -R /etc/iofog-agent && \ + chmod 774 -R /var/log/iofog-agent && \ + chmod 774 -R /var/lib/iofog-agent && \ + chmod 774 -R /var/run/iofog-agent && \ + chmod 774 -R /var/backups/iofog-agent && \ + chmod 754 -R /usr/share/iofog-agent && \ + chmod 774 /etc/init.d/iofog-agent && \ + chmod 754 /usr/bin/iofog-agent && \ + chown :iofog-agent /usr/bin/iofog-agent && \ + true + +CMD [ "java", "-jar", "/usr/bin/iofog-agentd.jar", "start" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..8079d9d2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,278 @@ +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and + + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. + +"Contributor" means any person or entity that Distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. + +"Program" means the Contributions Distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. + +2. GRANT OF RIGHTS + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). + +3. REQUIREMENTS + +3.1 If a Contributor Distributes the Program in any form, then: + + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and + + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; + + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; + + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. + +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. + +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + diff --git a/NOTICE b/NOTICE new file mode 100644 index 00000000..cdbbcbfc --- /dev/null +++ b/NOTICE @@ -0,0 +1,42 @@ +# Notices for Eclipse ioFog-Agent + +This content is produced and maintained by the Eclipse ioFog-Agent project. + +* Project home: https://projects.eclipse.org/projects/iot.iofog + +## Trademarks + +Eclipse ioFog-Agent is a trademark of the Eclipse Foundation. + +## Copyright + +All content is the property of the respective authors or their employers. For +more information regarding authorship of content, please consult the listed +source code repository logs. + +## Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License v. 2.0 which is available at +http://www.eclipse.org/legal/epl-2.0. + +SPDX-License-Identifier: EPL-2.0 + +## Source Code + +The project maintains the following source code repositories: + +* https://github.com/ioFog +* http://git.eclipse.org/c/iofog-agent/org.eclipse.iofog.git + +## Third-party Content + +## Cryptography + +Content may contain encryption software. The country in which you are currently +may have restrictions on the import, possession, and use, and/or re-export to +another country, of encryption software. BEFORE using any encryption software, +please check the country's laws, regulations and policies concerning the import, +possession, or use, and re-export of encryption software, to see if this is +permitted. + diff --git a/README.md b/README.md new file mode 100644 index 00000000..07c10bca --- /dev/null +++ b/README.md @@ -0,0 +1,105 @@ +# The ioFog Agent product + +Imagine a world where you can choose self-contained pieces of code (called microservices) and make them run anywhere you want at the push of a button. Where you can remotely control the code that is running on twenty iPhones in workers' pockets, thirty servers running in a factory building, and ten computers running in the trucks that ship your products... and you can do it all with the same single technology. Imagine a world where you move the processing close to where the data is happening, and where you can finally separate streams of information from the end applications that use them. This world will be brought to life by iofog, and a big part of the vision is the ioFog product that runs on the various computers. + +This repository is the production code base for the x86 Linux version of the ioFog product. + +ioFog is a service that runs constantly in the background on a Linux machine. It is the agent that turns a Linux computer into a piece of the iofog I/O compute fog. + +There should be an ioFog code base for every processing platform that becomes part of the I/O compute fog. Network connectivity, process invocation, thread management, and other details of an ioFog will vary from platform to platform. The same ioFog principles apply to every version, but the implementation of the principles should match the native languages and structures best suited for the platform. + +### Status + +![](https://img.shields.io/github/release/iofog/agent.svg?style=flat) +[![Build Status](https://travis-ci.org/ioFog/Agent.svg)](https://travis-ci.org/ioFog/Agent) + +![](https://img.shields.io/github/repo-size/iofog/agent.svg?style=flat) +![](https://img.shields.io/github/last-commit/iofog/agent.svg?style=flat) +![](https://img.shields.io/github/contributors/iofog/agent.svg?style=flat) +![](https://img.shields.io/github/issues/iofog/agent.svg?style=flat) + +![Supports amd64 Architecture][amd64-shield] +![Supports aarch64 Architecture][arm64-shield] +![Supports armhf Architecture][arm-shield] + +[arm64-shield]: https://img.shields.io/badge/aarch64-yes-green.svg +[amd64-shield]: https://img.shields.io/badge/amd64-yes-green.svg +[arm-shield]: https://img.shields.io/badge/armhf-yes-green.svg + +### Principles of an ioFog Agent: + +* Never go down +* Respond immediately to the fog controller +* Operate flawlessly when offline +* Report status frequently and reliably +* Execute instructions with no understanding of the bigger picture +* Provide a high-performance message bus and local API +* Enforce the configured resource consumption constraints strictly +* Allow the most flexible and powerful processing element model possible +* Be able to instantiate processing elements from any available source +* Be able to communicate with any reachable fog controller +* Allow processing elements to implement security and connectivity as they would natively +* Ensure that complying with the local API is the only requirement placed on a processing element +* Only shutdown or restart processing elements when requested or when absolutely necessary +* Run only processing elements with verified source and integrity +* Never allow a message to reach unauthorized processing elements +* Only allow messages of the proper registered type to reach processing elements +* Guarantee message source and order + + +See the docs folder in this repository for architecture, project microculture, engineering philosophy, functional specifications, and more. + +**ioFog Agent Setup** + +The entire ioFog platform is best deployed through the unified CLI: `iofogctl`. + +Go to [iofog.org](https://iofog.org/docs/) to learn how to deploy the ioFog Control Plane and Agents. + +**Usage** + +1. To view help menu + + sudo iofog-agent help + +2. To view current status + + sudo iofog-agent status + +3. To view version and license + + sudo iofog-agent version + +4. To view current configuration + + sudo iofog-agent info + +5. Provision iofog for use + + sudo iofog-agent provision ABCDWXYZ + +**Logs** +- Log files are located at '/var/log/iofog-agent' + +**System Requirements (Recommended)** +- Processor: 64 bit Dual Core or better +- RAM: 1 GB minimum +- Hard Disk: 5 GB minimum +- Java Runtime (JRE) 8 or higher +- Docker 1.10 or higher +- Linux kernel 3.10 or higher + +**Platforms Supported (Ubuntu Linux)** +- 14.04 - Trusty Tahr +- 16.04 - Xenial Xerus + + + - ioFog Agent Update: + + sudo service iofog-agent stop + sudo apt-get install --only-upgrade iofog-agent + sudo service iofog-agent start + or + sudo service iofog-agent stop + sudo apt-get install --only-upgrade iofog-agent-dev (developer's version) + sudo service iofog-agent stop + diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 00000000..e7bf728f --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,240 @@ +trigger: + tags: + include: + - v* + branches: + include: + - develop + - release* + + paths: + exclude: + - README.md + - CHANGELOG.md + - LICENSE + +variables: + repository: 'focal-freedom-236620/agent' + buildTag: $(Build.BuildId) + ref: $(Build.SourceBranch) + branchTag: $(Build.SourceBranchName) + imageTag: + +jobs: + - job: Agent + pool: + vmImage: 'Ubuntu-18.04' + + steps: + - task: Gradle@2 + inputs: + workingDirectory: '' + gradleWrapperFile: 'gradlew' + gradleOptions: '-Xmx3072m' + javaHomeOption: 'JDKVersion' + jdkVersionOption: '1.8' + jdkArchitectureOption: 'x64' + publishJUnitResults: true + testResultsFiles: '**/TEST-*.xml' + tasks: 'build' + + - task: Palmmedia.reportgenerator.reportgenerator-build-release-task.reportgenerator@4 + displayName: ReportGenerator + inputs: + reports: '$(System.DefaultWorkingDirectory)/iofog-agent-daemon/build/reports/jacoco/test/jacocoTestReport.xml' + targetdir: '$(System.DefaultWorkingDirectory)/iofog-agent-daemon/build/reports/jacocoHtml' + reporttypes: 'HtmlInline_AzurePipelines;Badges' + + - task: PublishCodeCoverageResults@1 + inputs: + codeCoverageTool: 'JaCoCo' + summaryFileLocation: '$(System.DefaultWorkingDirectory)/iofog-agent-daemon/build/reports/jacoco/test/jacocoTestReport.xml' + reportDirectory: '$(System.DefaultWorkingDirectory)/iofog-agent-daemon/build/reports/jacocoHtml' + displayName: 'Publish Code Coverage Results' + + # We need nodejs for Snyk + - task: NodeTool@0 + inputs: + versionSpec: '12.x' + displayName: 'Install Node.js' + + - script: | + ./gradlew copyJar + displayName: 'copy jars' + + - script: | + npm i -g snyk + snyk monitor --project-name=AgentCI + env: + SNYK_TOKEN: $(snykToken) + displayName: 'Snyk monitor' + + - script: | + if [[ $(ref) == refs/tags* ]]; then + TAG=$(echo $(ref) | sed "s|refs/tags/v||g") + echo "##vso[task.setvariable variable=imageTag]$TAG" + else + LATESTTAG=$(git tag | tail -1) + LATESTVERS=${LATESTTAG#?} + if [ -z "$LATESTVERS" ]; then LATESTVERS=0.0.0; fi + echo "##vso[task.setvariable variable=imageTag]$LATESTVERS-b$(buildTag)" + fi + echo $(imageTag) + displayName: 'Setup supplementary tag for docker images' + name: setvarStep + + - script: | + echo "gcr.io/$(repository):$(imageTag)" > GCR_DOCKER_IMAGE + displayName: 'Save Docker image name and tag to GCR_DOCKER_IMAGE into artifacts' + + - task: Docker@2 + displayName: 'build docker image' + inputs: + containerRegistry: 'Edgeworx GCP' + repository: $(repository) + command: 'build' + Dockerfile: 'Dockerfile' + tags: | + $(imageTag) + $(branchTag) + latest + + - task: Docker@2 + displayName: 'push docker image' + inputs: + containerRegistry: 'Edgeworx GCP' + repository: $(repository) + command: 'push' + Dockerfile: 'Dockerfile' + tags: | + $(imageTag) + $(branchTag) + latest + condition: or(and(succeeded(), startsWith(variables['build.sourceBranch'], 'refs/heads/release/')), and(succeeded(), eq(variables['build.sourceBranch'], 'refs/heads/develop')), and(succeeded(), startsWith(variables['build.sourceBranch'], 'refs/tags/'))) + + + - script: | + echo "api test" + displayName: 'api tests' + + - task: DownloadSecureFile@1 + inputs: + secureFile: 'package_cloud' + displayName: 'download package cloud token file' + + - task: UseRubyVersion@0 + inputs: + versionSpec: '= 2.7' + addToPath: true + displayName: 'install rubygem to be used to install package_cloud cli' + + - script: | + gem install --no-document fpm + fpm -h + gem install package_cloud + package_cloud -h + echo "config file..." + echo $DOWNLOADSECUREFILE_SECUREFILEPATH + displayName: 'install package_cloud cli and fpm' + + - script: | + version=$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}') + if [[ $(Build.SourceBranch) == refs/tags* ]]; then + pkg_version=$version + else + pkg_version=$version-b$(Build.BuildId) + fi + echo $pkg_version + cd packaging/iofog-agent + fpm -s dir -d 'openjdk-8-jdk | openjdk-11-jdk' -d docker -t deb -n iofog-agent -v $pkg_version -a all --deb-no-default-config-files --after-install debian.sh --after-remove remove.sh --before-upgrade upgrade.sh --after-upgrade debian.sh etc usr + echo "pkg maybe created" + echo $(ls | grep *.deb) + echo "##vso[task.setvariable variable=pkg_version]$pkg_version" + displayName: 'create deb package' + + - script: | + sudo apt-get install jq + displayName: 'install jq' + + - script: | + curl https://packagecloud.io/install/repositories/iofog/iofogctl/script.deb.sh | sudo bash + sudo apt-get install iofogctl=$(iofogctl.version) + displayName: 'install iofogctl' + + - script: | + sudo apt-get update -y + sudo apt-get install -y bats + displayName: 'install bats' + + - script: | + sed -i "s|CONTROLLER_IMAGE=.*|CONTROLLER_IMAGE=\"$(controller.image)\"|g" test/resources/env.sh + sudo bash test/deploy_ecn.bash deployControlPlane + displayName: 'deploy local ecn' + + - script: | + sudo bash test/run.bash + displayName: 'integration test' + + - script: | + sudo bash test/deploy_ecn.bash deleteECN + displayName: 'delete ecn' + condition: always() + + - script: | + cd packaging/iofog-agent + package=$(ls | grep *.deb) + echo "package..." + echo $package + + package_cloud push iofog/iofog-agent-dev/any/any $package --config=$DOWNLOADSECUREFILE_SECUREFILEPATH + displayName: 'publish deb to package-cloud' + + - script: | + cd packaging/iofog-agent + echo $(pkg_version) + fpm -s dir --depends java-11-openjdk -d docker-ce -t rpm -n iofog-agent -v $(pkg_version) -a all --rpm-os 'linux' --after-install rpm.sh --after-remove remove.sh --before-upgrade upgrade.sh --after-upgrade rpm.sh etc usr; + echo "pkg maybe created" + echo $(ls | grep *.rpm) + displayName: 'create rpm package' + + - script: | + cd packaging/iofog-agent + package=$(ls | grep *.rpm) + echo "package..." + echo $package + + package_cloud push iofog/iofog-agent-dev/rpm_any/rpm_any $package --config=$DOWNLOADSECUREFILE_SECUREFILEPATH + displayName: 'publish rpm to package-cloud' + + - script: | + cd packaging/iofog-agent + sed -i.bak 's/default/dev/g' etc/iofog-agent/config-switcher_new.xml + fpm -s dir -d 'openjdk-8-jdk | openjdk-11-jdk' -d docker -t deb -n iofog-agent -v 0.0.0-dev -a all --deb-no-default-config-files --after-install debian.sh --after-remove remove.sh --before-upgrade upgrade.sh --after-upgrade debian.sh etc usr + echo "pkg maybe created" + echo $(ls | grep *.deb) + echo "package..." + package=$(ls | grep *.deb) + echo $package + package_cloud yank iofog/iofog-agent-dev/any/any iofog-agent_0.0.0-dev_all.deb --config=$DOWNLOADSECUREFILE_SECUREFILEPATH + package_cloud push iofog/iofog-agent-dev/any/any iofog-agent_0.0.0-dev_all.deb --config=$DOWNLOADSECUREFILE_SECUREFILEPATH + displayName: 'publish deb develop package to package-cloud' + condition: and(succeeded(), eq(variables['build.sourceBranch'], 'refs/heads/develop')) + + - task: CopyFiles@2 + inputs: + SourceFolder: $(System.DefaultWorkingDirectory) + TargetFolder: $(Build.ArtifactStagingDirectory) + Contents: | + GCR_DOCKER_IMAGE + **/libs/** + packaging/**/* + Dockerfile + OverWrite: true + displayName: 'copy all artefacts' + + - task: PublishBuildArtifacts@1 + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)' + ArtifactName: 'agent' + displayName: 'publish artefacts' + diff --git a/build.gradle b/build.gradle new file mode 100644 index 00000000..9fbfb5ee --- /dev/null +++ b/build.gradle @@ -0,0 +1,26 @@ +plugins { + id "com.github.johnrengelman.shadow" version "5.0.0" apply false +} + +allprojects { + group = 'org.eclipse' + version = '3.0.1' +} + +subprojects { + + apply plugin: 'java' + + repositories { + mavenCentral() + mavenLocal() + } + + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + + task sourcesJar(type: Jar) { + from sourceSets.main.allJava + archiveClassifier = 'sources' + } +} diff --git a/docs/Project-Microculture.md b/docs/Project-Microculture.md new file mode 100644 index 00000000..4e7fcfe0 --- /dev/null +++ b/docs/Project-Microculture.md @@ -0,0 +1,28 @@ +# Project Microculture + +Every project has different requirements and principles. This should result in a different engineering philosophy per project, with each philosophy focusing on what really matters for the project at hand. Whether or not this happens on other projects, it is definintely happening here. + +In addition to an engineering philosophy, every project should have a set of rules, protocols, and expected behaviors for the people involved. These things, coupled with the engineering philosophy, form a tiny little culture (a microculture) that drives the way people interact with each other and with the project iteself. + +In order to contribute to ioFog, you must buy into the project's microculture entirely. + +A good way to capture the essence of a microculture is to put it in the form of belief statements. The statements may be refined or changed over time by the microculture administrator (usually the owner of the project) in order to serve the project better. For the most part, however, the statements are stable and should be treated as the project's code of conduct. + +### Our Microculture Statements (in no particular order) + +* We use the minimal amount of code for the job +* We believe that nobody will ever reuse our classes so we code for our own efficiency +* We comment code well enough that we don't have to read the code to know what it does +* We use the simplest and most direct approach for every task +* We keep the number of 3rd party libraries and dependencies to an absolute minimum +* We write very protective code that handles all exception types instead of throwing errors +* We avoid writing any build tool dependencies into the actual code base +* We write great performance tests and unit tests so we can evaluate quality on many levels at build time +* We choose to write fast executing code over code that's easy to write and maintain +* We talk openly about what is not working or where we need help +* We engineer with a focus on the long term quality and reliability of the project +* We are always available at the scheduled times so we can rely on each other as team members +* We teach each other when we know more +* We learn from each other when we know less +* We try to use standard practices for each language and framework so nobody has to spend time figuring out our techniques +* We respect each other's ideas and we listen to them fully before responding diff --git a/docs/base64test.jpg b/docs/base64test.jpg new file mode 100644 index 0000000000000000000000000000000000000000..7fc2388d3cde11bd35df544d30c2b7b2b84f38ae GIT binary patch literal 6714 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 QMNZ%Rvz`9`y&aGI7xGvUjsO4v literal 0 HcmV?d00001 diff --git a/docs/ioFog-Architecture-Diagram.png b/docs/ioFog-Architecture-Diagram.png new file mode 100644 index 0000000000000000000000000000000000000000..d3053a8b2534b059832983913669e2d77912f63a GIT binary patch literal 56432 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 + +## Module Details + +The following breakdown of functional modules gives detailed descriptions and functional requirements. Even though the functionality of ioFog has been split into modules, that does not mean that the actual code should contain separate libraries or separately compiled components. In some cases it might, but this is not necessary. The goal is to keep the duties of the application clearly categorized so repeat code is minimized and so coding tasks are grouped. + +Implement the functional requirements for each module and across modules in a way that fits the underlying compute platform (such as x86 Linux) in the best way possible. + +### Supervisor + +The supervisor module is the root thread of the ioFog application. It is repsonsible for launching the other modules and monitoring them to make sure they are always running. The supervisor module should never stop running unless the user stops the ioFog application service. It should start when the system boots unless the user manually removes the automatic starting of the ioFog service. + +The supervisor doesn't provide much of the actual ioFog functionality but it does provide the key application features that are exposed to the user. Each ioFog instance is tied to a particular fog controller and user account through a provisioning process. The provisioning functionality is handled by the supervisor module, which then passes the information down to the field agent. Each ioFog instance is also manually configurable through its configuration file located in the installation directory. The supervisor module is responsible for parsing the configuration file and passing the different pieces of configuration information to the other modules. + +The supervisor module exposes several command-line interactions that the Linux system user can use to set up, monitor, and control ioFog. + +#### Functional Requirements + +* Be the main executable process of the ioFog product (the main thread) +* Parse the product's configuration XML file +* Store the product's configuration in memory for use while the software is operational +* Pass configuration information into the other modules of the software where needed +* Write configuration changes to the configuration XML file as they occur during software operation +* Pass updated configuration changes into the other modules of the software as needed as they occur +* Store configuration changes in memory as they occur +* Provide command-line interface functionality according to the command-line interface specification document +* Parse and handle the configuration according to the configuration specification document +* Monitor the status of the other modules +* Make the status of itself (the supervisor module) and the other modules available via command-line and available to the status reporting module +* Start the other modules and manage multi-threading as needed +* Restart the other modules on a decreasing frequency basis when they fail (immediately, then after 10 seconds, then 30 seconds, then 1 minute, and so on) +* Provide logging functionality to all other modules +* Initiate and manage the software's log files +* Log start-up and shut-down sequences +* Log module starts, stops, and restarts +* Log configuration XML file parsing +* Log configuration changes + + +#### Performance Requirements + +* Start immediately (as fast as possible) +* Use as little memory, disk space, and CPU time as possible +* Monitor modules frequently enough to be performant but also keep CPU consumption to a minimum + + +### Resource Consumption Manager + +The Resource Consumption Manager is in charge of monitoring the usage behavior of the whole application. Timeliness and efficiency are more important than precision. It is easy to monitor resources with heavy code. The problem is, this precise monitoring drags on system performance and consumes significant resources itself. + +The Resource Consumption Manager in ioFog should be checking frequently enough to be effective, but should only cause minimal drain on CPU time and other resources. + +In some cases, the Resource Consumption Manager needs to tell another module to "curb its behavior" and use less memory or curtail processing because the CPU usage is too high. In other cases, the modules themselves are supposed to keep their usage to a set limit (such as logging) and the job of Resource Consumption Manager is simply to monitor and report violations. + +In production systems, users will be expecting ioFog to stay within certain resource consumption boundaries. The ioFog product needs to be reliable in its self-management so it will operate peacefully with other software. + +#### Functional Requirements + +* Check how much RAM the program is using +* Check what percentage of CPU time the program is using +* Check how much disk space the logging functionality is using +* Check how much disk space the Message Bus is using +* Report usage violations to the Status Reporter +* Tell the Process Manager to curtail its CPU and/or RAM usage +* Tell the Message Bus to curtail its CPU and/or RAM usage + + +#### Performance Requirements + +* Use minimal resources to monitor resource consumption +* Catch resource usage violations within a few seconds + + +### Status Reporter + +The Status Reporter is the central place for finding the program's status. It can be thought of as both a place to deposit status (if you are a module) and a place to get the status information you need. Some types of status are measurements of progress. Some types of status are boolean (we just need to know if something is running or not). By centralizing the management of status in the application, we simplify current usage across the code base and make it much easier to track more status in the future. + +Other than serving as the status repository, the only activity that the Status Reporter performs is to judge whether or not newly updated status information should be sent to the fog controller. This happens via the Field Agent, so the Status Reporter is just repsonsible for juding the importance of the information and, if needed, telling the Field Agent to report new information to the fog controller. + +#### Functional Requirements + +* Store status information centrally for all modules and parts of the program +* Allow all modules and parts of the program to update their status +* Store status information according to the Status Information Specification Document +* Check each status information change to see if it should be reported to the fog controller +* Tell the Field Agent to report changes to the fog controller whenever there is a qualifying status information change +* Allow the command line program to access the status information +* Allow all modules and parts of the program to access the status information + + +#### Performance Requirements + +* Store status information quickly when changes are submitted +* Access and deliver status information quickly when it is requested + + +### Process Manager + +The Process Manager module is in charge of starting, stopping, and generally controlling the processes that run in ioFog. In the case of this particular ioFog version, the processes take the form of Linux kernel containers running via Docker. These processes are often called ioElement containers or sometimes just elements in the overall iofog system. They are the actual computing tasks that are taking place on the iofog I/O compute fog. There is no need for ioFog to have any awareness of what the processes might be. It only needs to manage them properly and manage them all exactly the same. Through that standardization, all ioElement containers become portable and re-usable from one part of the fog to another. + +The Process Manager needs to interface with the Docker daemon to get a lot of its work done. It does that through the socket defined in the ioFog configuration. The default is for Docker to communicate using Unix sockets, which is the most secure and is very fast. Therefore the default configuration in ioFog is to use that default Docker setup. If the ioFog user wants to run Docker over a TCP/IP socket, they are allowed to do so. As long as they enter the correct socket setting in the ioFog configuration, everything should work as expected. + +This module needs to be aware of the containers that are supposed to be running, and it also needs to figure out what to start up and what to shut down when the list of containers changes. It should leverage Docker's functionality as much as possible, leaving almost 100% of the container handling to the Docker daemon but telling Docker what exactly it should do. + +#### Functional Requirements + +* Maintain a list of containers that are supposed to be running on this ioFog instance +* Add and remove containers from the list as updates to the list are provided +* Shut down Docker containers when they are removed from the list +* Start up Docker containers when they are added to the list +* Make sure all Docker containers are started the system is rebooted (but do not restart them without need) +* Restart Docker containers that are supposed to be running if they go down +* Build Docker containers that are not yet created locally +* Give Docker instructions in parallel in order to take advantage of its multi-threading and speed +* Restart specific Docker containers with updated network port settings when port changes for a container are provided +* Name each Docker container with the ioElement ID that is provided in the container list item details +* Map a network host into each Docker container as "iofog:#.#.#.#" where the actual IP address of the ioFog instance is used in place of the # signs +* When setting up a container with Docker, set the "restart policy" to restart 10 times +* Map an environment variable into each Docker container as "SELFNAME=ABCDEFG" where the ioElement ID is used in place of the ABCDEFG +* Maintain a list of Docker registries that are supposed to be used with this ioFog instance +* Make sure Docker verifies the signature and identity of every container image (requires Docker 1.8+ and may not require any effort on our part) +* Rebuild specific Docker containers (fetching the image again from the correct registry) when instructed +* Accept certificate files for Docker registries and associate them with the proper registry in the list and store them in the correct place for Docker to access them +* Accept login credentials for Docker registries and associate them with the proper registry in the list +* Make the Docker daemon login to registries as needed +* Communicate with the Docker daemon using the socket defined in the ioFog configuration +* Report Process Manager status information to the Status Reporter module according to the Status Information Specification document + + +#### Performance Requirements + +* Docker caches container images and the sub-images within them - leverage the speed of Docker's caching whenever possible (such as getting the Ubuntu 14.04 container once on the first container before starting up the other Ubuntu containers) + +* Respond to container list changes as quickly as possible without breaking stability - for example, when a container is removed from the list you should shut it down immediately... but if it is still being built then you might have to wait or submit a different command to Docker to stop the build + +* Avoid restarting containers unless it is necessary (when containers are restarted, it takes time and can seriously interrupt data flow) - the appropriate times to restart containers are as follows: + * When the container has been stopped or has crashed + * When the network and port mapping for the container has changed + * When the container needs to be rebuilt + +* Never ever ever miss a container update or change to the container list - if a new port is opened for a container, make absolutely sure that the container gets restarted with the port opened (unless there is an error, in which case you should make absolutely sure that the error gets reported in the status) + +* Do not let errors interrupt or corrupt the remaining tasks that Process Manager is performing - such as when the Docker daemon throws an error on building a container... you should still make sure to build all of the other containers + + +### Local API + +The Local API module is the part of ioFog that creates an interface for ioElement containers to interact with the system (and therefore with other containers indirectly). Many systems allow plugins and 3rd party modules to be built, but they usually require a certain language or an SDK library to be compiled into the plugin. The Internet of Things needs a more general interaction model than that. Processes running on the I/O Compute Layer (made up of ioFog instances) should have the same flexibility as processes running on the general Internet. But how can this be accomplished? + +We can accomplish it by using standard Web technologies, such as REST APIs, at the "edge" instead of just in the cloud. Because the actual code being executed in ioFog is in containers, it is isolated from all of the other running code. This is great for security and stability and dynamic allocation... but it makes interconnectivity much more difficult. Instead of trying to make containers talk to each other, we just have every container talk to a single trusted local source. That is the Local API module. By offering Web technologies, programmers who understand regular network and Web programming can now build for the Internet of Things without changing languages, frameworks, libraries, and tool sets. + +The Local API offers two communication methods. The first is a REST API. Just like other common REST APIs, it accepts JSON and responds in JSON. There are documented endpoints. When an endpoint is accessed, a response is provided and the connection is ended. The REST API portion of the Local API needs to be very responsive and capable of handling a high transaction volume. As the number of containers rises, the number of requests will rise dramatically. + +The second communication method is a set of Websockets. There are two types of Websockets. One is for messages and the other is for control signals. For a container to get connected to the Websockets, it should be able to open a connection to a pre-defined endpoint and maintain the connection using standard methods. The control signal Websocket will allow the Local API to give the container new configuration information in real time or pass it other control signals in real time. The message Websocket will allow the Local API to give the container its data messages in real time as they arrive at the Local API. The same message Websocket also allows the container to publish data messages into the Local API, which will be passed to the Message Bus, which will route them. + +When using the REST API, containers need to transact in JSON. This means that any binary data will have to be encoded using base64 so it can be passed as UTF-8 information. The CPU cost of doing the encoding and decoding of the bytes is unfortunate and the encoded bytes take up about 30% more space. But the flexibility, ease of use, and universality of JSON and the REST API still make it a valuable communication channel on the edge. + +To send and receive information in real time, containers must use the Websockets. In addition to having messages "pushed" to them instead of "polling", the containers also receive the messages as pure bytes. No base64 encoding and decoding is required. For streaming media messages such as photos and video, the Websockets are the most appropriate choice. + + +#### Functional Requirements + +* Report Local API status information to the Status Reporter module according to the Status Information Specification document +* Provide a REST API on port 54321 according to the Local API Specification document +* Provide a real-time message Websocket according to the Local API Specification document +* Provide a real-time control Websocket according to the Local API Specification document +* Receive messages from the Message Bus and move them to the proper recipients through the real-time message sockets +* Receive notification of configuration changes for the containers from the Field Agent module and move them to the proper recipients through the real-time control sockets +* Hold the most recent configuration information for all containers in memory so the access is very fast +* Retrieve the stored container configuration from the proper application component when the Local API module starts up +* Update the container configuration held in memory when configuration changes are received +* Get the next messages for a particular container from the Message Bus module when needed +* Get the queried set of messages from the Message Bus module as needed +* Allow messages transacted over the Websockets to be pure bytes (no base64 encoding required) +* Receive newly published messages from containers and deliver them to the Message Bus module +* Handle erroneous API inputs gracefully and give proper responses back to the offending containers + + +#### Performance Requirements + +* Handle dozens of simultaneous maintained Websocket connections +* Handle at least 20,000 REST API requests per minute +* Add only minimal latency to the delivery of messaages between the Message Bus module and containers +* Respond to REST API requests within 100 milliseconds on average +* Be available to containers at all times + + +### Field Agent + +The Field Agent module handles all of the communication with the fog controller. It serves as the provider of updates to the other modules. It also sends updates to the fog controller on behalf of the other modules. The Field Agent is in charge of establishing and maintaining a connection to the fog controller at all times, and it must report a change in connection status when it occurs. In addition, the Field Agent is in charge of verifying the identity of the configured fog controller before communicating with it. + +In this version of ioFog, the Field Agent learns about new commands from the fog controller through simple polling of the fog controller REST API. In order to be performant, the ioFog instance must poll somewhat frequently. In future versions of ioFog, a Websocket connection will allow real-time delivery of changes and commands from the fog controller without cyclical polling. + + +#### Functional Requirements + +* Validate the identity of the fog controller using the certificate path in the ioFog configuration +* Update the fog controller identity verification status when it changes +* Post status information to the fog controller according to the Fog Controller API Specification document +* Check the fog controller regularly for changes +* Ping the fog controller regularly to make sure it is online and reachable +* Update the fog controller connection status when it changes +* Perform the provisioning process with the fog controller when requested to do so +* Use the provisioning token passed via the command line when performing the provisioning process +* Store the results of the provisioning process in the ioFog configuration +* Get update configuration information for the ioFog instance when it has changed +* Store the updated configuration in the ioFog configuration when it has changed +* Alert other modules that their configuration has been changed but only alert the modules that have actual changes +* Post the ioFog configuration to the fog controller when it changes locally +* Always communicate with the fog controller over a secure connection (TLS using HTTPS) +* Provide a common code class for all modules to get the current list of containers, container configuration, routing, and list of registries +* Read the list of containers, container configuration, routing, and list of registries stored on disk when starting and populate the common code class +* Write the udpated list of containers, container configuration, routing, and list of registries to disk when any of them change +* Do not communicate with a fog controller if the ioFog instance has been deprovisioned +* Update the "Last Command Time" status whenever any changes are received from the fog controller +* Do not communicate with a fog controller that has failed the identity verification process +* Get the updated container list when it changes +* Alert the Process Manager module that the container list has changed +* Store the updated container list in memory in the common code class +* Get the updated container configuration when it changes +* Alert the Local API module that the container configuration has changed +* Store the updated container configuration in memory in the common code class +* Get the updated routing when it changes +* Alert the Message Bus module that the routing has changed +* Store the updated routing in memory in the common code class +* Get the updated list of registries when it changes +* Alert the Process Manager module that the list of registries has changed +* Store the updated list of registries in memory in the common code class + + +#### Performance Requirements + +* Retrieve detailed changes immediately when a type of changes is indicated by the fog controller +* Check the fog controller connection every 60 seconds or more frequently +* Check the fog controller for changes every 30 seconds or more frequently +* Post status changes immediately when they occur +* Post ioFog configuration changes immediately when they occur + + +### Message Bus + +The Message Bus module moves the actual data messages around the ioFog system. Through the Local API, it can receive new messages and deliver messages to the recipient containers. The Message Bus must be as fast as possible in order to avoid adding latency to data moving through ioFog. The Message Bus maintains a routing table that it uses to determine the recipients of each message. The routing table is subject to change, with new routing information coming through the operations of the Field Agent module. + +The Message Bus is a combination of a disk spooling message bus and an in-memory message bus. All messages are spooled to disk for future retrieval and for archival purposes. But messages written to disk and read from disk are not the highest speed messages. All messages are also either delivered to recipients in real-time (through the Local API message Websockets) or held in memory for the fastest delivery when a recipient container requests its next messages. This dual-mode operation gives the Message Bus module of ioFog fast performance with message reliability. + +The Message Bus must assign a unique message ID to each newly posted message. The specification for message IDs can be found in the ioMessage Specification document. Because these unique IDs are rather large, the Message Bus might be best implemented having a ready pool of pre-calculated but unused message IDs to draw from in real-time processing. + + +#### Functional Requirements + +* Hold routing information in memory in a way that facilitates rapid routing of messages +* Store the current unread messages for each container in memory +* Limi the number of unread messages stored for each container to the total allowed RAM usage divided by the number of containers +* Respond to a request to reduce RAM usage by eliminating the oldest messages held in memory until the RAM usage is within limits +* Respond to a request to reduce disk usage by deleting the oldest messages from disk storage until the disk usage is within limits +* Provide a way for the Local API module to get the currently unread messages for a specified recipient container +* Provide a way for the Local API module to post new messages to the Message Bus +* Respond to the Local API with a unique message ID and the timestamp of the message receipt when the Local API posts a new message +* Look up the routing of the publisher when a new message is posted and perform the following tasks: + * Send the message as a real-time message to the Local API for each recipient + * If the Local API indicates that the recipient did not receive the message via a real-time connection, place the message in memory in the currently unread message list for that recipient container +* Write all newly posted messages to disk +* Use the configured disk location to store messages +* Write a separate set of message storage files for each publisher +* Start a new message file for a publisher when the previous file contains 1,000 messages +* Name each file as follows: + * uh43wiufdsiushdfkj_1234567890123_9876543210123.iomsg where "uh43wiufdsiushdfkj" is the publishing container ID and "1234567890123" is the timestamp of the oldest message in the file and "9876543210123" is the timestamp of the newest message in the file + * Use uh43wiufdsiushdfkj_1234567890123_current.iomsg when the file is the currently unfilled message storage file +* Provide a way for the Local API to request the entire set of messages from a given list of publishers within a specified timeframe for a particular recipient +* Filter out messages from any message retrieval request that do not comply with the current routing configuration +* When the Local API requests the currently unread messages for a recipient container, remove the delivered messages from memory +* Accept new routing information alerts from the Field Agent and retrieve the new routing configuration and store it in memory +* Count total messages being posted into the Message Bus and update the status information +* Count messages published per container and update the status information +* Store timestamp information with microsecond accuracy for when messages are posted +* Store timestamp information with microsecond accuracy for when messages are either confirmed as delivered via a real-time connection or when messages are retrieved from the unread queue +* Periodically calculate the average speed of message movement using the stored timestamps of message arrival and delivery +* Update the status information with the average message speed when it has been newly calculated +* Store, send, and receive messages according to the ioMessage Specification document + + +#### Performance Requirements + +* Apply unique message IDs and respond to message post operations as quickly as possible +* Calculate message recipients (from routing configuration) and deliver real-time messages as quickly as possible +* Reduce the accuracy or frequency of measurement operations (such as message counts) if it can help increase the speed of message delivery operations +* Calculate the average message speed every five minutes or more frequently + diff --git a/docs/ioFog-Command-Line-Specification.md b/docs/ioFog-Command-Line-Specification.md new file mode 100644 index 00000000..cbea4b88 --- /dev/null +++ b/docs/ioFog-Command-Line-Specification.md @@ -0,0 +1,299 @@ +# Command Line Specification + +As a service intended to run constantly in the background (also known as a daemon), the ioFog software needs to respond to shell commands from the user. This document defines all of the commands that the software needs to accept and the exact structure of the commands and responses. + +We will follow the guidelines set forth in the GNU Coding Standards document regarding command line interfaces. + +All command line outputs are sent to the "standard out" stream. + +The root command is the executable keyword. When using a text editor such as "nano" you simply type "nano xyz.xml" if you want to edit an XML file in the current directory. The executable keyword is "nano" and the parameter that follows is the file to open in the nano editor. + +The root command keyword for the ioFog product is "iofog" in all lowercase letters. If a user only types "iofog" they should be presented with the help options displayed as if they typed "iofog -h" or "iofog --help" or "iofog -?" to access the help menu. + +The ioFog command line should have auto-complete functionality. The user should be able to type the start of an ioFog command and hit [TAB] to use the auto-complete. + +#### Help Menu + +##### Accepted Inputs + +
+iofog
+iofog help
+iofog --help
+iofog -h
+iofog -?
+
+ +##### Output + +
+Usage: iofog [OPTIONS] COMMAND [arg...]
+
+Option                   GNU long option              Meaning
+======                   ===============              =======
+-h, -?                   --help                       Show this message
+-v                       --version                    Display the software version and license information
+
+
+Command                  Arguments                    Meaning
+=======                  =========                    =======
+help                                                  Show this message
+version                                               Display the software version and license information
+status                                                Display current status information about the software
+start                                                 Start the ioFog daemon which runs in the background
+stop                                                  Stop the ioFog daemon
+restart                                               Stop and then start the ioFog daemon
+provision                <provisioning key>           Attach this software to the configured ioFog controller
+deprovision                                           Detach this software from all ioFog controllers
+info                                                  Display the current configuration and other information about the software
+config                   [OPTION] [VALUE]             Change the software configuration according to the options provided
+                         -d <#GB Limit>                    Set the limit, in GiB, of disk space that the software is allowed to use
+                         -dl <dir>                         Set the directory to use for disk storage
+                         -m <#MB Limit>                    Set the limit, in MiB, of memory that the software is allowed to use
+                         -p <#cpu % Limit>                 Set the limit, in percentage, of CPU time that the software is allowed to use
+                         -a <uri>                          Set the uri of the fog controller to which this software connects
+                         -ac <filepath>                    Set the file path of the SSL/TLS certificate for validating the fog controller identity
+                         -c <uri>                          Set the UNIX socket or network address that the Docker daemon is using
+                         -n <network adapter>              Set the name of the network adapter that holds the correct IP address of this machine
+                         -l <#MB Limit>                    Set the limit, in MiB, of disk space that the log files can consume
+                         -ld <dir>                         Set the directory to use for log file storage
+                         -lc <#log files>                  Set the number of log files to evenly split the log storage limit
+                         -sf <#seconds>                    Set the status update frequency
+                         -cf <#seconds>                    Set the get changes frequency
+                         -df <#seconds>                    Set the post diagnostics frequency
+                         -idc <on/off>                     Set the mode on which any not registered docker container will be shutted down
+                         -gps <auto/off/#DD.DDD(lat),DD.DDD(lon)>    Set gps location of fog. Use auto to get coordinates by IP, use off to forbid gps,use GPS coordinates in DD format to set them manually
+                         -ft <auto/intel_amd/arm>          Set fog type. Use auto to detect fog type by system commands, use arm or intel_amd to set it manually
+
+
+Report bugs to: edgemaster@iofog.org
+ioFog home page: http://iofog.org
+
+ + + +#### Display ioFog Version + +##### Accepted Inputs + +
+iofog version
+iofog --version
+iofog -v
+
+ +##### Output + +
+ioFog 1.0
+Copyright (C) 2018-2022 Edgeworx, Inc.
+License ######### http://iofog.org/license
+This is open-source software with a commercial license: your usage is free until you use it in production commercially.
+There is NO WARRANTY, to the extent permitted by law.
+
+ + + +#### Display ioFog Status + +##### Accepted Inputs + +
+iofog status
+
+ +##### Output + +
+ioFog daemon             : [running][stopped]
+Memory Usage                : about 158.5 MiB
+Disk Usage                  : about 24.1 GiB
+CPU Usage                   : about 32.0%
+Running Elements            : 13
+Connection to Controller    : [ok][broken][not provisioned]
+Messages Processed          : about 1,583,323
+System Time                 : Feb 08 2016 20:14:32.873
+
+ + + +#### Start ioFog + +##### Accepted Inputs + +
+iofog start
+
+ +##### Output + +
+ioFog daemon already started
+
+- OR -
+
+ioFog daemon starting...
+...
+...
+started
+
+ + + +#### Stop ioFog + +##### Accepted Inputs + +
+iofog stop
+
+ +##### Output + +
+ioFog daemon already stopped
+
+- OR -
+
+ioFog daemon stopping...
+...
+stopped
+
+ + + +#### Restart ioFog + +##### Accepted Inputs + +
+iofog restart
+
+ +##### Output + +
+ioFog daemon restarting...
+...
+...
+...
+...
+restarted
+
+ + + +#### Provision this ioFog instance to a controller + +##### Accepted Inputs + +
+iofog provision D98we4sd
+
+* The provisioning key entered by the user takes the place of the D98we4sd above
+
+ +##### Output + +
+Provisioning with key s734sH9J...
+...
+[Success - Iofog UUID is fw49hrSuh43SEFuihsdfw4wefuh]
+[Failure - <error message from provisioning process>]
+
+ + + +#### De-provision this ioFog instance (removed from any controller) + +##### Accepted Inputs + +
+iofog deprovision
+
+ +##### Output + +
+Deprovisioning from controller...
+Success - tokens and identifiers and keys removed
+
+ + + +#### Show ioFog information + +##### Accepted Inputs + +
+iofog info
+
+ +##### Output + +
+Iofog UUID                              : sdfh43t9EFHSD98hwefiuwefkshd890she
+IP Address                               : 201.43.0.88
+Network Adapter                          : eth0
+ioFog controller                         : http://iofog.org/controllers/2398yef
+ioFog Certificate                        : ~/temp/certs/abc.crt
+Docker URI                               : unix:///var/run/docker.sock
+Disk Limit                               : 14.5 GiB
+Disk Directory                           : ~/temp/spool/
+Memory Limit                             : 720 MiB
+CPU Limit                                : 74.8%
+Log Limit                                : 2.0 GiB
+Log File Directory                       : ~/temp/logs/
+Log Rolling File Count                   : 10
+Status Update Frequency                  : 30
+Get Changes Frequency                    : 60
+Scan Devices Frequency                   : 60
+Post Diagnostics Frequency                : 10
+Isolated Docker Containers Mode          : on
+GPS mode                                 : auto
+GPS coordinates                          : 53.9,27.5667
+
+ + + +#### Change ioFog configuration + +##### Accepted Inputs + +
+iofog config -d 17.5
+iofog config -dl ~/temp/spool/
+iofog config -m 568
+iofog config -p 82.0
+iofog config -a https://250.17.0.200/controllers/7/
+iofog config -ac ~/temp/certs/controller_identity_proof.crt
+iofog config -c unix:///var/run/docker.sock
+iofog config -n eth0
+iofog config -l 2.0
+iofog config -ld ~/temp/logs/
+iofog config -lc 10
+
+iofog config -sf 20
+iofog config -cf 10
+iofog config -sd 30
+iofog config -df 20
+iofog config -idc off
+iofog config -gps 53.9,27.56
+iofog config -ft intel_amd
+
+* Any combination of parameters listed here can be entered on the command line simultaneously
+* for example, iofog config -m 2048 -p 80.0 -n wlan0
+
+ +##### Output + +
+Invalid parameter <-X> <VALUE>
+
+- OR -
+
+Change accepted for <parameter name>
+Old value was <prior value>
+New value is <input value>
+
+ diff --git a/docs/ioFog-Configuration-Specification.md b/docs/ioFog-Configuration-Specification.md new file mode 100644 index 00000000..a17543ff --- /dev/null +++ b/docs/ioFog-Configuration-Specification.md @@ -0,0 +1,27 @@ +# Configuration Specification + +The ioFog-Agent product is configured using an XML file called config.xml located in the "config" directory. The "config" directory location is /etc/iofog-agent. + +ioFog can also be configured using the command line or using the fog controller to which the ioFog-Agent instance has been provisioned. But in either of these cases, the config.xml file will be updated and will still be the only stable local source of configuration. + +#### Configuration Items + +* access_token - the access token granted by a fog controller to the ioFog-Agent instance during the provisioning process +* iofog_uuid - the unique identifier given to this ioFog-Agent instance by the fog controller +* controller_url - the root URL for the fog controller from which this ioFog-Agent instance should take its commands +* controller_cert - the file path for the SSL certficate corresponding to the fog controller (for proving its identity) +* network_interface - the name of the network interface that should be used for determining the IP address of this ioFog-Agent instance +* docker_url - the URL of the local Docker API +* disk_consumption_limit - the limit, in gibibytes (GiB), of disk space that this ioFog instance is allowed to use +* disk_directory - the directory that this ioFog instance is allowed to use for storage +* memory_consumption_limit - the limit, in mebibytes (MiB), of RAM that this ioFog instance is allowed to use +* processor_consumption_limit - the limit, in percentage, of CPU time that this ioFog instance is allowed to use +* log_disk_consumption_limit - the limit, in mebibytes (MiB), of disk space that this ioFog instance is allowed ot use +* log_disk_directory - the directory that this ioFog instance is allowed to use for log files +* log_file_count - the number of log files that should be kept, splitting the log consumption limit evenly between them +* status_update_freq - the frequency of sending ioFog status messages to Fog Controller +* get_changes_freq - the frequency of getting commands from Fog Controller +* scan_devices_freq - the frequency of scanning devices connected to ioFog +* post_diagnostics_freq - the frequency of getting commands from Fog Controller +* isolated_docker_container - mode on which any not registered docker container will be shutted down +* gps - gps coordinates of ioFog diff --git a/docs/ioFog-Container-SDK-Requirements.md b/docs/ioFog-Container-SDK-Requirements.md new file mode 100644 index 00000000..2ea87090 --- /dev/null +++ b/docs/ioFog-Container-SDK-Requirements.md @@ -0,0 +1,72 @@ +# Container SDK Requirements + +The ioFog Local API allows developers to use any language and framework to build ioElement containers. It uses standard REST API endpoints that speak JSON for stateless communication and it uses Websockets for real-time message and control communication. + +But while the API is easy to work with, it is always nice to skip some coding effort and jump right into the application development. To help developers get started faster and experience fewer issues, we need to provide SDK libraries for popular languages. + +The number of SDKs will grow over time as new languages are added. And developers across the world are welcome to create their own SDKs if we don't yet offer one for their favorite language. The requirements here are for the starting SDKs that we will provide for everyone. They reflect the most popular languages for building IoT components. + +#### Java + +* Provide a class for messages according to the ioMessage Specification document +* Provide a standard listener (Observer) pattern for developers to receive incoming real-time control and message communication +* Provide methods for performing all of the functionality listed in the Local API Specification document +* Publish the SDK so it can be included in any developer's build without manual installation +* Provide convenience functions for changing base64 encoded data to raw bytes +* Decode (from base64 to raw bytes) the ContextData and ContentData fields of messages arriving via the REST API +* Encode (from raw bytes to base64) the ContextData and ContentData fields of messages being send to the REST API + + +#### Node.js + +* Treat messages as standard JavaScript objects according to the ioMessage Specification document +* Provide standard callbacks for developers to receive incoming real-time control and message communication +* Provide methods for performing all of the functionality listed in the Local API Specification document +* Publish the SDK with NPM so it can be included in any developer's build without manual installation +* Provide convenience functions for changing base64 encoded data to raw bytes +* Decode (from base64 to raw bytes) the ContextData and ContentData fields of messages arriving via the REST API +* Encode (from raw bytes to base64) the ContextData and ContentData fields of messages being send to the REST API + + +#### Python + +* Provide a class for messages according to the ioMessage Specification document +* Provide standard callbacks for developers to receive incoming real-time control and message communication +* Provide methods for performing all of the functionality listed in the Local API Specification document +* Publish the SDK with the Python foundation so it can be included (using pip) in any developer's build without manual installation +* Provide convenience functions for changing base64 encoded data to raw bytes +* Decode (from base64 to raw bytes) the ContextData and ContentData fields of messages arriving via the REST API +* Encode (from raw bytes to base64) the ContextData and ContentData fields of messages being send to the REST API + + +#### C + +* Provide a struct for messages according to the ioMessage Specification document +* Use the 'curl' library to avoid re-writing low-level networking +* Provide function pointer openings or direct socket access for developers to receive incoming real-time control and message communication +* Provide functions for performing all of the functionality listed in the Local API Specification document +* Provide convenience functions for changing base64 encoded data to raw bytes +* Decode (from base64 to raw bytes) the ContextData and ContentData fields of messages arriving via the REST API +* Encode (from raw bytes to base64) the ContextData and ContentData fields of messages being send to the REST API + + +#### C++ + +* Provide a class for messages according to the ioMessage Specification document +* Use the 'curl' library to avoid re-writing low-level networking +* Provide a standard observer pattern implementation for developers to receive incoming real-time control and message communication +* Provide methods for performing all of the functionality listed in the Local API Specification document +* Provide convenience functions for changing base64 encoded data to raw bytes +* Decode (from base64 to raw bytes) the ContextData and ContentData fields of messages arriving via the REST API +* Encode (from raw bytes to base64) the ContextData and ContentData fields of messages being send to the REST API + + +#### C# + +* Provide a class for messages according to the ioMessage Specification document +* Provide a standard observer pattern implementation for developers to receive incoming real-time control and message communication +* Provide methods for performing all of the functionality listed in the Local API Specification document +* Provide convenience functions for changing base64 encoded data to raw bytes +* Decode (from base64 to raw bytes) the ContextData and ContentData fields of messages arriving via the REST API +* Encode (from raw bytes to base64) the ContextData and ContentData fields of messages being send to the REST API + diff --git a/docs/ioFog-Debug-Console-Specification.md b/docs/ioFog-Debug-Console-Specification.md new file mode 100644 index 00000000..b6cc6427 --- /dev/null +++ b/docs/ioFog-Debug-Console-Specification.md @@ -0,0 +1,104 @@ +# Debug Console Specification + +The Debug Console system container receives configuration through the ioFog Local API just like every other container. The configuration is specified here. It hosts a REST API that is also specified here. + +#### Container Configuration Example +
+	{"accesstoken":"fshkuewwre89ysdkSDFHKJwe9ywiuhfsdkhj","filesizelimit":200.0}
+
+	The "accesstoken" value is the current token that must be provided by anyone attempting to access the REST API
+
+	The "filesizelimit" value is the size limit of each message storage file that is created per publisher, in MiB
+
+ + +### REST API Endpoints + +All endpoints are hosted on port 80 as regular HTTP REST API that provide JSON outputs (MIME type of application/json). All endpoints require that the current access token be passed in the query otherwise the response should be a "404 not found" HTTP response code. + +#### Get Debug Messages For Publisher Within Timeframe + +This endpoint takes in the access token, publisher ID, timeframe start, and timeframe end parameters and gives out a JSON array of messages. The messages are all messages that have been received by the Debug Console container and stored in a file for this particular publisher. + +##### Endpoint + +
+	http://1.2.3.4:80/v2/debug/messages/publisher/dfshigu4wedsuiohdsf/starttime/1234567890123/endtime/1234567890123/accesstoken/fshkuewwre89ysdkSDFHKJwe9ywiuhfsdkhj
+
+	Note that the example IP address of 1.2.3.4 will be replaced by the real container IP address and the container itself does not need to know the address
+
+ +##### Response + +
+	{
+		"status":"okay",
+		"count":2,
+		"messages":
+			[
+				{
+					"id":"ObJ5STY02PMLM4XKXM8oSuPlc7mUh5Ej",
+					"tag":"",
+					"groupid":"",
+					"sequencenumber":1,
+					"sequencetotal":1,
+					"priority":0,
+					"timestamp":1452214777495,
+					"publisher":"dfshigu4wedsuiohdsf",
+					"authid":"",
+					"authgroup":"",
+					"version":4,
+					"chainposition":0,
+					"hash":"",
+					"previoushash":"",
+					"nonce":"",
+					"difficultytarget":0.0,
+					"infotype":"text",
+					"infoformat":"utf-8",
+					"contextdata":"",
+					"contentdata":"A New Message!"
+				},
+				{
+					"id":"sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf",
+					"tag":"Bosch Camera 16",
+					"groupid":"",
+					"sequencenumber":1,
+					"sequencetotal":1,
+					"priority":0,
+					"timestamp":1234567890123,
+					"publisher":"dfshigu4wedsuiohdsf",
+					"authid":"",
+					"authgroup":"",
+					"version":4,
+					"chainposition":0,
+					"hash":"",
+					"previoushash":"",
+					"nonce":"",
+					"difficultytarget":0.0,
+					"infotype":"image/jpeg",
+					"infoformat":"base64",
+					"contextdata":"",
+					"contentdata":"sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufhaskldjfslkjdhfalsjdf=serg4towhr"
+				}
+			]
+	}
+
+ +##### Querystring Parameters + +
+	publisher - the Publisher ID from which to return messages
+
+	starttime - the timestamp representing the earliest message desired (inclusive)
+
+	endtime - the timestamp representing the latest message desired (inclusive)
+
+	accesstoken - the current access token for getting access to the REST API endpoints
+
+ +##### POST Parameters + +
+	None
+
+ diff --git a/docs/ioFog-Device-Connections.md b/docs/ioFog-Device-Connections.md new file mode 100644 index 00000000..e294e30e --- /dev/null +++ b/docs/ioFog-Device-Connections.md @@ -0,0 +1,163 @@ +# Connecting Devices to ioFog + +One of the main challenges of the Internet of Things (IoT) is the large variety of connection methods for devices and systems. If a sensor cannot communicate with the greater system, then all is lost. ioFog provides both edge processing and edge connectivity. The connectivity is very flexible, which also means there are some decisions to be made for each implementation. + +This document describes the different ways that you can connect sensors, devices, legacy systems, and the greater world into ioFog. Once you do that, of course, the rest is pretty darn easy. + +Some connectivity methods are more efficient than others. Some should only be used if there is no better option available. The drawbacks and benefits of each connection method are listed here to help you determine what will fit best for your situations. + +#### Listen for Incoming Data + +When you add a container element to your ioFog instance, you can choose to open ports. The ports are mapped so that you can have a different port on the inside of the container than the one which is exposed to the outside world. This is so the container code can be written to listen to standard ports (such as 80 or 443) and yet there can be many such containers running at the same time in an ioFog instance. + +By opening a listening port on a container element, the sensors and devices outside of ioFog can direct their communications to the IP address of the ioFog instance and the port of the appropriate container element. The container element will simply start receiving the incoming network traffic and can perform all of the parsing, decoding, decrypting, and other tasks needed to work with the device. + +Note that opening a port for listening does not, in any way, reduce the container element's ability to send network traffic out or talk directly to the devices. + +##### Pros + +* Efficiency - the sensors and devices talk directly to the receiving container elements whenever they need +* Simplicity - the container element does not need to establish connections or request data +* Scale - a single listening container element can take in data from a large number of devices + +##### Cons + +* Setup - the sensors, devices, and external systems all need to be setup with the IP address and port information of the receiving container element +* Security - if the container element is not built with protection mechanisms in place, the external port opening can pose a security risk +* Network - the sensors, devices, and external systems will need to be able to send traffic over Internet Protocol (IP) in order to reach the container element + + + +#### Use Local DNS Settings to Capture Traffic + +If the devices you want to connect are not able to be configured with the network information of the ioFog installation, you may be able to capture the traffic coming from the devices by acting as the originally configured destination. By creating local DNS entries on the network, all of the traffic originally intended for a device cloud will end up in a container element. In some cases it will be easy to parse the traffic and make use of the data. In other cases you will be unable to decrypt secure traffic and may have no point of reference for parsing the information. + +This connection method requires you to open an external port on the container element, of course, in order to listen for incoming network traffic. + +This method of connection is only recommended if there is no other direct way of connecting. Although this method is very beneficial if the manufacturer of the devices has provided a "local cloud" container element. In that situation, you will be able to realize the benefits of local device connections without need to configure each individual device. + +##### Pros + +* Setup - no changes required on the devices themselves +* Simplicity - the container element does not need to establish connections or request data +* Scale - a single listening container element can take in data from a large number of devices + +##### Cons + +* Success - this method may not work if you are unable to manage or parse the incoming network traffic from the devices +* Unsupported - depending on the device manufacturer, you may not receive any support for this method of device connection +* Security - if the container element is not build with protection mechanisms in place, the external port opening can pose a security risk +* Network - the sensors, devices, and external systems will need to be able to send traffic over Internet Protocol (IP) in order to reach the container element + + + +#### Pass Cryptography Certificates to Containers + +Regardless of how the network connection between the device and the container is made, it may be necessary to decrypt traffic from the device. You may also need to encrypt traffic going to the device coming from the container. + +A properly written container element will not have a hard-coded certificate for talking to devices. It will leave the installation of the certificate to happen during the run-time configuration of the container. + +Because all container elements in ioFog have configurable properties, it is easy to add a property that holds the certificate. The certificate will automatically be delivered to the container element once it is running and retrieves its configuration information. + +##### Pros + +* Flexibility - container elements can be configured at run-time to work with encrypted traffic from devices +* Universality - the approach of passing certificates to container elements works with all types of connectivity methods +* Security - the traffic between devices and ioFog is encrypted using certificates that are delivered dynamically and can be revoked, changed, etc. + +##### Cons + +* Setup - depending on the device manufacturer, you may need to individually set up devices to contain the proper certificate matching the container element +* Administration - keeping track of certificates for many devices and many container elements brings administrative overhead + + + +#### Connect to Clouds from the Edge + +Sometimes there is no way to capture data locally from devices, such as microprocessor boards that require a direct connection to the manufacturers cloud. And sometimes the data does not exist locally but you want to bring it to the edge, such as information from a REST API providing stock prices. In any of these situations, container elements should simply use their Intenet connectivity to talk directly to the desired cloud as would be done in Web applications. + +Going to a cloud and back introduces latency into the IoT application on the edge, so container elements should be programmed as efficiently as possible in order to minimize the negative effects. + +If the cloud connection requires credentials or certificates, those should be provided to the container element through its configurable properties. API keys and login credentials for REST APIs should never be hard-coded into container elements for security and flexibility reasons. + +If your desire is to move information to a cloud instead of drawing information from a cloud, then this connection method is the right choice. The purpose of many IoT edge applications is to prepare information and move it to a destination. When the destination is a cloud, a container element can just send it directly from the edge and avoid any further ingestion steps or data repository hassles. + +##### Pros + +* Ease of Use - all popular languages and frameworks have the ability to connect to cloud resources and the programming model is straightforward +* Setup - most clouds will not require any configuration in this case, so there is virtually no setup required to get started +* Scale - the scalability of the edge processing essentially becomes tied to the scalability of the cloud resource +* Efficiency - when sending data out to a cloud, the efficiency can be practically as high as having the device send data to the cloud directly +* Dynamism - the connection details for a container element can be updated dynamically, making it possible to change data sources at run-time + +##### Cons + +* Speed - if the cloud connection is used to bring data into the application, then significant latency may be introduced +* Efficiency - because most cloud resources require polling in order to retrieve data, the efficiency of a real-time data stream is lost +* Security - moving information from a cloud to the edge and back breaks the privacy and security boundary of the edge information environment + + + +#### Reach Out to Local Resources + +All container elements running in ioFog have the ability to connect on the LAN. Some devices need to be queried for their information. The container element can simply connect to the device and retrieve the desired data. This approach also works very well for common network resources such as network storage, LDAP, and Active Directory resources. + +The container element will need to know how to reach the devices. This information should be delivered through container element properties, of course. If any credentials or certificates are required to make the connections, these can be included in the container element properties configuration, as well. + +Some hardware systems, such as ID badge readers and door locks in a corporate office, will have interfaces that operate over IP networks. This connection method works very well for bringing such legacy resources into an IoT application. The network traffic between the resource and the container element is bidirectional, which allows for full integration of things found on the LAN. + +##### Pros + +* Flexibility - the container element can be written to speak to the device or LAN resource natively, allowing for endless connection possibilities +* Repackaging - if code already exists for connecting to devices or network resources, it may be possible to simply package it as a container element +* Security - because the container element initiates the connection, there is less of an attack surface on the container element itself + +##### ons + +* Efficiency - if the data coming from devices or network resources is retrieved frequently, many extra CPU cycles can be spent polling +* Scale - if there are many device connections required, the container element itself will have to establish and manage them +* Speed - although polling frequencies can be quite high on the LAN, a small amount of latency will be introduced in any polling situation + + + +#### Connect to Proxy Devices on the Network + +Some devices communicate directly with gateway hardware. A good example is an ARM mbed gateway which speaks the same structured protocol (COAP) to all devices it sees. When gateways are available on the network, a single container element can be used to speak with the gateway in order to reach all of the devices. This approach has some scale advantages and may bring setup and configuration advantages, too. + +The network hardware does not have to be a device gateway. It may also be a radio interface, as well. One example is a device that connects on the TCP/IP network but also has a Bluetooth radio to connect to devices that don't connect on TCP/IP. By using a container element to connect with the Bluetooth interface hardware, full Bluetooth (or Bluetooth Low Energy) connectivity can be brought into the IoT application. + +Another example is long-range, low-power wireless networks such as LoRA or Ultra-Narrow Band. These wireless networks provide high battery life for devices and long network range. But they are not TCP/IP networks. The base station for such wireless networks can be connected on the LAN and the ioFog container element can connect to the base station. This setup allows the most advanced IoT wireless network technology to be integrated into the solution with the highest security and speed possible. + +##### Pros + +* Network - the choice of networks for your IoT application can go beyond TCP/IP to any possible network type +* Setup - the setup process for devices remains as the native process of the gateway or wireless network base station +* Efficiency - the base station or gateway hardware can move data directly into the container element as it arrives +* Scale - gateway and base station hardware can be chosen for a particular device scale, and many pieces of such hardware can be integrated into one or more container elements + +##### Cons + +* Complexity - additional network types may require additional coding skills and configuration skills +* Security - adding long-range wireless networks can increase the attack surface of the IoT application + + + +#### Connect to Proxy Devices via USB or Serial Port + +If network interfaces such as Bluetooth are not available over the TCP/IP network, it is possible to connect to common interfaces such as a USB Bluetooth dongle. Any resource available on the root Linux installation can be opened to the container element for direct access. With access to the USB interface, a container element can be used to interact with the resources attached to it and circumvent the problem of the hardware missing a TCP/IP interface. + +This approach is only recommended as a last resort for several reasons. It requires the gateway hardware or network interface to be physically located near the ioFog installation. It also requires the container element to access non-segregated system resources such as the USB interface or the RS-232 serial port interface. + +##### Pros + +* Readiness - even when TCP/IP hardware is not available, your IoT application can be extended with locally connected radios and gateways +* Network - the choice of networks for your IoT application can go beyond TCP/IP to any possible network type +* Setup - the setup process for devices remains as the native process of the gateway or wireless network base station +* Efficiency - the base station or gateway hardware can move data directly into the container element as it arrives + +##### Cons + +* Scale - there are only so many local ports avialable per ioFog install +* Complexity - the container element must be built to match the connected hardware and may need to contain a USB driver or similar software +* Security - system resource segregation is rather difficult in this situation and the container element has privileged access to low-level interfaces + diff --git a/docs/ioFog-Fabric-Controller-API-Specification.md b/docs/ioFog-Fabric-Controller-API-Specification.md new file mode 100644 index 00000000..019e56a1 --- /dev/null +++ b/docs/ioFog-Fabric-Controller-API-Specification.md @@ -0,0 +1,528 @@ +# Fog Controller API V2 Specification + +##### his is the 2nd version of the Fog Controller API. The first version remains active and unchanged. + +Each ioFog instance can do very little without connecting to a fog controller. In fact, connecting to a fog controller is what makes a particular ioFog instance become an actual part of the I/O Compute Fog. Every fog controller will offer this API exactly as it is shown here. This allows an ioFog instance to connect to fog controller and operate properly. + +The API endpoints are listed here with a short description and the actual inputs and outputs. The actual IP address or domain name of the fog controller will vary from deployment to deployment. It is mandatory that HTTPS be used, and both domain names and IP addresses are allowed for connecting to a fog controller. The placeholder address of 1.2.3.4 is used in this document for the location of the fog controller. + +#### Get Server Status + +This endpoint just gives you a response from the fog controller with its status. It can be used for simple "ping" purposes to see if the fog controller is online and operational. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/status
+
+ +##### Response + +
+	{
+		“status”:”ok”,
+		”timestamp”:1234567890123
+	}
+
+ +##### Querystring Parameters + +
+	None
+
+ +##### POST Parameters + +
+	None
+
+ + +#### Get ioFog UUID and Access Token + +This endpoint registers the ioFog instance that is submitting the provisioning key and delivers the ioFog UUID along with an access token that must be submitted for any further API interaction. The access token remains valid until it is revoked. If it becomes invalid, the ioFog instance must be re-provisioned to re-establish access to the fog controller API. + +The ioFog UUID provided by this endpoint is a 128-bit random ID formatted in base58. We use base58 for compactness, readability, portability, and transmission safety between systems. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/provision/key/A8842h/fogtype/1
+
+ +##### Response + +
+	{
+		“status”:”ok”,
+		”timestamp”:1234567890123,
+		“id”:”4sd9whcfh”,
+		“token”:”3498wfesdhusdvkjh3refkjhsdpaohrg”
+	}
+
+ +##### Querystring Parameters + +
+	key - the provisioning key provided via the command line (example shown here as a8842h)
+	fogtype - an integer representing the system architecture of this ioFog instance
+
+ +##### POST Parameters + +
+	None
+
+ + +#### Post ioFog Instance Status Information + +This endpoint allows the ioFog instance to send its status information to the fog controller. This should be done regularly, but not so often as to waste bandwidth and CPU resources. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/status/id/4sd9whcfh/token/3498wfesdhusdvkjh3refkjhsdpaohrg
+
+ +##### Response + +
+	{
+		“status”:”ok”,
+		”timestamp”:1234567890123
+	}
+
+ +##### Querystring Parameters + +
+	UUID - the UUID held by the ioFog instance (example shown here as 4sd9whcfh)
+    
+    token - the access token given to the ioFog instance for accessing the API (example shown here as 3498wfesdhusdvkjh3refkjhsdpaohrg)
+
+ +##### POST Parameters + +
+    daemonstatus - ioFog daemon status string (example: running)
+    
+    daemonoperatingduration - ioFog daemon operating duration in milliseconds (example: 43473272)
+    
+    daemonlaststart - Timestamp of the ioFog daemon last start in milliseconds (example: 1234567890123)
+    
+    memoryusage - ioFog current memory usage in mebibytes MiB (example: 562.8)
+    
+    diskusage - ioFog current disk usage in gibibytes GiB (example: 2.79)
+    
+    cpuusage - ioFog current CPU usage in percent (example: 24.71)
+    
+    memoryviolation - Status indicating if the ioFog's current memory usage is in violation of the configured limit (example: yes)
+    
+    diskviolation - Status indicating if the ioFog's current disk usage is in violation of the configured limit (example: no)
+    
+    cpuviolation - Status indicating if the ioFog's current CPU usage is in violation of the configured limit (example: no)
+    
+    elementstatus - JSON string providing the status of all elements (example below)
+    	
+    	[{"id":"sdfkjhweSDDkjhwer8","status":"starting","starttime":1234567890123,"operatingduration":278421},{"id":"239y7dsDSFuihweiuhr32we","status":"stopped","starttime":1234567890123,"operatingduration":421900}]
+        
+    repositorycount - Number of Docker container registries being used by the ioFog instance (example: 5)
+
+    repositorystatus - JSON string providing the status of all the repositories (example below)
+
+    	[{"url":"hub.docker.com","linkstatus":"connected"},{"url":"188.65.2.81/containers","failed login"}]
+
+    systemtime - Timestamp of the current ioFog system time in milliseconds (example: 1234567890123)
+    
+    laststatustime - Timestamp in milliseconds of the last time any status information on the ioFog instance was updated (example: 1234567890123)
+
+    ipaddress - Current IP address of the network adapter configured for the ioFog instance (example: 10.0.15.13)
+
+    processedmessages - Total number of messages processed by the ioFog instance (example: 4481)
+
+    elementmessagecounts - JSON string providing the number of messages published per element (example below)
+
+    	[{"id":"d9823y23rewfouhSDFkh","messagecount":428},{"id":"978yerwfiouhASDFkjh","messagecount":8321}]
+    
+    messagespeed - The average speed, in milliseconds, of messages moving through the ioFog instance (example: 84)
+
+    lastcommandtime - Timestamp, in milliseconds, of the last update received by the ioFog instance (example: 1234567890123)
+    
+    version - String representing the current version of the ioFog software that is posting status (example: 1.24)
+
+ + +#### Get ioFog Configuration + +This endpoint provides the configuration for the ioFog instance. Note that some configuration items, such as the fog controller URL and certificate path, are not featured here. That's for security reasons. If someone gains control of a fog controller, we don't want them to be able to tell the ioFog instances to listen to a different fog controller. This also prevents accidental disconnection of ioFog instances from the fog controller. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/config/id/4sd9whcfh/token/3498wfesdhusdvkjh3refkjhsdpaohrg
+
+ +##### Response + +
+	{
+        “status”:”ok”,
+        “timestamp”:1234567890123,
+        “config”:
+            {
+                "networkinterface":"p2p1",
+                "dockerurl":"unix:///var/run/docker.sock",
+                "disklimit":12.0,
+                "diskdirectory":"/var/lib/iofog/",
+                "memorylimit":1024.0,
+                "cpulimit":35.58,
+                "loglimit":2.45,
+                "logdirectory":"/var/log/iofog/",
+                "logfilecount":10
+            }
+    }
+
+ +##### Querystring Parameters + +
+	UUID - the UUID held by the ioFog instance (example shown here as 4sd9whcfh)
+    
+    token - the access token given to the ioFog instance for accessing the API (example shown here as 3498wfesdhusdvkjh3refkjhsdpaohrg)
+
+ +##### POST Parameters + +
+	None
+
+ + +#### Post ioFog Configuration + +This endpoint allows the ioFog instance to send its configuration to the fog controller. It should send the updated configuration to this endpoint whenever a change is made locally. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/config/changes/id/4sd9whcfh/token/3498wfesdhusdvkjh3refkjhsdpaohrg
+
+ +##### Response + +
+	{
+		“status”:”ok”,
+		”timestamp”:1234567890123
+	}
+
+ +##### Querystring Parameters + +
+	UUID - the UUID held by the ioFog instance (example shown here as 4sd9whcfh)
+    
+    token - the access token given to the ioFog instance for accessing the API (example shown here as 3498wfesdhusdvkjh3refkjhsdpaohrg)
+
+ +##### POST Parameters + +
+    networkinterface - example: p2p1
+
+    dockerurl - example: unix:///var/run/docker.sock
+
+    disklimit - example: 12.0
+
+    diskdirectory - example: /var/lib/iofog/
+
+    memorylimit - example: 1024.0
+
+    cpulimit - example: 35.58
+
+    loglimit - example: 2.45
+
+    logdirectory - example: /var/log/iofog/
+
+    logfilecount - example: 10
+
+ + +#### Get ioFog Changes List + +This endpoint lists the current changes for the ioFog instance. Much of the time there will not be any changes. The ioFog instance should use this endpoint to check frequently, such as every 20 seconds. The changes are calculated based upon the timestamp that is sent in the querystring parameters. The timestamp must be stored locally in the ioFog instance and passed to this endpoint on every request. It should be updated whenever a successful call to this endpoint is completed, and should use the timestamp provided in the response. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/changes/id/4sd9whcfh/token/3498wfesdhusdvkjh3refkjhsdpaohrg/timestamp/1234567890123
+
+ +##### Response + +
+	{
+        “status”:”ok”,
+        “timestamp”:1234567890123,
+        “changes”:
+            {
+                “config”:false,
+                “containerlist”:false,
+                “containerconfig”:true,
+                “routing”:false,
+                "registries":true
+            }
+    }
+
+ +##### Querystring Parameters + +
+	UUID - the UUID held by the ioFog instance (example shown here as 4sd9whcfh)
+    
+    token - the access token given to the ioFog instance for accessing the API (example shown here as 3498wfesdhusdvkjh3refkjhsdpaohrg)
+
+    timestamp - the timestamp from the last received results of this specific API call (example shown here as 1234567890123)
+
+ +##### POST Parameters + +
+	None
+
+ + +#### Get ioFog Container List + +This endpoint provides the current list of containers that should be running on the ioFog instance. Containers should be added, removed, and restarted based upon this list. A change in port mappings should result in a restart because containers can only have their port mappings updated when they are being started. When the "rebuild" flag is set to true, the Docker daemon should be asked to build the container again. If there is an updated image in the registry, Docker will see the change and flush its cache and build the container from the updated image. Triggering container updates is the purpose of this "rebuild" flag. + +When the "roothostaccess" flag is set to true, the container should have its network mapped directly to the host network, which is done in Docker by the command "--net=host". Custom port mappings are not possible in this configuration, so any port mappings that are specified for that container should be ignored. + +The container IDs provided by this endpoint are 128-bit random IDs formatted in base58. We use base58 for compactness, readability, portability, and transmission safety between systems. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/containerlist/id/4sd9whcfh/token/3498wfesdhusdvkjh3refkjhsdpaohrg
+
+ +##### Response + +
+	{
+        “status”:”ok”,
+        “timestamp”:1234567890123,
+        “containerlist”:
+            [
+                {
+                    “id”:”sh23489gyrsdifuhw3iruedsifyasf”,
+                    “imageid”:”iofog/open-weather-map”,
+                    "registryurl":"hub.docker.com",
+                    “lastmodified”:1234567890123,
+                    "roothostaccess":false,
+                    "rebuild":false,
+                    “portmappings”:
+                        [
+                            {
+                                “outsidecontainer”:”5500”,
+                                “insidecontainer”:”80”
+                            },
+                            {
+                                “outsidecontainer”:”5650”,
+                                “insidecontainer”:”2040”
+                            }
+                        ]
+                },
+				{
+                    “id”:”debug”,
+                    “imageid”:”iofog/seismic-sensor-simulator”,
+                    "registryurl":"24.158.9.17",
+					“lastmodified”:1234567890123,
+                    "roothostaccess":true,
+					"rebuild":true,
+                    “portmappings”:
+                        [
+                        ]
+				}
+            ]
+    }
+
+ +##### Querystring Parameters + +
+	UUID - the UUID held by the ioFog instance (example shown here as 4sd9whcfh)
+    
+    token - the access token given to the ioFog instance for accessing the API (example shown here as 3498wfesdhusdvkjh3refkjhsdpaohrg)
+
+ +##### POST Parameters + +
+	None
+
+ + +#### Get ioFog Container Configuration + +This endpoint provides the JSON configuration strings for all of the containers that should be running on the ioFog instance. Note that the container configuration JSON strings are escaped. This is because they are being delivered inside a JSON response and we don't want these configuration strings to become part fo the actual response object. We want the strings to be unescaped and passed to the containers without being parsed. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/containerconfig/id/4sd9whcfh/token/3498wfesdhusdvkjh3refkjhsdpaohrg
+
+ +##### Response + +
+	{
+        “status”:”ok”,
+        “timestamp”:1234567890123,
+        “containerconfig”:
+            [
+                {
+                    “id”:”sdguh34tkwjdhfsdkhfs”,
+                    “lastupdatedtimestamp”:1234567890123,
+                    “config”:”\{\”username\”:\”iokilton\”,\”password\”:\”abc123\”\}”
+				},
+				{
+                    “id”:”345t9yergdskfhtwerwhuk”,
+                    “lastupdatedtimestamp”:1234567890123,
+                    “config”:”\{\”speed\”:40,\”sendphotos\”:true\}”
+				},
+				{
+                    “id”:”viewer”,
+                    “lastupdatedtimestamp”:1234567890123,
+                    “config”:”\{\}”
+				}
+            ]
+    }
+
+ +##### Querystring Parameters + +
+	UUID - the UUID held by the ioFog instance (example shown here as 4sd9whcfh)
+    
+    token - the access token given to the ioFog instance for accessing the API (example shown here as 3498wfesdhusdvkjh3refkjhsdpaohrg)
+
+ +##### POST Parameters + +
+	None
+
+ + +#### Get ioFog Routing + +This endpoint provides the routing plan for all containers. Note that no container ever specifies its inputs. It only specifies its outputs. This is because the vast majority of IoT data streams begins with a container that does not take in ioMessages. It just connects to some external device or external system. Then it publishes ioMessages and the routing chain begins as a sequence of outputs from container to container. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/routing/id/4sd9whcfh/token/3498wfesdhusdvkjh3refkjhsdpaohrg
+
+ +##### Response + +
+	{
+        “status”:”ok”,
+        “timestamp”:1234567890123,
+        “routing”:
+            [
+                {
+                    “container”:”sdh4wte98yefsdouhdv”,
+                    “receivers”:
+						[
+                            “349y8sdofshsdefh”,
+                            “2398yrodsfkdshdsf”,
+                            “viewer”,
+                            “debug”
+                        ]
+				},
+				{
+                    “container”:”ou23uewds98tesdfjkhsed”,
+                    “receivers”:
+						[
+                            “iwe32rlejsdfxkjhsdf”,
+                            “2398yrodsfkdshdsf”
+                        ]
+				}
+            ]
+    }
+
+ +##### Querystring Parameters + +
+	UUID - the UUID held by the ioFog instance (example shown here as 4sd9whcfh)
+    
+    token - the access token given to the ioFog instance for accessing the API (example shown here as 3498wfesdhusdvkjh3refkjhsdpaohrg)
+
+ +##### POST Parameters + +
+	None
+
+ + +#### Get ioFog Registries + +This endpoint provides the list of Docker container registries that the ioFog instance needs to load container images from. Login credentials are provided for each registry. Information about whether or not a registry is secure is also provided. If a registry is not secure, it should be added to the Docker daemon "insecure" list. If a registry is secure, it may or may not require a certificate in order to access it. If it does, the certificate will be provided directly in the API response. Note that this field may contain intermediate certificates bundled into the certificate chain, making this a rather large amount of text. The certificate example shown in this documentation is merely a placeholder. + +##### Endpoint + +
+	https://1.2.3.4/api/v2/instance/registries/id/4sd9whcfh/token/3498wfesdhusdvkjh3refkjhsdpaohrg
+
+ +##### Response + +
+	{
+        “status”:”ok”,
+        “timestamp”:1234567890123,
+        "registries":
+            [
+                {
+                	"url":"15.68.152.8",
+                	"secure":true,
+                	"certificate":"4wht9wdfsSkusdfhi234kwrwoeruawofjas=wetiuh4wefssdf...",
+                	"requirescert":true,
+                	"username":"foguser1",
+                	"password":"abc123",
+                	"useremail":"jim@themail.com"
+            	},
+            	{
+                	"url":"hub.docker.com",
+                	"secure":true,
+                	"certificate":"",
+                	"requirescert":false,
+                	"username":"iofog",
+                	"password":"abc123",
+                	"useremail":"iofog_user@iofog.org"
+            	}
+            ]
+    }
+
+ +##### Querystring Parameters + +
+	UUID - the UUID held by the ioFog instance (example shown here as 4sd9whcfh)
+    
+    token - the access token given to the ioFog instance for accessing the API (example shown here as 3498wfesdhusdvkjh3refkjhsdpaohrg)
+
+ +##### POST Parameters + +
+	None
+
+ + diff --git a/docs/ioFog-Installation-Specification.md b/docs/ioFog-Installation-Specification.md new file mode 100644 index 00000000..961c3828 --- /dev/null +++ b/docs/ioFog-Installation-Specification.md @@ -0,0 +1,82 @@ +# Installation Specification + +One of the most important aspects of the ioFog product is its ease of installation. Unfortunately, it is difficult to produce an easy installation experience across a variety of Linux machines. It is even harder to make such an installation completely reliable. + +That's why we are putting the challenge of creating a great installation experience right at the front. Adding an installation package after a product is fully built seems like the logical treatment, but I believe it is backwards. If installation is important, put it up front. + +The packaging mechanisms and install scripts can grow and change, but the requirements of a great product installation experience don't waver. If we follow these requirements from the start, we will be able to overcome the challenges. + +#### Officially Supported Linux Versions + +* CentOS 7 +* RHEL (Red Hat Enterprise Linux) 7 +* Debian 7.7 +* Debian 8 +* Ubuntu 12.04 +* Ubuntu 14.04 +* Ubuntu 15.10 +* Fedora 22 +* Fedora 23 + +#### Package Installation Requirements + +* Set up the ioFog daemon to run as a service on system boot + * For Ubuntu - define the service as /etc/init.d/iofog and register it using update-rc.d + * For Debian - define the service as /etc/init.d/iofog and register it using update-rc.d + * For CentOS - define the service as /etc/init.d/iofog and register it using chkconfig + * For RHEL - define the service as /etc/init.d/iofog and register it using chkconfig + * For Fedora - define the service as /etc/init.d/iofog and register it using chkconfig + +* Place all program files in the standard locations for each Linux version + * For all Linux versions the directory for the executable file is /usr/bin/ + * For all Linux versions the directory for static configuration files is /etc/iofog/ + * For all Linux versions the default directory for log files is /var/log/iofog/ + * For all Linux versions the directory for files created by and used by the program (such as message bus archives) is /var/lib/iofog/ + * For all Linux versions the directory for files associated with the running daemon is /var/run/iofog/ + * For all Linux versions the directory for auto-complete scripts is /etc/bash_completion.d/ + +* Create the proper groups, users, and permissions + * Create a group called "iofog" + * Create a user called "iofog" + * Make the iofog user a member of the iofog group + * Give ownership of the installed files and directories to both the iofog user and group + * Give the proper permissions to the installed files and directories + +* Register the executable path so the command line functionality works from anywhere + * For all Linux versions, just create a symbolic link to the executable path + * Make the link into the /usr/local/bin/ directory because it is always in the pre-registered paths + * Use the command "ln -sf /usr/bin/iofog /usr/local/bin/iofog" to create the link (symbolic link with forced overwrite) + +* Minimize the amount of installation text the user sees + +* Clearly report the cause of installation errors on the screen if they are encountered + +* Provide the software as a native package for each Linux version + * For Ubuntu - provide iofog as a Debian package (deb) so it can be installed using "apt-get" + * For Debian - provide iofog as a Debian package (deb) so it can be installed using "apt-get" + * For CentOS - provide iofog as an RPM package (rpm) so it can be installed using "yum" + * For RHEL - provide iofog as an RPM package (rpm) so it can be installed using "yum" + * For Fedora - provide iofog as an RPM package (rpm) so it can be installed using "dnf" + * For all Linux versions - host a repository on the iofog.org Web server + * For all Linux versions - host instructions for installation on the iofog.org Web server + * In the instructions, show how to add our repository to the list, add our verification keys, etc. + * In the instructions, link to Docker's installation guides for the different Linux versions: + * [Docker CentOS installation](https://docs.docker.com/engine/installation/linux/centos/) + * [Docker RHEL installation](https://docs.docker.com/engine/installation/linux/rhel/) + * [Docker Ubuntu installation](https://docs.docker.com/engine/installation/linux/ubuntulinux/) + * [Docker Debian installation](https://docs.docker.com/engine/installation/linux/debian/) + * [Docker Fedora installation](https://docs.docker.com/engine/installation/linux/fedora/) + +* Register the ioFog command line utility for auto-complete functionality + * For all Linux versions - make sure there is an auto-complete script copied into the /etc/bash_completion.d/ directory + +#### Convenience Installation Script Requirements + +* Focus on Ubuntu 14.04 first, then produce convenience scripts for the other Linux versions +* Create a shell script that can be downloaded and run by a root user or by anyone who can use "sudo" +* Install Docker 1.5+ as a dependency +* Install Java 8+ as a dependency +* Register the iofog package with the installer service +* Update the installer service +* Run the package installation of iofog + diff --git a/docs/ioFog-Local-API-Specification.md b/docs/ioFog-Local-API-Specification.md new file mode 100644 index 00000000..81a17e22 --- /dev/null +++ b/docs/ioFog-Local-API-Specification.md @@ -0,0 +1,366 @@ +# Local API Specification + +This is the second version of the Local API, which is also sometimes called the "Edge API" or the "Edge Services" of the ioFog. Only this second version will be offered in the new production release of ioFog for Linux. + +This Local API offers both standard REST API endpoints and Websocket API endpoints. While REST API standards are well-known and well-documented, the use of Websockets is still on the rise and not yet common. For Websocket specifications, we will follow the IANA standards here: + +IANA Official Websocket Registry + +This means that we will offer the standard closure codes, op codes, etc. + +All messages passing through the Local API must be in the standard ioMessage format, which can be found in the ioMessage Specification document. + +Note that the ContextData and ContentData fields of all messages must be base64 encoded when sending messages in a JSON response. All incoming messages will also have these fields base64 encoded and will need to be decoded upon arrival. No other fields should be encoded, because all other fields are capable of being transmitted directly as JSON. This allows containers to quickly examine an incoming message InfoType and InfoFormat to see if performing the base64 decoding is worthwhile. + +If a message InfoFormat is actually base64, then it will be encoded again during transmission as JSON. While this is not very efficient, it is sustainable as a practice. Neither the ioFog Local API nor the containers need to examine messages to determine the type of encoding. They both simply decode arriving messages and encode messages before sending (just the ContextData and ContentData fields). + +#### Get Container Configuration + +This endpoint provides the current JSON configuration string for the requesting container. Containers identify themselves by their element ID, which is mapped into the container as an environment variable. + +##### Endpoint + +
+	http://iofog:54321/v2/config/get
+
+ +##### Response + +
+	{
+		"status":"okay",
+		"config":"{\"property1\":\"value1\",\"property2\":\"value2\"}"
+	}
+
+ +##### Querystring Parameters + +
+	None
+
+ +##### POST Parameters + +
+	{“id”:”R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ”}
+
+	Note: The POST value is JSON and must be sent with HTTP header set as “Content-Type:application/json”
+
+ + +#### Get Container Next Unread Messages + +This endpoint returns a JSON array containing all of the unread messages for this container up to this point. Receiving the messages clears them from the queue so a following call to this API endpoint will not contain the same messages. + +##### Endpoint + +
+	http://iofog:54321/v2/messages/next
+
+ +##### Response + +
+	{
+		"status":"okay",
+		"count":2,
+		"messages":
+			[
+				{
+					"id":"ObJ5STY02PMLM4XKXM8oSuPlc7mUh5Ej",
+					"tag":"",
+					"groupid":"",
+					"sequencenumber":1,
+					"sequencetotal":1,
+					"priority":0,
+					"timestamp":1452214777495,
+					"publisher":"R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ",
+					"authid":"",
+					"authgroup":"",
+					"version":4,
+					"chainposition":0,
+					"hash":"",
+					"previoushash":"",
+					"nonce":"",
+					"difficultytarget":0.0,
+					"infotype":"text",
+					"infoformat":"utf-8",
+					"contextdata":"",
+					"contentdata":"8943asefSDhdkljsafhasldkjhfdlk==wehj23"
+				},
+				{
+					"id":"sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf",
+					"tag":"Bosch Camera 16",
+					"groupid":"",
+					"sequencenumber":1,
+					"sequencetotal":1,
+					"priority":0,
+					"timestamp":1234567890123,
+					"publisher":"Ayew98wtosdhFSKdjhsdfkjhkjesdhg",
+					"authid":"",
+					"authgroup":"",
+					"version":4,
+					"chainposition":0,
+					"hash":"",
+					"previoushash":"",
+					"nonce":"",
+					"difficultytarget":0.0,
+					"infotype":"image/jpeg",
+					"infoformat":"file/.jpg",
+					"contextdata":"",
+					"contentdata":"sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufhaskldjfslkjdhfalsjdf=serg4towhr"
+				}
+			]
+	}
+
+ +##### Querystring Parameters + +
+	None
+
+ +##### POST Parameters + +
+	{“id”:”R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ”}
+
+	Note: The POST value is JSON and must be sent with HTTP header set as “Content-Type:application/json”
+
+ + +#### Post Message + +This endpoing allows a container to post a message to the system. The message ID is generated inside the ioFog system, so it is not passed by the container when posting the message. The timestamp is not generated by the container, either. Both of these are returned in the repsonse from this endpoint. This prevents time synchronization problems and unique identifier problems from taking place in the container, where the language, frameworks, and code quality are unknown. + +##### Endpoint + +
+	http://iofog:54321/v2/messages/new
+
+ +##### Response + +
+	{
+		"status":"okay",
+		"timestamp":1234567890123,
+		"id":"f9y43trfdsSDFkjhdso8y4twouhsdfksjhdf2o834wyr4we"
+	}
+
+ +##### Querystring Parameters + +
+	None
+
+ +##### POST Parameters + +
+	{
+		"tag":"",
+		"groupid":"",
+		"sequencenumber":1,
+		"sequencetotal":1,
+		"priority":0,
+		"publisher":"R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ",
+		"authid":"",
+		"authgroup":"",
+		"version":4,
+		"chainposition":0,
+		"hash":"",
+		"previoushash":"",
+		"nonce":"",
+		"difficultytarget":0.0,
+		"infotype":"text",
+		"infoformat":"utf-8",
+		"contextdata":"",
+		"contentdata":"42h3isuhsdlukhfsd==w3efakhsfdkljhafs"
+	}
+
+	Note: The POST value is JSON and must be sent with HTTP header set as “Content-Type:application/json”
+
+ + +#### Get Messages From Publishers Within Timeframe + +This endpoint allows a container to query for messages from any number of publishers within any timeframe. The messages will only be provided for publishers that the container is allowed to access. In other words, if a container doesn't normally receive messages from a particular publisher, then the container can try to query for messages from that publisher but it won't receive any. The message retrieval and security controls are all performed by the Message Bus module and the allowed messages are passed to the Local API to send out. + +Beause of memory limitations, the Local API may only send a portion of the requested messages. The Local API will decide how many messages are appropriate to send and will return the adjusted starting and ending timeframe as illustrated in the sample response output below. The Local API will always use the starting timeframe and will adjust the ending timeframe to reflect the timestamp of the actual last message in the list. + +##### Endpoint + +
+	http://iofog:54321/v2/messages/query
+
+ +##### Response + +
+	{
+		"status":"okay",
+		"count":2,
+		"timeframestart":1234567890123,
+		"timeframeend":9876543210123,
+		"messages":
+			[
+				{
+					"id":"ObJ5STY02PMLM4XKXM8oSuPlc7mUh5Ej",
+					"tag":"",
+					"groupid":"",
+					"sequencenumber":1,
+					"sequencetotal":1,
+					"priority":0,
+					"timestamp":1452214777495,
+					"publisher":"R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ",
+					"authid":"",
+					"authgroup":"",
+					"version":4,
+					"chainposition":0,
+					"hash":"",
+					"previoushash":"",
+					"nonce":"",
+					"difficultytarget":0.0,
+					"infotype":"text",
+					"infoformat":"utf-8",
+					"contextdata":"",
+					"contentdata":"wei8y43ipouwhefdskhufdslkjahsdf"
+				},
+				{
+					"id":"sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf",
+					"tag":"Bosch Camera 16",
+					"groupid":"",
+					"sequencenumber":1,
+					"sequencetotal":1,
+					"priority":0,
+					"timestamp":1234567890123,
+					"publisher":"Ayew98wtosdhFSKdjhsdfkjhkjesdhg",
+					"authid":"",
+					"authgroup":"",
+					"version":4,
+					"chainposition":0,
+					"hash":"",
+					"previoushash":"",
+					"nonce":"",
+					"difficultytarget":0.0,
+					"infotype":"image/jpeg",
+					"infoformat":"file/.jpg",
+					"contextdata":"",
+					"contentdata":"sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufhaskldjfslkjdhfalsjdf=serg4towhr"
+				}
+			]
+	}
+
+ +##### Querystring Parameters + +
+	None
+
+ +##### POST Parameters + +
+	{"id":”R4b2WPZRbycCzyZBz9tD7BdMWg94YDhQ”, "timeframestart":1234567890123, "timeframeend":1234567890123, "publishers":["sefhuiw4984twefsdoiuhsdf","d895y459rwdsifuhSDFKukuewf","SESD984wtsdidsiusidsufgsdfkh"]}
+
+	Note: The POST value is JSON and must be sent with HTTP header set as “Content-Type:application/json”
+
+ + +#### Get Control Websocket Connection + +This endpoint opens a control Websocket connection for the container. The control commands sent over this Websocket are specified here. It is the responsibility of the container to establish this connection and ensure it is always running. If the container loses the Websocket connection, it should establish a new connection. The Local API is responsible for knowing which Websocket connection belongs to which container so that it can pass information to the appropriate recipients. + +The container ID must be passed as part of the URL because otherwise it would have to be passed in the Websocket connection itself and that would make associated connections with container IDs rather difficult. + +##### Endpoint + +
+	ws://iofog:54321/v2/control/socket/id/34t9whefsdfDFKjhw4tiouhwef
+
+ +##### Response + +
+	None - the Websocket will simply be opened successfully
+
+ +##### Querystring Parameters + +
+	id - the container ID of the container requesting the Websocket connection (example shown here as 34t9whefsdfDFKjhw4tiouhwef)
+
+ +##### POST Parameters + +
+	None
+
+ +##### Transmissions From ioFog To Container + +
+	Standard "Ping" message (op code 9)
+	Standard "Pong" message (op code 10)
+	Acknowledgement message (op code 11)
+	New container configuration available (op code 12)
+
+ +##### Transmissions From Container To ioFog + +
+	Standard "Ping" message (op code 9)
+	Standard "Pong" message (op code 10)
+	Acknowledgement message (op code 11)
+
+ + +#### Get Message Websocket Connection + +This endpoint opens a message Websocket connection for the container. The messages and other commands sent over this Websocket are specified here. It is the responsibility of the container to establish this connection and ensure it is always running. If the container loses the Websocket connection, it should establish a new connection. The Local API is responsible for knowing which Websocket connection belongs to which container so that it can pass information to the appropriate recipients. + +The container ID must be passed as part of the URL because otherwise it would have to be passed in the Websocket connection itself and that would make associated connections with container IDs rather difficult. + +##### Endpoint + +
+	ws://iofog:54321/v2/message/socket/id/34t9whefsdfDFKjhw4tiouhwef
+
+ +##### Response + +
+	None - the Websocket will simply be opened successfully
+
+ +##### Querystring Parameters + +
+	id - the container ID of the container requesting the Websocket connection (example shown here as 34t9whefsdfDFKjhw4tiouhwef)
+
+ +##### POST Parameters + +
+	None
+
+ +##### Transmissions from ioFog to Container + +
+	Standard "Ping" message (op code 9)
+	Standard "Pong" message (op code 10)
+	ioMessage transmission (op code 13 followed by 4 bytes indicating the total length of the message followed by the bytes of the actual ioMessage)
+	ioMessage receipt transmission (op code 14 followed by 4 bytes indicating the length of the response followed by the actual bytes of the response message containing the ioMessage ID and Timestamp fields with all other fields empty)
+
+ +##### Transmissions from Container to ioFog + +
+	Standard "Ping" message (op code 9)
+	Standard "Pong" message (op code 10)
+	Acknowledgement message (op code 11)
+	ioMessage transmission (op code 13 followed by 4 bytes indicating the total length of the message followed by the bytes of the actual ioMessage)
+
+ diff --git a/docs/ioFog-Packaging-Instructions.md b/docs/ioFog-Packaging-Instructions.md new file mode 100644 index 00000000..7e585623 --- /dev/null +++ b/docs/ioFog-Packaging-Instructions.md @@ -0,0 +1,96 @@ +# Creating and Publishing the ioFog Linux Installation Packages + +Every time the ioFog software gets updated, the installation packages must also be updated. This document lists all of the steps required in order to build and publish the packages. It requires login credentials for the build server and for the code GitHub repository. The credentials are not included in this document for security reasons. + +#### Build and check in the latest .jar file + +A new .jar file must be compiled and then checked into the GitHub repository in the root directory of the project. This is because the package build server needs to use Git for fetching the latest .jar to put in the packages. + +#### SSH into the package build server + +Using the credentials that were provided to you, SSH into the server at 166.78.135.165. Login as the root user, so the command is: + +
+	ssh root@166.78.135.165
+
+ +#### Build the new Debian package and publish it + +Change directories into the Debian packaging folder. Make a new package build, and BE SURE to increment the version number by .01 every time you do this. Otherwise the publishing will not work properly. + +
+	Change directory:
+
+	cd /iofog-agent-packaging
+	
+	See that the other Debian package(s) are present (they have a .deb file extension) and note the current highest version number:
+
+	ls -l
+
+	Create the package file and increment the version by 0.01:
+
+	fpm -s dir -t deb -n "iofog" -v 1.XX -a all --deb-no-default-config-files --after-install debian.sh --after-remove remove.sh --before-upgrade upgrade.sh --after-upgrade debian.sh etc usr var
+
+	Verify that the package was produced and note the file name:
+
+	ls -l
+
+	Publish the package file to the Package Cloud with the new file name:
+
+	package_cloud push iofog/iofog XXXXXXXX.deb
+
+	Repeat the publishing step until you have published for all of the following Linux versions:
+
+	Ubuntu 12.04
+	Ubuntu 14.04
+	Ubuntu 14.10
+	Ubuntu 15.04
+	Ubuntu 15.10
+	Ubuntu 16.04
+
+	Debian Wheezy
+	Debian Jessie
+	Debian Stretch
+	Debian Buster
+
+	Raspian Wheezy
+	Raspian Jessie
+	Raspian Stretch
+	Raspian Buster
+
+ + +#### Build the new RPM package and publish it + +Change directories into the RPM packaging folder. Make a new package build, and BE SURE to increment the version number by .01 every time you do this. Otherwise the publishing will not work properly. + +
+	Change directory:
+
+	cd /iofog-agent-packaging-rpm
+	
+	See that the other RPM package(s) are present (they have a .rpm file extension) and note the current highest version number:
+
+	ls -l
+
+	Create the package file and increment the version by 0.01:
+
+	fpm -s dir -t rpm -n "iofog" -v 1.XX -a all --rpm-os 'linux' --after-install rpm.sh --after-remove remove.sh --before-upgrade upgrade.sh --after-upgrade rpm.sh etc usr var
+
+	Verify that the package was produced and note the file name:
+
+	ls -l
+
+	Publish the package file to the Package Cloud with the new file name:
+
+	package_cloud push iofog/iofog XXXXXXXX.rpm
+
+	Repeat the publishing step until you have published for all of the following Linux versions:
+
+	Fedora 22
+	Fedora 23
+
+	Enterprise Linux 7
+
+ + diff --git a/docs/ioFog-QA-Test-Cases.md b/docs/ioFog-QA-Test-Cases.md new file mode 100644 index 00000000..96a82a64 --- /dev/null +++ b/docs/ioFog-QA-Test-Cases.md @@ -0,0 +1,139 @@ +# iofog-agent Installation QA Test Cases + +To perform quality assurance testing on the ioFog-Agent product, the same test steps need to be executed again and again on different Linux versions, different hardware machines, and after each update to the ioFog-Agent product itself. Use these steps to perform proper testing. The tests validate the product functionality, the installation instructions, and the hosted installation packages. + +#### Installation + +* Choose one of the following supported Linux versions + + * Ubuntu 12.04, 14.04, or 15.10 + * Fedora 22 or 23 + * Debian 7.7 or 8 + * CentOS 7 + * Red Hat Enterprise Linux 7 + +* Create a new Linux installation or use an existing one + +* Use the installation instructions on the correct iofog.org Web page + + * Ubuntu - https://iotracks.com/products/iofabric/installation/linux/ubuntu + + * Debian - https://iotracks.com/products/iofabric/installation/linux/debian + + * Fedora - https://iotracks.com/products/iofabric/installation/linux/fedora + + * Red Hat - https://iotracks.com/products/iofabric/installation/linux/rhel + + * CentOS - https://iotracks.com/products/iofabric/installation/linux/centos + +* Make sure that your Linux installation meets the minimum system requirements + + * Install Java runtime (JRE) 8 or 9 if it is not already installed (both OpenJDK and Oracle Java are suitable) + + * Install Docker 1.5 or higher if it is not already installed + +* Follow the installation steps on the Web page + +* After installation, type 'iofog-agent status' and verify that it produces information results on the screen + +* Type 'iofog-agent start' and see that it runs the ioFog-Agent service in your command prompt (your cursor will be "locked up" during this) + +* Type the "ctrl" and "c" keys together to end the ioFog-Agent service and see that you are returned to a fresh command prompt + +* Use the instructions on the Web page to start the ioFog-Agent daemon in the background (the command may be different for different Linux versions) + +* Type 'iofog-agent status' to verify that the daemon is running + +* Reboot your Linux machine to test the auto-starting of the ioFog-Agent daemon + +* Type 'iofog-agent status' after reboot to verify that it is running + +* Check the information provided by the 'iofog-agent status' command + + * The 'iofog-agent daemon' value should be 'running' + + * The 'system time' value should match the Linux machine + + * The 'connection to controller' value should be 'not provisioned' at this time + +* Type the various help menu commands to make sure they all work properly + + * Type 'iofog-agent' and verify that the help menu displays + + * Verify with 'iofog-agent help' + + * Verify with 'iofog-agent -h' + + * Verify with 'iofog-agent --help' + + * Verify with 'iofog-agent -?' + +* Type the version commands to make sure they work properly + + * Type 'iofog-agent version' and verify that the version information displays + + * Verify with 'iofog-agent -v' + + * Verify with 'iofog-agent --version' + +* Type 'iofog-agent info' and verify that the current ioFog-Agent configuration information displays + +* Type the various configuration update commands to make sure they all work properly + + * Record of all the current configuration values using the 'iofog-agent info' command - you will replace these values at the end of this test sequence + + * For all of the following tests, you should also use the 'iofog-agent info' command to see the configuration changes as you go along + + * Type 'iofog-agent config -d 33.3' and verify that the output shows the old value and the new value is '33.3' + + * Type 'iofog-agent config -dl ~/any/folder/' and verify that the output shows the old value and the new value is '~/any/folder/' + + * Type 'iofog-agent config -m 909' and verify that the output shows the old value and the new value is '909' + + * Type 'iofog-agent config -p 78.5' and verify that the output shows the old value and the new value is '78.5' + + * Type 'iofog-agent config -a https://1.2.3.4/controller/' and verify that the output shows the old value and the new value is 'https://1.2.3.4/controller/' + + * Type 'iofog-agent config -ac ~/temp/certs/abc.crt' and verify that the output shows the old value and the new value is '~/temp/certs/abc.crt' + + * Type 'iofog-agent config -c abcd' and verify that the output shows the old value and the new value is 'abcd' + + * Type 'iofog-agent config -n p2p1' and verify that the output shows the old value and the new value is 'p2p1' + + * Type 'iofog-agent config -l 2.7' and verify that the output shows the old value and the new value is '2.7' + + * Type 'iofog-agent config -ld ~/any/logs/' and verify that the output shows the old value and the new value is '~/any/logs/' + + * Type 'iofog-agent config -lc 8' and verify that the output shows the old value and the new value is '8' + + * For the commands that used numbers, repeat the commands with the value 'zzz' and verify that an 'invalid paramter' message is displayed + + * Enter several commands at the same time, such as 'iofog-agent config -lc 12 -p 90.0 -m 1024' and verify that the changes work properly for all items entered + + * Return all configuration settings to their original values + +* Access the new ioAuthoring 0.2 software in a Web browser + + * If you don't have an account yet, sign up here https://iofog.org/signup + + * Login here https://iofog.org/login + + * Manually enter the URL https://iofog.org/authoring2 in your Web browser address bar + +* Create a new ioFog-Agent instance and generate a provisioning key + +* Back in your Linux command line, type 'iofog-agent provision ABCDWXYZ' and replace the ABCDWXYZ with your provisioning key (it is case sensitive) and verify the results + + * The output will display a success message if the process is successful and will show an ioFog-Agent UUID + + * The output will dipslay an error if the process not successful (a common problem is having the wrong URL for the fog controller set in the configuration) + +* If the provisioning process was successful, type 'iofog-agent status' and verify that the 'controller connection' value is now listed as 'ok' + +* Type 'iofog-agent deprovision' and verify that the output says the ioFog-Agent instance has been deprovisioned + +* Type 'iofog-agent status' and verify that the 'controller connection' value is now listed as 'not provisioned' + + + + diff --git a/docs/ioFog-Status-Information-Specification.md b/docs/ioFog-Status-Information-Specification.md new file mode 100644 index 00000000..90d7f053 --- /dev/null +++ b/docs/ioFog-Status-Information-Specification.md @@ -0,0 +1,65 @@ +# Status Information Specification + +Each module has specific status information associated with it, including the Status Reporter module itself. Some of the pieces of information must be sent to the fog controller through the Field Agent module. Those pieces of information are marked with a "(FC)" after the name. + +#### Supervisor + +* Daemon status (FC) - what is the ioFog daemon condition? Values are "starting", "running", and "stopped" +* Module status - what is the status for each module? Values are "starting", "running", and "stopped" +* Operating duration (FC) - how long has the ioFog daemon been running uninterrupted? +* Timestamp of last start (FC) - what is the UTC timestamp of when the daemon was last started? + + +#### Resource Consumption Manager + +* Memory usage (FC) - how much RAM is ioFog using? +* Disk usage (FC) - how much disk space is ioFog using (do not include log file disk measurements here)? +* CPU usage (FC) - what percentage of CPU time is ioFog using? +* Memory in violation (FC) - is the memory usage in violation of the consumption limit? +* Disk in violation (FC) - is the disk usage in violation of the consumption limit? +* CPU in violation (FC) - is the CPU usage in violation of the consumption limit? + + +#### Process Manager + +* Number of running elements - how many elements are running right now? +* Docker status - what is the condition of Docker on this machine? Values are "not present", "running", and "stopped" +* Element status (FC) - what is the status of each element? Use the following breakdown to represent the element status information: + * ID - the unique identifier of each element + * Status - the current status of the element. Values are "building", "failed verification", "starting", "running", and "stopped" + * Start time - the UTC timestamp of when the element was last started + * Operating duration - how long has the element been running? +* Number of repositories (FC) - how many Docker container repositories are being used by ioFog? +* Repository status (FC) - what is the status of each repository? Use the following breakdown to represent the repository status information: + * URL - the URL of the repository + * Link status - what is the condition of the connection to this repository? Values are "failed verification", "failed login", and "connected" + + +#### Status Reporter + +* System time (FC) - what is the current system time in ioFog? This only needs to be measured and reported about every minute +* Last status update time (FC) - what is the UTC timestamp of the newest piece of status information? + + +#### Local API + +* Current IP address (FC) - what is the IP address of the machine running ioFog? It should be the address assigned to the configured network adapter +* Number of active real-time configuration sockets - how many real-time configuration sockets are being held open on the Local API? +* Number of active real-time data sockets - how many real-time data sockets are being held open on the Local API? + + +#### Message Bus + +* Processed messages (FC) - how many total messages (approximate) have been processed by ioFog? +* Messages published per element (FC) - how many messages (approximate) have been published by each element? Use the following breakdown to represent the information: + * ID - the unique identifier of each element + * Number of messages - the count of messages published by this element +* Average message speed (FC) - what is the average speed of messages moving through ioFog? + + +#### Field Agent + +* Controller connection status - what is the status of the connection to the configured fog controller? Values are "not provisioned", "broken", and "ok" +* Last command time (FC) - what is the UTC timestamp of the newest command received by the field agent? +* Controller verification - what is the status of the fog controller identity verification? Values are "failed verification", and "verified" + diff --git a/docs/ioFog-Stream-Viewer-Specification.md b/docs/ioFog-Stream-Viewer-Specification.md new file mode 100644 index 00000000..83cf41e5 --- /dev/null +++ b/docs/ioFog-Stream-Viewer-Specification.md @@ -0,0 +1,75 @@ +# Stream Viewer Specification + +The Stream Viewer system container receives configuration through the ioFog Local API just like every other container. The configuration is specified here. It hosts a REST API and a standard HTTP Web server that are also specified here. + +#### Container Configuration Example +
+	{"accesstoken":"fshkuewwre89ysdkSDFHKJwe9ywiuhfsdkhj","foldersizelimit":200.0}
+
+	The "accesstoken" value is the current token that must be provided by anyone attempting to access the REST API
+
+	The "foldersizelimit" value is the size limit of each output file storage folder that is created per publisher, in MiB
+
+ + +### REST API Endpoints + +All endpoints are hosted on port 80 as regular HTTP REST API that provide JSON outputs (MIME type of application/json). All endpoints require that the current access token be passed in the query otherwise the response should be a "404 not found" HTTP response code. + +#### Get File List For Publisher Within Timeframe + +This endpoint takes in the access token, publisher ID, timeframe start, and timeframe end parameters and gives out a JSON array of files. The files are all messages that have been received by the Stream Viewer container and converted into files and stored in a folder for this particular publisher. + +##### Endpoint + +
+	http://1.2.3.4:80/v2/viewer/files/publisher/dfshigu4wedsuiohdsf/starttime/1234567890123/endtime/1234567890123/accesstoken/fshkuewwre89ysdkSDFHKJwe9ywiuhfsdkhj
+
+	Note that the example IP address of 1.2.3.4 will be replaced by the real container IP address and the container itself does not need to know the address
+
+ +##### Response + +
+	{
+		"status":"okay",
+		"count":4,
+		"files":
+			[
+				"files/dfshigu4wedsuiohdsf/1234567890123.txt",
+				"files/dfshigu4wedsuiohdsf/1234567890128.txt",
+				"files/dfshigu4wedsuiohdsf/1234567890149.txt",
+				"files/dfshigu4wedsuiohdsf/1234567890202.png"
+			]
+	}
+
+ +##### Querystring Parameters + +
+	publisher - the Publisher ID from which to return files
+
+	starttime - the timestamp representing the earliest file desired (inclusive)
+
+	endtime - the timestamp representing the latest file desired (inclusive)
+
+	accesstoken - the current access token for getting access to the REST API endpoints
+
+ +##### POST Parameters + +
+	None
+
+ + +### HTTP Web Server + +Standard HTTP Web server allow clients to request files that reside on the server. In the case of the Stream Viewer system container, the files are in the different publisher folders. The code for providing the files as output is the same as the code that would be used to provide Web content files as output. The URLs for the files will therefore look something like this: + +
+	http://1.2.3.4:80/files/dfshigu4wedsuiohdsf/1234567890202.png
+
+ + + diff --git a/docs/ioFog-System-Container-Requirements.md b/docs/ioFog-System-Container-Requirements.md new file mode 100644 index 00000000..dbb4b9e6 --- /dev/null +++ b/docs/ioFog-System-Container-Requirements.md @@ -0,0 +1,171 @@ +# ioFog System Container Requirements + +Every ioFog instance comes with some default containers. These "system containers" provide functionality that enhances the ioFog software. The reason this functionality comes in the form of a container is so it can be updated easily for all ioFog instances. One of the best things about ioFog is that once the base software that handles containers is running, everything else can be implemented as a container and this minimizes the versioning problems that happen with distributed software. + +The first system container is called Core Networking. It is responsible for connecting to instances of the ComSat product, also made by iofog. ComSat creates secure network pipes that move pure bytes from one place to another through all firewalls and other internetworking challenges. So the Core Networking container has functionality that manages connections to the ComSat, understands how to verify the identity of a ComSat, and relays the incoming traffic to the proper place in the ioFog instance. It also moves the outbound traffic to the ComSat so it can reach its desintation on the other side (which is always unknown from the ioFog instance's perspective). The Core Networking system container can be operated in two different modes. + +In public mode, the container takes in bytes via ComSat socket connections and sends those bytes directly to a designated host on the local network at the proper local port. Then it takes the response bytes and moves them directly back to the ComSat socket that sent the original bytes. This creates a secure pipe from the outside world into a specific port on a specific container... all without revealing any information about the container to the outside world or exposing anything other than the chosen port. + +In private mode, the Core Networking system container receives data messages on the ioFog real-time data message Websocket and sends them on the ComSat socket. It also receives messages on the ComSat socket and publishes them on the ioFog real-time data message Websocket. Although the Core Networking container does not know where the other end of the ComSat socket is connected, this still results in a many-to-many interconnected web of ioFogs. The container does not need to know the destination. It only needs to follow the rules of communication while operating in private mode. The ComSat is the only thing that knows which sockets connect together, but it has no knowledge of what is connected to those sockets. And because the ioFog instances are the initiators of the ComSat socket connections on both sides... if the ComSat becomes compromised, any break in the existing connections to try to re-route them would result in ioFog instances closing their connections... and when they would try to reestablish connections they would be using non-matching passcodes. + +The second system container is called Debug Console. Its primary purpose is to give developers the ability to look at ioMessages without needing to build an application to do so. So why can't developers just look at ioMessages directly? Because actual ioMessages move very quickly from one container to another and then they are gone. They either end up going out from an ioFog instance to another ioFog instance or going out to some final endpoint such as a data repository, an enterprise cloud system, or something similar. Developers need to look at ioMessages so they can debug their code in production situations and so they can see what the ioMessage data looks like at different points in the container-to-container processing chain. + +The Debug Console captures the ioMessages that are routed to it and makes them available through a REST API. By taking data that is in motion and holding onto it, the Debug Console gives the developer a chance to see what's happening in a live system just like they were running a debugging console on a local build environment. It's a lot like setting a breakpoint in an IDE and looking at the value of some variables or objects. + +The Debug Console hosts a REST API on port 80 that provides access to the messages it is holding. In order to talk to the container on port 80, the public port feature of the ComSat technology is used. This allows a developer to see ioMessages moving through a live system from anywhere. This is important because most deployed instances of ioFog will not be in the same physical location as the developer or the solution maintenance person. To prevent unwanted access to the ioMessages, the Debug Console only responds to REST API requests that provide a valid access token. The Debug Console container gets the current valid access token from its container configuration information. + +The third system container is called Stream Viewer. Its primary purpose is to provide developers with a way to see the messages moving through the ioFog in human way... looking at the content instead of the data. Just like photo editing software lets you look at the picture you are editing, the Stream Viewer lets you see the information moving through ioFog in its native form. In order to show the messages in a human-viewable format, the Stream Viewer needs to interpret the incoming messages and save them as the appropriate output files. It knows how to format and save the different types of messages because it has a reference table that matches to the known ioMessage InfoTypes and InfoFormats. When an incoming message does not match any known types (there is no entry in the table) then the Stream Viewer just creates a default output file for that message. + +The Stream Viewer stores output files with timestamps as the names. They are stored in folders with the names of the publishers. This allows Stream Viewer to identify the proper files when generating responses for its REST API. And speaking of REST APIs... the Stream Viewer provides a REST API on port 80 that allows developers to ask for the list of files for a particular publisher within a particular timeframe. The Stream Viewer also provides an HTTP Web server that serves out the files from the publisher folders when they are directly requested on port 80. By providing both of these services, Stream Viewer gives developers the ability to see what files are available to view and then retrieve them for direct viewing. As an example, let's assume we have a camera connected to ioFog. The photos coming from the camera are routed into Stream Viewer. The Stream Viewer saves them as their native PNG file type and keeps them in a folder. The developer asks the Stream Viewer REST API for the list of files belonging to that camera and generated in the last 5 minutes. The Stream Viewer gives the file list as JSON output and then the developer loops through the file list and retrieves the PNG files directly from the Stream Viewer like a regular Web server. Now the developer can see what the camera saw in the last 5 minutes... and they can do it from anywhere on the planet! + + +#### Core Networking Container Requirements + +* Hold a pool of socket connections to the ComSat specified in the configuration + +* Create the number of pool connections specified in the configuration + +* If configured in "private" mode, receive and post messages from and to the ioFog + +* If configured in "public" mode, take incoming bytes on the ComSat socket and pipe them directly into a local network request + +* Make local network requests based on the configuration provided for this container + +* Pipe the response from the local network request directly back to the ComSat socket which sent the bytes + +* When a ComSat socket closes, remove it from the connection pool + +* Monitor the connection pool and make sure it always has the configured number of open connections + +* If connectivity to the ComSat disappears for any reason (gets dropped, network unavailable, etc.) then close all connections in the pool and open fresh connections to the ComSat + +* If connections to the ComSat cannot be opened, try again regularly but don't consume too much CPU usage + +* Send the ComSat socket passcode (provided in container configuration) immediately upon successfully opening each ComSat socket + +* When in "private" mode, use the real-time data message Websocket to send and receive messages to and from the ioFog Local API + +* Use the real-time control message Websocket to make sure any changes to container configuration are received immediately + +* When a "new config" control message is received, immediately make a request on the ioFog REST API to retrieve the updated container configuration + +* Build this system container with the Java Container SDK + +* Use TLS secure socket connections to the ComSat, which will not open successfully if the identity of the ComSat cannot be verified + +* Use a local intermediate public certificate file to verify the identity of the ComSat + +* Send a heartbeat transmission to the ComSat on every open socket at the interval specified in the container configuration + +* Send the ASCII byte values 'BEAT' as the heartbeat transmission on the ComSat sockets + +* Keep track of the last time each socket had successful communication with the ComSat (the "last active" timestamp) + +* Check incoming socket messages to see if they are equal to 'BEAT' or 'BEATBEAT'... if they are, update the "last active" timestamp for the receiving socket + +* When a ComSat socket has been inactive past the threshold (set in container config) then close the socket so a new one can be opened + +* Check incoming socket messages to see if they are equal to "AUTHORIZED"... if they are, update the "last active" timestamp for the receiving socket... this is a response that the ComSat will provide when the passcode was correct for a newly opened socket + +* When sending a message on a ComSat socket, send a 'TXEND' transmission immediately after the end of the actual message + +* When receiving a message on a ComSat socket, accumulate the incoming message bytes until receiving a 'TXEND' transmission... then it is OK to parse the message + +* After receiving a 'TXEND' transmission on a ComSat socket, send an 'ACK' transmission + +* When operating in "private" mode, keep a buffer of messages to be sent on the ComSat socket... this this allows messages to still be delivered under troublesome network connectivity situations + +* Remove a message from the buffer when an "ACK" message has been received after sending the message + +* If an 'ACK' is not received after sending a message on a ComSat socket, send the same message again after a short time period + +* Limit the amount of messages stored in the buffer to a safe level to avoid memory limit crashes... simply delete the oldest message when a new one arrives + +* Limit the number of bytes being buffered by a receiving ComSat socket to a safe level... drop bytes out of memory if needed and do not attempt to parse messages that have missing bytes... and close a socket connection if needed in order to avoid memory limit crashes... alternatively you can drop bytes until receiving a 'TXEND' and then send an 'ACK' in order to avoid receiving the same large message again + +* Parse and consume container configuration according to this example: + +
+	{"mode":"public","host":"comsat1.iofog.org","port":35046,"connectioncount":60,"passcode":"vy7cvpztnhgc3jdptgxp9ttmzxfyfbqh","localhost":"iofog","localport":60401,"heartbeatfrequency":20000,"heartbeatabsencethreshold":60000}
+
+	Or
+
+	{"mode":"private","host":"comsat2.iofog.org","port":35081,"connectioncount":1,"passcode":"vy7cvpztnhgc3jdptgxp9ttmzxfyfbqh","localhost":"","localport":0,"heartbeatfrequency":20000,"heartbeatabsencethreshold":60000}
+
+ + +#### Debug Console Container Requirements + +* Get the current container configuration from the ioFog Local API immediately when the container starts + +* Open a control message Websocket connection to the ioFog Local API and make sure that an open connection is always present + +* When a new config message is received on the control message Websocket, send an "acknowledge" response and then make a request to the ioFog Local API REST endpoing to get the current container configuration + +* Whenever container configuration is received, use the configuration information to set up the container's operations according to the config information - the template for the configuration information can be found in the Debug Console Specification document + +* Open a data message Websocket connection to the ioFog Local API and make sure that an open connection is always present + +* Receive messages that arrive on the data message Websocket connection and send an "acknowledge" response and then store each message in the appropriate file + +* Store messages in a different file for each publisher + +* Store messages in JSON format in the files in a way that allows them to be retrieved and turned into an array easily + +* Name the storage files as "XXXX.json" where the XXXX is the actual publisher ID + +* Limit the stored file size for each publisher - The limit storage size will be provided in the container configuration + +* When a file for a particular publisher file has reached its size limit, simply delete the oldest message to make room for the next new message + +* Provide a REST API on port 80 according to the Debug Console Specification document + +* Provide a "404 Not Found" response to any request on the REST API that does not include a valid access token + +* Use the local JSON message storage files to get the messages needed for output on the REST API + +* Use the Java Container SDK to build the container + + +#### Stream Viewer Container Requirements + +* Get the current container configuration from the ioFog Local API immediately when the container starts + +* Open a control message Websocket connection to the ioFog Local API and make sure that an open connection is always present + +* When a new config message is received on the control message Websocket, send an "acknowledge" response and then make a request to the ioFog Local API REST endpoing to get the current container configuration + +* Whenever container configuration is received, use the configuration information to set up the container's operations according to the config information - the template for the configuration information can be found in the Stream Viewer Specification document + +* Open a data message Websocket connection to the ioFog Local API and make sure that an open connection is always present + +* Receive messages that arrive on the data message Websocket connection and send an "acknowledge" response and then process each message + +* Keep a table of message types and formats that contains a processing instruction for each type and format + +* Provide a set of processing functions that can be used for processing different types of messages... the processing consists of taking the incoming message data and turning it into a completed output file + +* Provide a default processing function that can be used for messages which have no entry in the table of message types and formats + +* Store messages as a separate file in the appropriate folder + +* Create a separate folder for each publisher that is sending messages into the Stream Viewer container + +* Name the stored files as "1234567890123.XXX" where the 1234567890123 is the timestamp of the message and XXX is the standard file extension of the file type + +* Limit the stored folder size for each publisher - The limit storage size will be provided in the container configuration + +* When a folder for a particular publisher has reached its size limit, simply delete the oldest file to make room for the next new file + +* Provide a REST API on port 80 according to the Stream Viewer Specification document + +* Provide a "404 Not Found" response to any request on the REST API that does not include a valid access token + +* Provide a standard HTTP Web server on port 80 that serves out the files located in the publisher folders when directly requested + +* Use the local stored files to generate the file list for output on the REST API + +* Use the Java Container SDK to build the container + + + diff --git a/docs/ioFog-Test-Message-Generator.md b/docs/ioFog-Test-Message-Generator.md new file mode 100644 index 00000000..e6053dcf --- /dev/null +++ b/docs/ioFog-Test-Message-Generator.md @@ -0,0 +1,52 @@ +# Test Message Generator + +As developers build ioElement containers, they start by building the code in a non-container environment. They need to test the code before spending the time turning it into a published container... but they can't actually test the processing of messages or connection to the ioFog instance without going through the publishing and deployment process. + +To facilitate development, we need to have a surrogate version of the ioFog Local API. It should mimic the API endpoints of the real ioFog Local API, including offering the control Websocket and message Websocket. It should run on "localhost" so it can be reached directly on the computer that is being used to build the ioElement container. + +A developer can precisely mimic the production environment on their build machine by mapping a host. If they map "127.0.0.1" with the host name "iofog" then their local code will be able to operate with the same "http://iofog:54321/" endpoints found in the SDKs and described in the API specification. + +#### Functional Requirements + +* Allow the developer to set up a list of fully defined ioMessages that the Test Message Generator will output + +* Read the list of defined ioMessages from a local file called "messages.xml" + +* Randomly send ioMessages from the list as output + +* Allow the developer to specify the rate of output messages + +* Provide the full set of API endpoints specified for the production Local API module of ioFog (including both Websockets) + +* Run as a local server listening on port 54321 just like the production ioFog Local API + +* Log messages that are posted into the Test Message Generator so developers can verify that their message transmission is working properly + +* Output the messages that are posted into the Test Message Generator in a local file called "receivedmessages.xml" + +* Allow the developer to set up configuration JSON for their ioElement container that the Test Message Generator will give as output + +* Read the configuration JSON that the developer has setup from a local file called "containerconfig.json" + +* Allow the developer to specify the rate of transmission of "new configuration" control messages + +* Send a "new configuration" control message to the ioElement container at the interval specified by the developer + +* Send the complete list of defined ioMessages when the ioElement container makes a request on the "http://iofog:54321/v2/messages/query" endpoint. Do not send any "new configuration" control messages. No matter what publisher list and timeframe is submitted to this endpoint, only respond with the complete list of defined data messages. + +* Read the Test Message Generator configuration from a local file called "configuration.xml" + + +#### Configuration Example + +Developers can set the Test Message Generator to provide messages at a certain rate. The message that is sent will be randomly selected from the list of defined ioMessages. Setting up the behavior of the Test Message Generator is done with a local configuration XML file. Interval times are in milliseconds, allowing developers to test their container code with fast message rates. + +Here is a sample of the configuration file: + +
+	<configuration>
+		<datamessageinterval>500</datamessageinterval>
+		<controlmessageinterval>10000</controlmessageinterval>
+	</configuration>
+
+ diff --git a/docs/ioFog-Testing-Strategy.md b/docs/ioFog-Testing-Strategy.md new file mode 100644 index 00000000..4c15c7fb --- /dev/null +++ b/docs/ioFog-Testing-Strategy.md @@ -0,0 +1,35 @@ +# Testing Strategy + +Adding tests during the creative initial phases of development can derail progress. Nothing is stable yet, so how can you decide on proper tests? Yet knowing the types of tests that will soon be added can guide development in the right direction. This document lists the types of tests that will be applied to the product as development progresses. It should be expanded and clarified as time goes on. + +The tests themselves will be implemented at at the appropriate stages. Unit tests, for example, will be added as soon as code structures exit prototyping and become mostly stable. Performance tests and usability tests will be added when entire end-to-end processes are functioning in the product. Security tests will be added mid-way through the product development but will be kept in mind from the very start of the engineering efforts. + +#### Unit Tests + +* Outcome-based tests for code classes (don't test each little method, test the actual entry and exit points that will be used in the program flow) +* Test for all input and output conditions in order to catch as many edge cases as possible +* Use unit tests to measure performance of code, but only fail the test if the performance metric has a hard limit that is understood +* Apply unit tests during the build process using standard Java tools and methods + +#### Performance Tests + +* Test the response time of the command line functionality +* Test the speed of setting up the default ioElement containers +* Test the installation time +* Test the time needed to provision an ioFog instance to a fog controller +* Test the data and message throughput +* Test the speed of setting up additional ioElement containers +* Test the speed of changing configuration and network settings for ioElement containers + +#### Usability Tests + +* Test the installation process on each version of Linux in the supported version list +* Test the command line functionality thoroughly +* Test using the help information provided by the program to learn how to operate the program +* Test the success rate of provisioning an ioFog instance to a fog controller + +#### Security Tests + +* Test the ability of a user with insufficient privileges to access the program and perform tasks +* Check the program against the Potential Security Vulnerabilities document guidelines to look for weaknesses +* Hire a 3rd party security expert to run penetration tests and evaluate the product diff --git a/docs/ioFog-ioMessage-Specification.md b/docs/ioFog-ioMessage-Specification.md new file mode 100644 index 00000000..ae62c1ff --- /dev/null +++ b/docs/ioFog-ioMessage-Specification.md @@ -0,0 +1,302 @@ +# ioMessage Specification version 4 (March 2nd, 2016) + +The purpose of a message is move information along a path. No understanding of the contents of the messages should be required in order to help it to its correct destination. The header fields of each message, however, are intended to be read and understood by functional pieces of the iofog system. Because the data contents of the message format are open, that means each recipient will be required to determine for itself if it understands how to read the data. Recipients can check the information type and information format headers to determine this. + +The ioMessage versions are integers, not decimals. This is because it is harder to parse a raw binary message with decimals across different computing platforms. So... ioMessage versions will be things like 4, 5, and 12. The version can be used to determine what fields will be present in the message and perhaps how the data will be arranged in those fields. + +The ID for each message must be unique across the Earth for 20 years or longer. Depending on the volume of ioMessages across the globe, a 128-bit identifier may reach a 99.9%+ chance of collisions well before that timeframe ends. So a 256-bit identifier has been chosen and should suffice. + +The fields listed here do not contain any formatting information except for the ID, which is strictly standardized. Each embodiment of the ioMessage standard will make use of the best features of the embodiment method. For example, when using JSON to create ioMessages, there is no need to include length information about the different fields. And there is no need to put any particular field in any particular position. XML is similar. But when encoding an ioMessage in raw bytes, the order of the information is very crucial for packing and parsing the messages. While JSON and XML offer some advantages, they also have more overhead than raw bytes. And while raw byte formatting requires parsing by the receiver, it also has very low overhead and is excellent for real-time transmission of media such as photos or video. + +A listing for JSON, XML, and raw bytes is included in this document after the main field listing. + +### Fields of an ioMessage + +#### ID +| | | +|---|---| +|*Data Type*|Text| +|*Key*|ID| +|*Required*|Yes| +|*Description*|A 256-bit universally unique identifier per message allows for portability and globe-wide verification of events. The ID string is formatted in base58 for readability, transmission safety between systems, and compactness.| + +#### Tag +| | | +|---|---| +|*Data Type*|Text| +|*Key*|Tag| +|*Required*|No| +|*Description*|This is an open field for associating a message with a particular device or any other interesting thing. It should be queryable later, making this a high-value field for some applications.| + + +#### Message Group ID +| | | +|---|---| +|*Data Type*|Text| +|*Key*|GroupID| +|*Required*|No| +|*Description*|This is how messages can be allocated to a sequence or stream.| + + +#### Sequence Number +| | | +|---|---| +|*Data Type*|Integer| +|*Key*|SequenceNumber| +|*Required*|No| +|*Description*|What number in the sequence is this current message?| + + +#### Sequence Total +| | | +|---|---| +|*Data Type*|Integer| +|*Key*|SequenceTotal| +|*Required*|No| +|*Description*|How many total messages are in the sequence? Absence of a total count while sequence numbers and a message group ID are present may be used to indicate a stream with indeterminate length.| + + +#### Priority +| | | +|---|---| +|*Data Type*|Integer| +|*Key*|Priority| +|*Required*|No| +|*Description*|The lower the number, the higher the priority. This is a simple quality of service (QoS) indicator. Emergency messages or system error logs might get the highest priority. Self-contained messages (such as a button push or a temperature reading) might get very high priority. Media stream messages (such as one second of audio) might get very low priority ranking in order to allow message slowing or dropping as needed in a busy system.| + + +#### Timestamp +| | | +|---|---| +|*Data Type*|Integer| +|*Key*|Timestamp| +|*Required*|Yes| +|*Description*|Universal timecode including milliseconds. Milliseconds can be entered as zeroes if needed.| + + +#### Publisher +| | | +|---|---| +|*Data Type*|Text| +|*Key*|Publisher| +|*Required*|Yes| +|*Description*|This is the identifier of the element that is sending the message. It can be used to determine routing or guarantee privacy and security. Because each element is assigned a UUID during configuration, even across ioFog instances no message should be received by an unintended entity.| + + +#### Authentication Identifier +| | | +|---|---| +|*Data Type*|Text| +|*Key*|AuthID| +|*Required*|No| +|*Description*|This is an open field to pass along authentication information about the particular authorized entity generating the message, such as an employee ID number or a user ID in the application.| + + +#### Authentication Group +| | | +|---|---| +|*Data Type*|Text| +|*Key*|AuthGroup| +|*Required*|No| +|*Description*|This is an open field to pass authentication group information. This allows pieces of the application to know they are dealing with a message from an authenticated user of a particular type (such as “employee” or “system admin”) without needing to know the actual identification information.| + + +#### ioMessage Version +| | | +|---|---| +|*Data Type*|Integer| +|*Key*|Version| +|*Required*|Yes| +|*Description*|Which version of the ioMessage format does this particular message comply with?| + + +#### Chain Position +| | | +|---|---| +|*Data Type*|Integer| +|*Key*|ChainPosition| +|*Required*|No| +|*Description*|When using cryptographic message chaining, this field represents the position in the message chain that this paricular message occupies. It is similar to the "block height" value found in blockchain technology.| + + +#### Hash +| | | +|---|---| +|*Data Type*|Text| +|*Key*|Hash| +|*Required*|No| +|*Description*|When using cryptographic message chaining, a hash of this entire message can be included here.| + + +#### Previous Message Hash +| | | +|---|---| +|*Data Type*|Text| +|*Key*|PreviousHash| +|*Required*|No| +|*Description*|When using cryptographic message chaining, the hash value of the previous message is included here. This forms the cryptographic link from the prior message to this one.| + + +#### Nonce +| | | +|---|---| +|*Data Type*|Text| +|*Key*|Nonce| +|*Required*|No| +|*Description*|When using cryptographic message chaining, an open field is needed to achieve the correct hash value. The information in this field will not be meaningful, but will be necessary to produce the final hash of the message.| + + +#### Difficulty Target +| | | +|---|---| +|*Data Type*|Integer| +|*Key*|DifficultyTarget| +|*Required*|No| +|*Description*|When using cryptographic message chaining, this field represents the hashing workload required to cryptographically seal the chain.| + + +#### Information Type +| | | +|---|---| +|*Data Type*|Text| +|*Key*|InfoType| +|*Required*|Yes| +|*Description*|This is like a MIME type. It describes what type of information is contained in the content data field.| + + +#### Information Format +| | | +|---|---| +|*Data Type*|Text| +|*Key*|InfoFormat| +|*Required*|Yes| +|*Description*|This is a sub-field of the Information Type. It defines the format of the data content in this message. If the information type is “Temperature”, for example, then the information format might be “Degrees Kelvin”.| + + +#### Context Data +| | | +|---|---| +|*Data Type*|Any (including binary, text, integer, etc.)| +|*Key*|ContextData| +|*Required*|No| +|*Description*|Context data in raw bytes. This field can be used to embed any information desired and will likely be very different from one solution to the next. It is the responsibility of the receiving element(s) to understand the context data format and the meaning of the context information.| + + +#### Data Content +| | | +|---|---| +|*Data Type*|Any (including binary, text, integer, etc.)| +|*Key*|ContentData| +|*Required*|Yes| +|*Description*|The actual data content of the message in its raw form. Having a raw format for this field allows for the greatest amount of flexibility in the system.| + + + +### JSON Embodiment of an ioMessage + +The ContextData and ContentData fields of an ioMessage, when embodied in JSON, will always be base64 encoded. This is because these fields contain raw bytes and there is no other way to represent raw bytes in the utf-8 structure that JSON uses. Upon receiving a JSON ioMessage, you must base64 decode those two fields. Before sending a JSON ioMessage, you must base64 encode those two fields. + +
+	{
+		"id":"sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf",
+		"tag":"Bosch Camera 16",
+		"groupid":"",
+		"sequencenumber":1,
+		"sequencetotal":1,
+		"priority":0,
+		"timestamp":1234567890123,
+		"publisher":"Ayew98wtosdhFSKdjhsdfkjhkjesdhg",
+		"authid":"",
+		"authgroup":"",
+		"version":4,
+		"chainposition":0,
+		"hash":"",
+		"previoushash":"",
+		"nonce":"",
+		"difficultytarget":0.0,
+		"infotype":"image/jpeg",
+		"infoformat":"file/.jpg",
+		"contextdata":"",
+		"contentdata":"sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufhaskldjfslkjdhfalsjdf=serg4towhr"
+	}
+
+ + +### XML Embodiment of an ioMessage + +The ContextData and ContentData fields of an ioMessage, when embodied in XML, will always be base64 encoded. This is because these fields contain raw bytes and there is no other way to represent raw bytes in the text formats that XML uses. Upon receiving an XML ioMessage, you must base64 decode those two fields. Before sending an XML ioMessage, you must base64 encode those two fields. + +
+	<iomessage>
+		<id>sd098wytfskduhdsfDSKfhjw4o8ytwesdoiuhsdf</id>
+		<tag>Bosch Camera 16</tag>
+		<groupid></groupid>
+		<sequencenumber>1</sequencenumber>
+		<sequencetotal>1</sequencetotal>
+		<priority>0</priority>
+		<timestamp>1234567890123</timestamp>
+		<publisher>Ayew98wtosdhFSKdjhsdfkjhkjesdhg</publisher>
+		<authid></authid>
+		<authgroup></authgroup>
+		<version>4</version>
+		<chainposition>0</chainposition>
+		<hash></hash>
+		<previoushash></previoushash>
+		<nonce></nonce>
+		<difficultytarget>0.0</difficultytarget>
+		<infotype>image/jpeg</infotype>
+		<infoformat>file/.jpg</infoformat>
+		<contextdata></contextdata>
+		<contentdata>sDFDSKJhsdkljasjklweklfjwhefiauhw98p328946982weiusfhsdkufha</contentdata>
+	</iomessage>
+
+ + +### Binary Embodiment of an ioMessage + +Bytes are octets here. No funny business. Just good old 8-bit bytes. The sequence of bytes here must be followed strictly so the message can be parsed by the receiver. + +
+	[2 bytes] - Version
+
+	[1 bytes] - Length of ID field
+	[2 bytes] - Length of Tag field
+	[1 bytes] - Length of Group ID field
+	[1 bytes] - Length of Sequence Number field
+	[1 bytes] - Length of Sequence Total field
+	[1 bytes] - Length of Priority field
+	[1 bytes] - Length of Timestamp field
+	[1 bytes] - Length of Publisher field
+	[2 bytes] - Length of Auth ID field
+	[2 bytes] - Length of Auth Group field
+	[1 bytes] - Length of Chain Position field
+	[2 bytes] - Length of Hash field
+	[2 bytes] - Length of Previous Hash field
+	[2 bytes] - Length of Nonce field
+	[1 bytes] - Length of Difficulty Target field
+	[1 bytes] - Length of Info Type field
+	[1 bytes] - Length of Info Format field
+	[4 bytes] - Length of Context Data field
+	[4 bytes] - Length of Content Data field
+
+	[n bytes] - ID value
+	[n bytes] - Tag value
+	[n bytes] - Group ID value
+	[n bytes] - Sequence Number value
+	[n bytes] - Sequence Total value
+	[n bytes] - Priority value
+	[n bytes] - Timestamp value
+	[n bytes] - Publisher value
+	[n bytes] - Auth ID value
+	[n bytes] - Auth Group value
+	[n bytes] - Chain Position value
+	[n bytes] - Hash value
+	[n bytes] - Previous Hash value
+	[n bytes] - Nonce value
+	[n bytes] - Difficulty Target value
+	[n bytes] - Info Type value
+	[n bytes] - Info Format value
+	[n bytes] - Context Data value
+	[n bytes] - Content Data value
+
+ diff --git a/docs/ioMessage-Formats-Specification.md b/docs/ioMessage-Formats-Specification.md new file mode 100644 index 00000000..ce34b293 --- /dev/null +++ b/docs/ioMessage-Formats-Specification.md @@ -0,0 +1,94 @@ +# ioMessage Formats + +The ioMessage structure allows developers to use any format they desire for moving information from one ioElement container to another, including completely undocumented custom formats. But high levels of code re-use come from using formats that other containers understand, as well. Drawing from established standards on the Internet, World Wide Web, and M2M industry makes it easier to build IoT solutions that merge with existing technologies. + +Standard ioMessage specifications, like MIME types on the World Wide Web, allow for instant interoperability between ioElements. There can be an infinite number of specifications, but the lower the number the better the Internet of Things will be for all involved. + +We will most likely need to police the proliferation of specifications at some point, but at first it’s better to just define a new type spec as needed with as much “general use focus” as possible. + +Every specification is simply a pre-defined text value for the “Information Type” and “Information Format” fields of the ioMsg message format. But along with the pre-defined values for those fields (InfoType and InfoFormat according to the ioMessage structure specification) comes adherence with a content payload structure that is also defined here. This loose arrangement is what makes it possible to parse an incoming message from elements built by other parties. + +This is a starting list of formats, intended to grow through mass contribution. + +### General Purpose JSON + +This format is really flexible but easy to abuse. It is intended to carry any JSON information, which means that there is no explanation of the type of information contained in the JSON. While it is convenient to use this format because it does not require any particular fields in the JSON, it should be noted that using this format requires the receiving containers to parse and identify the information being received or to have been built with prior knowledge of what information will be passed. + +##### InfoType +
+	application/json
+
+ +##### InfoFormat +
+	text/utf-8
+
+ +##### ContentData (raw) +
+	{“key1”:”value1”,”key2”:”value2”,”namedarray1”:[17,28,1201,0]}
+
+ +##### ContentData (base64) +
+	e+KAnGtleTHigJ064oCddmFsdWUx4oCdLOKAnWtleTLigJ064oCddmFsdWUy4oCdLOKAnW5hbWVkYXJyYXkx4oCdOlsxNywyOCwxMjAxLDBdfQ==
+
+ + +### Image JPEG + +Based on the standard MIME type, this is just a regular JPEG image file. Whether it is sent in raw bytes or encoded in base64, this message type contains exactly the same bytes as a .jpg file which would appear on a hard disk. + +##### InfoType +
+	image/jpeg
+
+ +##### InfoFormat +
+	file/.jpg
+
+ +##### ContentData (raw) + + +##### ContentData (base64) +
+	/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMtaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKE1hY2ludG9zaCkiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6N0M0MjJDRjkyNEY0MTFFNUEzNTlGRDM3NTYyRUM4RDgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6N0M0MjJDRkEyNEY0MTFFNUEzNTlGRDM3NTYyRUM4RDgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3QzQyMkNGNzI0RjQxMUU1QTM1OUZEMzc1NjJFQzhEOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3QzQyMkNGODI0RjQxMUU1QTM1OUZEMzc1NjJFQzhEOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv/uAA5BZG9iZQBkwAAAAAH/2wCEAAYEBAQFBAYFBQYJBgUGCQsI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
+
+ + +### OpenWeatherMap Mixed Data + +##### InfoType +
+	weather/mixed/open-weather-map
+
+ +##### InfoFormat +
+	text/json
+
+ +##### ContentData(raw) +
+	{"coord":{"lon":-122.45,"lat":37.77},"weather":[{"id":721,"main":"Haze","description":"haze","icon":"50d"}],"base":"cmc stations","main":{"temp":281.48,"pressure":1021,"humidity":83,"temp_min":276.15,"temp_max":289.15},"wind":{"speed":5.1,"deg":120,"gust":8.2},"clouds":{"all":1},"dt":1460727300,"sys":{"type":1,"id":226,"message":0.0045,"country":"US","sunrise":1460727181,"sunset":1460774797},"id":5391997,"name":"San Francisco County","cod":200}
+
+ +### Temperature Conversion + +##### InfoFormat +
+	decimal/kelvin
+	decimal/fahrenheit
+	decimal/celcius
+
+ +##### ContentData(raw) +
+	[Fractional Number]
+
+ + + + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..5c2d1cf016b3885f6930543d57b744ea8c220a1a GIT binary patch literal 55616 zcmafaW0WS*vSoFbZJS-TZP!<}ZQEV8ZQHihW!tvx>6!c9%-lQoy;&DmfdT@8fB*sl68LLCKtKQ283+jS?^Q-bNq|NIAW8=eB==8_)^)r*{C^$z z{u;{v?IMYnO`JhmPq7|LA_@Iz75S9h~8`iX>QrjrmMeu{>hn4U;+$dor zz+`T8Q0f}p^Ao)LsYq74!W*)&dTnv}E8;7H*Zetclpo2zf_f>9>HT8;`O^F8;M%l@ z57Z8dk34kG-~Wg7n48qF2xwPp;SOUpd1}9Moir5$VSyf4gF)Mp-?`wO3;2x9gYj59oFwG>?Leva43@e(z{mjm0b*@OAYLC`O9q|s+FQLOE z!+*Y;%_0(6Sr<(cxE0c=lS&-FGBFGWd_R<5$vwHRJG=tB&Mi8@hq_U7@IMyVyKkOo6wgR(<% zQw1O!nnQl3T9QJ)Vh=(`cZM{nsEKChjbJhx@UQH+G>6p z;beBQ1L!3Zl>^&*?cSZjy$B3(1=Zyn~>@`!j%5v7IBRt6X`O)yDpVLS^9EqmHxBcisVG$TRwiip#ViN|4( zYn!Av841_Z@Ys=T7w#>RT&iXvNgDq3*d?$N(SznG^wR`x{%w<6^qj&|g})La;iD?`M=p>99p><39r9+e z`dNhQ&tol5)P#;x8{tT47i*blMHaDKqJs8!Pi*F{#)9%USFxTVMfMOy{mp2ZrLR40 z2a9?TJgFyqgx~|j0eA6SegKVk@|Pd|_6P$HvwTrLTK)Re`~%kg8o9`EAE1oAiY5Jgo=H}0*D?tSCn^=SIN~fvv453Ia(<1|s07aTVVtsRxY6+tT3589iQdi^ zC92D$ewm9O6FA*u*{Fe_=b`%q`pmFvAz@hfF@OC_${IPmD#QMpPNo0mE9U=Ch;k0L zZteokPG-h7PUeRCPPYG%H!WswC?cp7M|w42pbtwj!m_&4%hB6MdLQe&}@5-h~! zkOt;w0BbDc0H!RBw;1UeVckHpJ@^|j%FBZlC} zsm?nFOT$`F_i#1_gh4|n$rDe>0md6HvA=B%hlX*3Z%y@a&W>Rq`Fe(8smIgxTGb#8 zZ`->%h!?QCk>v*~{!qp=w?a*};Y**1uH`)OX`Gi+L%-d6{rV?@}MU#qfCU(!hLz;kWH=0A%W7E^pA zD;A%Jg5SsRe!O*0TyYkAHe&O9z*Ij-YA$%-rR?sc`xz_v{>x%xY39!8g#!Z0#03H( z{O=drKfb0cbx1F*5%q81xvTDy#rfUGw(fesh1!xiS2XT;7_wBi(Rh4i(!rR^9=C+- z+**b9;icxfq@<7}Y!PW-0rTW+A^$o*#ZKenSkxLB$Qi$%gJSL>x!jc86`GmGGhai9 zOHq~hxh}KqQHJeN$2U{M>qd*t8_e&lyCs69{bm1?KGTYoj=c0`rTg>pS6G&J4&)xp zLEGIHSTEjC0-s-@+e6o&w=h1sEWWvJUvezID1&exb$)ahF9`(6`?3KLyVL$|c)CjS zx(bsy87~n8TQNOKle(BM^>1I!2-CZ^{x6zdA}qeDBIdrfd-(n@Vjl^9zO1(%2pP9@ zKBc~ozr$+4ZfjmzEIzoth(k?pbI87=d5OfjVZ`Bn)J|urr8yJq`ol^>_VAl^P)>2r)s+*3z5d<3rP+-fniCkjmk=2hTYRa@t zCQcSxF&w%mHmA?!vaXnj7ZA$)te}ds+n8$2lH{NeD4mwk$>xZCBFhRy$8PE>q$wS`}8pI%45Y;Mg;HH+}Dp=PL)m77nKF68FggQ-l3iXlVZuM2BDrR8AQbK;bn1%jzahl0; zqz0(mNe;f~h8(fPzPKKf2qRsG8`+Ca)>|<&lw>KEqM&Lpnvig>69%YQpK6fx=8YFj zHKrfzy>(7h2OhUVasdwKY`praH?>qU0326-kiSyOU_Qh>ytIs^htlBA62xU6xg?*l z)&REdn*f9U3?u4$j-@ndD#D3l!viAUtw}i5*Vgd0Y6`^hHF5R=No7j8G-*$NWl%?t z`7Nilf_Yre@Oe}QT3z+jOUVgYtT_Ym3PS5(D>kDLLas8~F+5kW%~ZYppSrf1C$gL* zCVy}fWpZ3s%2rPL-E63^tA|8OdqKsZ4TH5fny47ENs1#^C`_NLg~H^uf3&bAj#fGV zDe&#Ot%_Vhj$}yBrC3J1Xqj>Y%&k{B?lhxKrtYy;^E9DkyNHk5#6`4cuP&V7S8ce9 zTUF5PQIRO7TT4P2a*4;M&hk;Q7&{(83hJe5BSm=9qt~;U)NTf=4uKUcnxC`;iPJeI zW#~w?HIOM+0j3ptB0{UU{^6_#B*Q2gs;1x^YFey(%DJHNWz@e_NEL?$fv?CDxG`jk zH|52WFdVsZR;n!Up;K;4E$|w4h>ZIN+@Z}EwFXI{w_`?5x+SJFY_e4J@|f8U08%dd z#Qsa9JLdO$jv)?4F@&z_^{Q($tG`?|9bzt8ZfH9P`epY`soPYqi1`oC3x&|@m{hc6 zs0R!t$g>sR@#SPfNV6Pf`a^E?q3QIaY30IO%yKjx#Njj@gro1YH2Q(0+7D7mM~c>C zk&_?9Ye>B%*MA+77$Pa!?G~5tm`=p{NaZsUsOgm6Yzclr_P^2)r(7r%n(0?4B#$e7 z!fP;+l)$)0kPbMk#WOjm07+e?{E)(v)2|Ijo{o1+Z8#8ET#=kcT*OwM#K68fSNo%< zvZFdHrOrr;>`zq!_welWh!X}=oN5+V01WJn7=;z5uo6l_$7wSNkXuh=8Y>`TjDbO< z!yF}c42&QWYXl}XaRr0uL?BNPXlGw=QpDUMo`v8pXzzG(=!G;t+mfCsg8 zJb9v&a)E!zg8|%9#U?SJqW!|oBHMsOu}U2Uwq8}RnWeUBJ>FtHKAhP~;&T4mn(9pB zu9jPnnnH0`8ywm-4OWV91y1GY$!qiQCOB04DzfDDFlNy}S{$Vg9o^AY!XHMueN<{y zYPo$cJZ6f7``tmlR5h8WUGm;G*i}ff!h`}L#ypFyV7iuca!J+C-4m@7*Pmj9>m+jh zlpWbud)8j9zvQ`8-oQF#u=4!uK4kMFh>qS_pZciyq3NC(dQ{577lr-!+HD*QO_zB9 z_Rv<#qB{AAEF8Gbr7xQly%nMA%oR`a-i7nJw95F3iH&IX5hhy3CCV5y>mK4)&5aC*12 zI`{(g%MHq<(ocY5+@OK-Qn-$%!Nl%AGCgHl>e8ogTgepIKOf3)WoaOkuRJQt%MN8W z=N-kW+FLw=1^}yN@*-_c>;0N{-B!aXy#O}`%_~Nk?{e|O=JmU8@+92Q-Y6h)>@omP=9i~ zi`krLQK^!=@2BH?-R83DyFkejZkhHJqV%^} zUa&K22zwz7b*@CQV6BQ9X*RB177VCVa{Z!Lf?*c~PwS~V3K{id1TB^WZh=aMqiws5)qWylK#^SG9!tqg3-)p_o(ABJsC!0;0v36;0tC= z!zMQ_@se(*`KkTxJ~$nIx$7ez&_2EI+{4=uI~dwKD$deb5?mwLJ~ema_0Z z6A8Q$1~=tY&l5_EBZ?nAvn$3hIExWo_ZH2R)tYPjxTH5mAw#3n-*sOMVjpUrdnj1DBm4G!J+Ke}a|oQN9f?!p-TcYej+(6FNh_A? zJ3C%AOjc<8%9SPJ)U(md`W5_pzYpLEMwK<_jgeg-VXSX1Nk1oX-{yHz z-;CW!^2ds%PH{L{#12WonyeK5A=`O@s0Uc%s!@22etgSZW!K<%0(FHC+5(BxsXW@e zAvMWiO~XSkmcz%-@s{|F76uFaBJ8L5H>nq6QM-8FsX08ug_=E)r#DC>d_!6Nr+rXe zzUt30Du_d0oSfX~u>qOVR*BmrPBwL@WhF^5+dHjWRB;kB$`m8|46efLBXLkiF|*W= zg|Hd(W}ZnlJLotYZCYKoL7YsQdLXZ!F`rLqLf8n$OZOyAzK`uKcbC-n0qoH!5-rh&k-`VADETKHxrhK<5C zhF0BB4azs%j~_q_HA#fYPO0r;YTlaa-eb)Le+!IeP>4S{b8&STp|Y0if*`-A&DQ$^ z-%=i73HvEMf_V6zSEF?G>G-Eqn+|k`0=q?(^|ZcqWsuLlMF2!E*8dDAx%)}y=lyMa z$Nn0_f8YN8g<4D>8IL3)GPf#dJYU@|NZqIX$;Lco?Qj=?W6J;D@pa`T=Yh z-ybpFyFr*3^gRt!9NnbSJWs2R-S?Y4+s~J8vfrPd_&_*)HBQ{&rW(2X>P-_CZU8Y9 z-32><7|wL*K+3{ZXE5}nn~t@NNT#Bc0F6kKI4pVwLrpU@C#T-&f{Vm}0h1N3#89@d zgcx3QyS;Pb?V*XAq;3(W&rjLBazm69XX;%^n6r}0!CR2zTU1!x#TypCr`yrII%wk8 z+g)fyQ!&xIX(*>?T}HYL^>wGC2E}euj{DD_RYKK@w=yF+44367X17)GP8DCmBK!xS zE{WRfQ(WB-v>DAr!{F2-cQKHIjIUnLk^D}7XcTI#HyjSiEX)BO^GBI9NjxojYfQza zWsX@GkLc7EqtP8(UM^cq5zP~{?j~*2T^Bb={@PV)DTkrP<9&hxDwN2@hEq~8(ZiF! z3FuQH_iHyQ_s-#EmAC5~K$j_$cw{+!T>dm#8`t%CYA+->rWp09jvXY`AJQ-l%C{SJ z1c~@<5*7$`1%b}n7ivSo(1(j8k+*Gek(m^rQ!+LPvb=xA@co<|(XDK+(tb46xJ4) zcw7w<0p3=Idb_FjQ@ttoyDmF?cT4JRGrX5xl&|ViA@Lg!vRR}p#$A?0=Qe+1)Mizl zn;!zhm`B&9t0GA67GF09t_ceE(bGdJ0mbXYrUoV2iuc3c69e;!%)xNOGG*?x*@5k( zh)snvm0s&gRq^{yyeE)>hk~w8)nTN`8HJRtY0~1f`f9ue%RV4~V(K*B;jFfJY4dBb z*BGFK`9M-tpWzayiD>p_`U(29f$R|V-qEB;+_4T939BPb=XRw~8n2cGiRi`o$2qm~ zN&5N7JU{L*QGM@lO8VI)fUA0D7bPrhV(GjJ$+@=dcE5vAVyCy6r&R#4D=GyoEVOnu z8``8q`PN-pEy>xiA_@+EN?EJpY<#}BhrsUJC0afQFx7-pBeLXR9Mr+#w@!wSNR7vxHy@r`!9MFecB4O zh9jye3iSzL0@t3)OZ=OxFjjyK#KSF|zz@K}-+HaY6gW+O{T6%Zky@gD$6SW)Jq;V0 zt&LAG*YFO^+=ULohZZW*=3>7YgND-!$2}2)Mt~c>JO3j6QiPC-*ayH2xBF)2m7+}# z`@m#q{J9r~Dr^eBgrF(l^#sOjlVNFgDs5NR*Xp;V*wr~HqBx7?qBUZ8w)%vIbhhe) zt4(#1S~c$Cq7b_A%wpuah1Qn(X9#obljoY)VUoK%OiQZ#Fa|@ZvGD0_oxR=vz{>U* znC(W7HaUDTc5F!T77GswL-jj7e0#83DH2+lS-T@_^SaWfROz9btt*5zDGck${}*njAwf}3hLqKGLTeV&5(8FC+IP>s;p{L@a~RyCu)MIa zs~vA?_JQ1^2Xc&^cjDq02tT_Z0gkElR0Aa$v@VHi+5*)1(@&}gEXxP5Xon?lxE@is z9sxd|h#w2&P5uHJxWgmtVZJv5w>cl2ALzri;r57qg){6`urTu(2}EI?D?##g=!Sbh z*L*>c9xN1a3CH$u7C~u_!g81`W|xp=54oZl9CM)&V9~ATCC-Q!yfKD@vp#2EKh0(S zgt~aJ^oq-TM0IBol!w1S2j7tJ8H7;SR7yn4-H}iz&U^*zW95HrHiT!H&E|rSlnCYr z7Y1|V7xebn=TFbkH;>WIH6H>8;0?HS#b6lCke9rSsH%3AM1#2U-^*NVhXEIDSFtE^ z=jOo1>j!c__Bub(R*dHyGa)@3h?!ls1&M)d2{?W5#1|M@6|ENYYa`X=2EA_oJUw=I zjQ)K6;C!@>^i7vdf`pBOjH>Ts$97}B=lkb07<&;&?f#cy3I0p5{1=?O*#8m$C_5TE zh}&8lOWWF7I@|pRC$G2;Sm#IJfhKW@^jk=jfM1MdJP(v2fIrYTc{;e5;5gsp`}X8-!{9{S1{h+)<@?+D13s^B zq9(1Pu(Dfl#&z|~qJGuGSWDT&u{sq|huEsbJhiqMUae}K*g+R(vG7P$p6g}w*eYWn zQ7luPl1@{vX?PMK%-IBt+N7TMn~GB z!Ldy^(2Mp{fw_0;<$dgHAv1gZgyJAx%}dA?jR=NPW1K`FkoY zNDgag#YWI6-a2#&_E9NMIE~gQ+*)i<>0c)dSRUMHpg!+AL;a;^u|M1jp#0b<+#14z z+#LuQ1jCyV_GNj#lHWG3e9P@H34~n0VgP#(SBX=v|RSuOiY>L87 z#KA{JDDj2EOBX^{`a;xQxHtY1?q5^B5?up1akjEPhi1-KUsK|J9XEBAbt%^F`t0I- zjRYYKI4OB7Zq3FqJFBZwbI=RuT~J|4tA8x)(v2yB^^+TYYJS>Et`_&yge##PuQ%0I z^|X!Vtof}`UuIxPjoH8kofw4u1pT5h`Ip}d8;l>WcG^qTe>@x63s#zoJiGmDM@_h= zo;8IZR`@AJRLnBNtatipUvL^(1P_a;q8P%&voqy#R!0(bNBTlV&*W9QU?kRV1B*~I zWvI?SNo2cB<7bgVY{F_CF$7z!02Qxfw-Ew#p!8PC#! z1sRfOl`d-Y@&=)l(Sl4CS=>fVvor5lYm61C!!iF3NMocKQHUYr0%QM}a4v2>rzPfM zUO}YRDb7-NEqW+p_;e0{Zi%0C$&B3CKx6|4BW`@`AwsxE?Vu}@Jm<3%T5O&05z+Yq zkK!QF(vlN}Rm}m_J+*W4`8i~R&`P0&5!;^@S#>7qkfb9wxFv@(wN@$k%2*sEwen$a zQnWymf+#Uyv)0lQVd?L1gpS}jMQZ(NHHCKRyu zjK|Zai0|N_)5iv)67(zDBCK4Ktm#ygP|0(m5tU`*AzR&{TSeSY8W=v5^=Ic`ahxM-LBWO+uoL~wxZmgcSJMUF9q%<%>jsvh9Dnp^_e>J_V=ySx4p?SF0Y zg4ZpZt@!h>WR76~P3_YchYOak7oOzR|`t+h!BbN}?zd zq+vMTt0!duALNWDwWVIA$O=%{lWJEj;5(QD()huhFL5=6x_=1h|5ESMW&S|*oxgF# z-0GRIb ziolwI13hJ-Rl(4Rj@*^=&Zz3vD$RX8bFWvBM{niz(%?z0gWNh_vUvpBDoa>-N=P4c zbw-XEJ@txIbc<`wC883;&yE4ayVh>+N($SJ01m}fumz!#!aOg*;y4Hl{V{b;&ux3& zBEmSq2jQ7#IbVm3TPBw?2vVN z0wzj|Y6EBS(V%Pb+@OPkMvEKHW~%DZk#u|A18pZMmCrjWh%7J4Ph>vG61 zRBgJ6w^8dNRg2*=K$Wvh$t>$Q^SMaIX*UpBG)0bqcvY%*by=$EfZAy{ZOA#^tB(D( zh}T(SZgdTj?bG9u+G{Avs5Yr1x=f3k7%K|eJp^>BHK#~dsG<&+=`mM@>kQ-cAJ2k) zT+Ht5liXdc^(aMi9su~{pJUhe)!^U&qn%mV6PS%lye+Iw5F@Xv8E zdR4#?iz+R4--iiHDQmQWfNre=iofAbF~1oGTa1Ce?hId~W^kPuN(5vhNx++ZLkn?l zUA7L~{0x|qA%%%P=8+-Ck{&2$UHn#OQncFS@uUVuE39c9o~#hl)v#!$X(X*4ban2c z{buYr9!`H2;6n73n^W3Vg(!gdBV7$e#v3qubWALaUEAf@`ava{UTx%2~VVQbEE(*Q8_ zv#me9i+0=QnY)$IT+@3vP1l9Wrne+MlZNGO6|zUVG+v&lm7Xw3P*+gS6e#6mVx~(w zyuaXogGTw4!!&P3oZ1|4oc_sGEa&m3Jsqy^lzUdJ^y8RlvUjDmbC^NZ0AmO-c*&m( zSI%4P9f|s!B#073b>Eet`T@J;3qY!NrABuUaED6M^=s-Q^2oZS`jVzuA z>g&g$!Tc>`u-Q9PmKu0SLu-X(tZeZ<%7F+$j3qOOftaoXO5=4!+P!%Cx0rNU+@E~{ zxCclYb~G(Ci%o{}4PC(Bu>TyX9slm5A^2Yi$$kCq-M#Jl)a2W9L-bq5%@Pw^ zh*iuuAz`x6N_rJ1LZ7J^MU9~}RYh+EVIVP+-62u+7IC%1p@;xmmQ`dGCx$QpnIUtK z0`++;Ddz7{_R^~KDh%_yo8WM$IQhcNOALCIGC$3_PtUs?Y44@Osw;OZ()Lk=(H&Vc zXjkHt+^1@M|J%Q&?4>;%T-i%#h|Tb1u;pO5rKst8(Cv2!3U{TRXdm&>fWTJG)n*q&wQPjRzg%pS1RO9}U0*C6fhUi&f#qoV`1{U<&mWKS<$oVFW>{&*$6)r6Rx)F4W zdUL8Mm_qNk6ycFVkI5F?V+cYFUch$92|8O^-Z1JC94GU+Nuk zA#n3Z1q4<6zRiv%W5`NGk*Ym{#0E~IA6*)H-=RmfWIY%mEC0? zSih7uchi`9-WkF2@z1ev6J_N~u;d$QfSNLMgPVpHZoh9oH-8D*;EhoCr~*kJ<|-VD z_jklPveOxWZq40E!SV@0XXy+~Vfn!7nZ1GXsn~U$>#u0d*f?RL9!NMlz^qxYmz|xt zz6A&MUAV#eD%^GcP#@5}QH5e7AV`}(N2#(3xpc!7dDmgu7C3TpgX5Z|$%Vu8=&SQI zdxUk*XS-#C^-cM*O>k}WD5K81e2ayyRA)R&5>KT1QL!T!%@}fw{>BsF+-pzu>;7{g z^CCSWfH;YtJGT@+An0Ded#zM9>UEFOdR_Xq zS~!5R*{p1Whq62ynHo|n$4p7&d|bal{iGsxAY?opi3R${)Zt*8YyOU!$TWMYXF?|i zPXYr}wJp#EH;keSG5WYJ*(~oiu#GDR>C4%-HpIWr7v`W`lzQN-lb?*vpoit z8FqJ)`LC4w8fO8Fu}AYV`awF2NLMS4$f+?=KisU4P6@#+_t)5WDz@f*qE|NG0*hwO z&gv^k^kC6Fg;5>Gr`Q46C{6>3F(p0QukG6NM07rxa&?)_C*eyU(jtli>9Zh#eUb(y zt9NbC-bp0>^m?i`?$aJUyBmF`N0zQ% zvF_;vLVI{tq%Ji%u*8s2p4iBirv*uD(?t~PEz$CfxVa=@R z^HQu6-+I9w>a35kX!P)TfnJDD!)j8!%38(vWNe9vK0{k*`FS$ABZ`rdwfQe@IGDki zssfXnsa6teKXCZUTd^qhhhUZ}>GG_>F0~LG7*<*x;8e39nb-0Bka(l)%+QZ_IVy3q zcmm2uKO0p)9|HGxk*e_$mX2?->&-MXe`=Fz3FRTFfM!$_y}G?{F9jmNgD+L%R`jM1 zIP-kb=3Hlsb35Q&qo(%Ja(LwQj>~!GI|Hgq65J9^A!ibChYB3kxLn@&=#pr}BwON0Q=e5;#sF8GGGuzx6O}z%u3l?jlKF&8Y#lUA)Cs6ZiW8DgOk|q z=YBPAMsO7AoAhWgnSKae2I7%7*Xk>#AyLX-InyBO?OD_^2^nI4#;G|tBvg3C0ldO0 z*`$g(q^es4VqXH2t~0-u^m5cfK8eECh3Rb2h1kW%%^8A!+ya3OHLw$8kHorx4(vJO zAlVu$nC>D{7i?7xDg3116Y2e+)Zb4FPAdZaX}qA!WW{$d?u+sK(iIKqOE-YM zH7y^hkny24==(1;qEacfFU{W{xSXhffC&DJV&oqw`u~WAl@=HIel>KC-mLs2ggFld zsSm-03=Jd^XNDA4i$vKqJ|e|TBc19bglw{)QL${Q(xlN?E;lPumO~;4w_McND6d+R zsc2p*&uRWd`wTDszTcWKiii1mNBrF7n&LQp$2Z<}zkv=8k2s6-^+#siy_K1`5R+n( z++5VOU^LDo(kt3ok?@$3drI`<%+SWcF*`CUWqAJxl3PAq!X|q{al;8%HfgxxM#2Vb zeBS756iU|BzB>bN2NP=AX&!{uZXS;|F`LLd9F^97UTMnNks_t7EPnjZF`2ocD2*u+ z?oKP{xXrD*AKGYGkZtlnvCuazg6g16ZAF{Nu%w+LCZ+v_*`0R$NK)tOh_c#cze;o$ z)kY(eZ5Viv<5zl1XfL(#GO|2FlXL#w3T?hpj3BZ&OAl^L!7@ zy;+iJWYQYP?$(`li_!|bfn!h~k#=v-#XXyjTLd+_txOqZZETqSEp>m+O0ji7MxZ*W zSdq+yqEmafrsLErZG8&;kH2kbCwluSa<@1yU3^Q#5HmW(hYVR0E6!4ZvH;Cr<$`qf zSvqRc`Pq_9b+xrtN3qLmds9;d7HdtlR!2NV$rZPCh6>(7f7M}>C^LeM_5^b$B~mn| z#)?`E=zeo9(9?{O_ko>51~h|c?8{F=2=_-o(-eRc z9p)o51krhCmff^U2oUi#$AG2p-*wSq8DZ(i!Jmu1wzD*)#%J&r)yZTq`3e|v4>EI- z=c|^$Qhv}lEyG@!{G~@}Wbx~vxTxwKoe9zn%5_Z^H$F1?JG_Kadc(G8#|@yaf2-4< zM1bdQF$b5R!W1f`j(S>Id;CHMzfpyjYEC_95VQ*$U3y5piVy=9Rdwg7g&)%#6;U%b2W}_VVdh}qPnM4FY9zFP(5eR zWuCEFox6e;COjs$1RV}IbpE0EV;}5IP}Oq|zcb*77PEDIZU{;@_;8*22{~JRvG~1t zc+ln^I+)Q*+Ha>(@=ra&L&a-kD;l$WEN;YL0q^GE8+})U_A_StHjX_gO{)N>tx4&F zRK?99!6JqktfeS-IsD@74yuq*aFJoV{5&K(W`6Oa2Qy0O5JG>O`zZ-p7vBGh!MxS;}}h6(96Wp`dci3DY?|B@1p8fVsDf$|0S zfE{WL5g3<9&{~yygYyR?jK!>;eZ2L#tpL2)H#89*b zycE?VViXbH7M}m33{#tI69PUPD=r)EVPTBku={Qh{ zKi*pht1jJ+yRhVE)1=Y()iS9j`FesMo$bjLSqPMF-i<42Hxl6%y7{#vw5YT(C}x0? z$rJU7fFmoiR&%b|Y*pG?7O&+Jb#Z%S8&%o~fc?S9c`Dwdnc4BJC7njo7?3bp#Yonz zPC>y`DVK~nzN^n}jB5RhE4N>LzhCZD#WQseohYXvqp5^%Ns!q^B z&8zQN(jgPS(2ty~g2t9!x9;Dao~lYVujG-QEq{vZp<1Nlp;oj#kFVsBnJssU^p-4% zKF_A?5sRmA>d*~^og-I95z$>T*K*33TGBPzs{OMoV2i+(P6K|95UwSj$Zn<@Rt(g%|iY z$SkSjYVJ)I<@S(kMQ6md{HxAa8S`^lXGV?ktLX!ngTVI~%WW+p#A#XTWaFWeBAl%U z&rVhve#Yse*h4BC4nrq7A1n>Rlf^ErbOceJC`o#fyCu@H;y)`E#a#)w)3eg^{Hw&E7);N5*6V+z%olvLj zp^aJ4`h*4L4ij)K+uYvdpil(Z{EO@u{BcMI&}5{ephilI%zCkBhBMCvOQT#zp|!18 zuNl=idd81|{FpGkt%ty=$fnZnWXxem!t4x{ zat@68CPmac(xYaOIeF}@O1j8O?2jbR!KkMSuix;L8x?m01}|bS2=&gsjg^t2O|+0{ zlzfu5r5_l4)py8uPb5~NHPG>!lYVynw;;T-gk1Pl6PQ39Mwgd2O+iHDB397H)2grN zHwbd>8i%GY>Pfy7;y5X7AN>qGLZVH>N_ZuJZ-`z9UA> zfyb$nbmPqxyF2F;UW}7`Cu>SS%0W6h^Wq5e{PWAjxlh=#Fq+6SiPa-L*551SZKX&w zc9TkPv4eao?kqomkZ#X%tA{`UIvf|_=Y7p~mHZKqO>i_;q4PrwVtUDTk?M7NCssa?Y4uxYrsXj!+k@`Cxl;&{NLs*6!R<6k9$Bq z%grLhxJ#G_j~ytJpiND8neLfvD0+xu>wa$-%5v;4;RYYM66PUab)c9ruUm%d{^s{# zTBBY??@^foRv9H}iEf{w_J%rV<%T1wv^`)Jm#snLTIifjgRkX``x2wV(D6(=VTLL4 zI-o}&5WuwBl~(XSLIn5~{cGWorl#z+=(vXuBXC#lp}SdW=_)~8Z(Vv!#3h2@pdA3d z{cIPYK@Ojc9(ph=H3T7;aY>(S3~iuIn05Puh^32WObj%hVN(Y{Ty?n?Cm#!kGNZFa zW6Ybz!tq|@erhtMo4xAus|H8V_c+XfE5mu|lYe|{$V3mKnb1~fqoFim;&_ZHN_=?t zysQwC4qO}rTi}k8_f=R&i27RdBB)@bTeV9Wcd}Rysvod}7I%ujwYbTI*cN7Kbp_hO z=eU521!#cx$0O@k9b$;pnCTRtLIzv){nVW6Ux1<0@te6`S5%Ew3{Z^9=lbL5$NFvd4eUtK?%zgmB;_I&p`)YtpN`2Im(?jPN<(7Ua_ZWJRF(CChv`(gHfWodK%+joy>8Vaa;H1w zIJ?!kA|x7V;4U1BNr(UrhfvjPii7YENLIm`LtnL9Sx z5E9TYaILoB2nSwDe|BVmrpLT43*dJ8;T@1l zJE)4LEzIE{IN}+Nvpo3=ZtV!U#D;rB@9OXYw^4QH+(52&pQEcZq&~u9bTg63ikW9! z=!_RjN2xO=F+bk>fSPhsjQA;)%M1My#34T`I7tUf>Q_L>DRa=>Eo(sapm>}}LUsN% zVw!C~a)xcca`G#g*Xqo>_uCJTz>LoWGSKOwp-tv`yvfqw{17t`9Z}U4o+q2JGP^&9 z(m}|d13XhYSnEm$_8vH-Lq$A^>oWUz1)bnv|AVn_0FwM$vYu&8+qUg$+qP}nwrykD zwmIF?wr$()X@33oz1@B9zi+?Th^nZnsES)rb@O*K^JL~ZH|pRRk$i0+ohh?Il)y&~ zQaq{}9YxPt5~_2|+r#{k#~SUhO6yFq)uBGtYMMg4h1qddg!`TGHocYROyNFJtYjNe z3oezNpq6%TP5V1g(?^5DMeKV|i6vdBq)aGJ)BRv;K(EL0_q7$h@s?BV$)w31*c(jd z{@hDGl3QdXxS=#?0y3KmPd4JL(q(>0ikTk6nt98ptq$6_M|qrPi)N>HY>wKFbnCKY z%0`~`9p)MDESQJ#A`_>@iL7qOCmCJ(p^>f+zqaMuDRk!z01Nd2A_W^D%~M73jTqC* zKu8u$$r({vP~TE8rPk?8RSjlRvG*BLF}ye~Su%s~rivmjg2F z24dhh6-1EQF(c>Z1E8DWY)Jw#9U#wR<@6J)3hjA&2qN$X%piJ4s={|>d-|Gzl~RNu z##iR(m;9TN3|zh+>HgTI&82iR>$YVoOq$a(2%l*2mNP(AsV=lR^>=tIP-R9Tw!BYnZROx`PN*JiNH>8bG}&@h0_v$yOTk#@1;Mh;-={ZU7e@JE(~@@y0AuETvsqQV@7hbKe2wiWk@QvV=Kz`%@$rN z_0Hadkl?7oEdp5eaaMqBm;#Xj^`fxNO^GQ9S3|Fb#%{lN;1b`~yxLGEcy8~!cz{!! z=7tS!I)Qq%w(t9sTSMWNhoV#f=l5+a{a=}--?S!rA0w}QF!_Eq>V4NbmYKV&^OndM z4WiLbqeC5+P@g_!_rs01AY6HwF7)$~%Ok^(NPD9I@fn5I?f$(rcOQjP+z?_|V0DiN zb}l0fy*el9E3Q7fVRKw$EIlb&T0fG~fDJZL7Qn8*a5{)vUblM)*)NTLf1ll$ zpQ^(0pkSTol`|t~`Y4wzl;%NRn>689mpQrW=SJ*rB;7}w zVHB?&sVa2%-q@ANA~v)FXb`?Nz8M1rHKiZB4xC9<{Q3T!XaS#fEk=sXI4IFMnlRqG+yaFw< zF{}7tcMjV04!-_FFD8(FtuOZx+|CjF@-xl6-{qSFF!r7L3yD()=*Ss6fT?lDhy(h$ zt#%F575$U(3-e2LsJd>ksuUZZ%=c}2dWvu8f!V%>z3gajZ!Dlk zm=0|(wKY`c?r$|pX6XVo6padb9{EH}px)jIsdHoqG^(XH(7}r^bRa8BC(%M+wtcB? z6G2%tui|Tx6C3*#RFgNZi9emm*v~txI}~xV4C`Ns)qEoczZ>j*r zqQCa5k90Gntl?EX!{iWh=1t$~jVoXjs&*jKu0Ay`^k)hC^v_y0xU~brMZ6PPcmt5$ z@_h`f#qnI$6BD(`#IR0PrITIV^~O{uo=)+Bi$oHA$G* zH0a^PRoeYD3jU_k%!rTFh)v#@cq`P3_y=6D(M~GBud;4 zCk$LuxPgJ5=8OEDlnU!R^4QDM4jGni}~C zy;t2E%Qy;A^bz_5HSb5pq{x{g59U!ReE?6ULOw58DJcJy;H?g*ofr(X7+8wF;*3{rx>j&27Syl6A~{|w{pHb zeFgu0E>OC81~6a9(2F13r7NZDGdQxR8T68&t`-BK zE>ZV0*0Ba9HkF_(AwfAds-r=|dA&p`G&B_zn5f9Zfrz9n#Rvso`x%u~SwE4SzYj!G zVQ0@jrLwbYP=awX$21Aq!I%M{x?|C`narFWhp4n;=>Sj!0_J!k7|A0;N4!+z%Oqlk z1>l=MHhw3bi1vT}1!}zR=6JOIYSm==qEN#7_fVsht?7SFCj=*2+Ro}B4}HR=D%%)F z?eHy=I#Qx(vvx)@Fc3?MT_@D))w@oOCRR5zRw7614#?(-nC?RH`r(bb{Zzn+VV0bm zJ93!(bfrDH;^p=IZkCH73f*GR8nDKoBo|!}($3^s*hV$c45Zu>6QCV(JhBW=3(Tpf z=4PT6@|s1Uz+U=zJXil3K(N6;ePhAJhCIo`%XDJYW@x#7Za);~`ANTvi$N4(Fy!K- z?CQ3KeEK64F0@ykv$-0oWCWhYI-5ZC1pDqui@B|+LVJmU`WJ=&C|{I_))TlREOc4* zSd%N=pJ_5$G5d^3XK+yj2UZasg2) zXMLtMp<5XWWfh-o@ywb*nCnGdK{&S{YI54Wh2|h}yZ})+NCM;~i9H@1GMCgYf`d5n zwOR(*EEkE4-V#R2+Rc>@cAEho+GAS2L!tzisLl${42Y=A7v}h;#@71_Gh2MV=hPr0_a% z0!={Fcv5^GwuEU^5rD|sP;+y<%5o9;#m>ssbtVR2g<420(I-@fSqfBVMv z?`>61-^q;M(b3r2z{=QxSjyH=-%99fpvb}8z}d;%_8$$J$qJg1Sp3KzlO_!nCn|g8 zzg8skdHNsfgkf8A7PWs;YBz_S$S%!hWQ@G>guCgS--P!!Ui9#%GQ#Jh?s!U-4)7ozR?i>JXHU$| zg0^vuti{!=N|kWorZNFX`dJgdphgic#(8sOBHQdBkY}Qzp3V%T{DFb{nGPgS;QwnH9B9;-Xhy{? z(QVwtzkn9I)vHEmjY!T3ifk1l5B?%%TgP#;CqG-?16lTz;S_mHOzu#MY0w}XuF{lk z*dt`2?&plYn(B>FFXo+fd&CS3q^hquSLVEn6TMAZ6e*WC{Q2e&U7l|)*W;^4l~|Q= zt+yFlLVqPz!I40}NHv zE2t1meCuGH%<`5iJ(~8ji#VD{?uhP%F(TnG#uRZW-V}1=N%ev&+Gd4v!0(f`2Ar-Y z)GO6eYj7S{T_vxV?5^%l6TF{ygS_9e2DXT>9caP~xq*~oE<5KkngGtsv)sdCC zaQH#kSL%c*gLj6tV)zE6SGq|0iX*DPV|I`byc9kn_tNQkPU%y<`rj zMC}lD<93=Oj+D6Y2GNMZb|m$^)RVdi`&0*}mxNy0BW#0iq!GGN2BGx5I0LS>I|4op z(6^xWULBr=QRpbxIJDK~?h;K#>LwQI4N<8V?%3>9I5l+e*yG zFOZTIM0c3(q?y9f7qDHKX|%zsUF%2zN9jDa7%AK*qrI5@z~IruFP+IJy7!s~TE%V3 z_PSSxXlr!FU|Za>G_JL>DD3KVZ7u&}6VWbwWmSg?5;MabycEB)JT(eK8wg`^wvw!Q zH5h24_E$2cuib&9>Ue&@%Cly}6YZN-oO_ei5#33VvqV%L*~ZehqMe;)m;$9)$HBsM zfJ96Hk8GJyWwQ0$iiGjwhxGgQX$sN8ij%XJzW`pxqgwW=79hgMOMnC|0Q@ed%Y~=_ z?OnjUB|5rS+R$Q-p)vvM(eFS+Qr{_w$?#Y;0Iknw3u(+wA=2?gPyl~NyYa3me{-Su zhH#8;01jEm%r#5g5oy-f&F>VA5TE_9=a0aO4!|gJpu470WIrfGo~v}HkF91m6qEG2 zK4j=7C?wWUMG$kYbIp^+@)<#ArZ$3k^EQxraLk0qav9TynuE7T79%MsBxl3|nRn?L zD&8kt6*RJB6*a7=5c57wp!pg)p6O?WHQarI{o9@3a32zQ3FH8cK@P!DZ?CPN_LtmC6U4F zlv8T2?sau&+(i@EL6+tvP^&=|aq3@QgL4 zOu6S3wSWeYtgCnKqg*H4ifIQlR4hd^n{F+3>h3;u_q~qw-Sh;4dYtp^VYymX12$`? z;V2_NiRt82RC=yC+aG?=t&a81!gso$hQUb)LM2D4Z{)S zI1S9f020mSm(Dn$&Rlj0UX}H@ zv={G+fFC>Sad0~8yB%62V(NB4Z|b%6%Co8j!>D(VyAvjFBP%gB+`b*&KnJ zU8s}&F+?iFKE(AT913mq;57|)q?ZrA&8YD3Hw*$yhkm;p5G6PNiO3VdFlnH-&U#JH zEX+y>hB(4$R<6k|pt0?$?8l@zeWk&1Y5tlbgs3540F>A@@rfvY;KdnVncEh@N6Mfi zY)8tFRY~Z?Qw!{@{sE~vQy)0&fKsJpj?yR`Yj+H5SDO1PBId3~d!yjh>FcI#Ug|^M z7-%>aeyQhL8Zmj1!O0D7A2pZE-$>+-6m<#`QX8(n)Fg>}l404xFmPR~at%$(h$hYD zoTzbxo`O{S{E}s8Mv6WviXMP}(YPZoL11xfd>bggPx;#&pFd;*#Yx%TtN1cp)MuHf z+Z*5CG_AFPwk624V9@&aL0;=@Ql=2h6aJoqWx|hPQQzdF{e7|fe(m){0==hk_!$ou zI|p_?kzdO9&d^GBS1u+$>JE-6Ov*o{mu@MF-?$r9V>i%;>>Fo~U`ac2hD*X}-gx*v z1&;@ey`rA0qNcD9-5;3_K&jg|qvn@m^+t?8(GTF0l#|({Zwp^5Ywik@bW9mN+5`MU zJ#_Ju|jtsq{tv)xA zY$5SnHgHj}c%qlQG72VS_(OSv;H~1GLUAegygT3T-J{<#h}))pk$FjfRQ+Kr%`2ZiI)@$96Nivh82#K@t>ze^H?R8wHii6Pxy z0o#T(lh=V>ZD6EXf0U}sG~nQ1dFI`bx;vivBkYSVkxXn?yx1aGxbUiNBawMGad;6? zm{zp?xqAoogt=I2H0g@826=7z^DmTTLB11byYvAO;ir|O0xmNN3Ec0w%yHO({-%q(go%?_X{LP?=E1uXoQgrEGOfL1?~ zI%uPHC23dn-RC@UPs;mxq6cFr{UrgG@e3ONEL^SoxFm%kE^LBhe_D6+Ia+u0J=)BC zf8FB!0J$dYg33jb2SxfmkB|8qeN&De!%r5|@H@GiqReK(YEpnXC;-v~*o<#JmYuze zW}p-K=9?0=*fZyYTE7A}?QR6}m_vMPK!r~y*6%My)d;x4R?-=~MMLC_02KejX9q6= z4sUB4AD0+H4ulSYz4;6mL8uaD07eXFvpy*i5X@dmx--+9`ur@rcJ5<L#s%nq3MRi4Dpr;#28}dl36M{MkVs4+Fm3Pjo5qSV)h}i(2^$Ty|<7N z>*LiBzFKH30D!$@n^3B@HYI_V1?yM(G$2Ml{oZ}?frfPU+{i|dHQOP^M0N2#NN_$+ zs*E=MXUOd=$Z2F4jSA^XIW=?KN=w6{_vJ4f(ZYhLxvFtPozPJv9k%7+z!Zj+_0|HC zMU0(8`8c`Sa=%e$|Mu2+CT22Ifbac@7Vn*he`|6Bl81j`44IRcTu8aw_Y%;I$Hnyd zdWz~I!tkWuGZx4Yjof(?jM;exFlUsrj5qO=@2F;56&^gM9D^ZUQ!6TMMUw19zslEu zwB^^D&nG96Y+Qwbvgk?Zmkn9%d{+V;DGKmBE(yBWX6H#wbaAm&O1U^ zS4YS7j2!1LDC6|>cfdQa`}_^satOz6vc$BfFIG07LoU^IhVMS_u+N=|QCJao0{F>p z-^UkM)ODJW9#9*o;?LPCRV1y~k9B`&U)jbTdvuxG&2%!n_Z&udT=0mb@e;tZ$_l3bj6d0K2;Ya!&)q`A${SmdG_*4WfjubB)Mn+vaLV+)L5$yD zYSTGxpVok&fJDG9iS8#oMN{vQneO|W{Y_xL2Hhb%YhQJgq7j~X7?bcA|B||C?R=Eo z!z;=sSeKiw4mM$Qm>|aIP3nw36Tbh6Eml?hL#&PlR5xf9^vQGN6J8op1dpLfwFg}p zlqYx$610Zf?=vCbB_^~~(e4IMic7C}X(L6~AjDp^;|=d$`=!gd%iwCi5E9<6Y~z0! zX8p$qprEadiMgq>gZ_V~n$d~YUqqqsL#BE6t9ufXIUrs@DCTfGg^-Yh5Ms(wD1xAf zTX8g52V!jr9TlWLl+whcUDv?Rc~JmYs3haeG*UnV;4bI=;__i?OSk)bF3=c9;qTdP zeW1exJwD+;Q3yAw9j_42Zj9nuvs%qGF=6I@($2Ue(a9QGRMZTd4ZAlxbT5W~7(alP1u<^YY!c3B7QV z@jm$vn34XnA6Gh1I)NBgTmgmR=O1PKp#dT*mYDPRZ=}~X3B8}H*e_;;BHlr$FO}Eq zJ9oWk0y#h;N1~ho724x~d)A4Z-{V%F6#e5?Z^(`GGC}sYp5%DKnnB+i-NWxwL-CuF+^JWNl`t@VbXZ{K3#aIX+h9-{T*+t(b0BM&MymW9AA*{p^&-9 zWpWQ?*z(Yw!y%AoeoYS|E!(3IlLksr@?Z9Hqlig?Q4|cGe;0rg#FC}tXTmTNfpE}; z$sfUYEG@hLHUb$(K{A{R%~%6MQN|Bu949`f#H6YC*E(p3lBBKcx z-~Bsd6^QsKzB0)$FteBf*b3i7CN4hccSa-&lfQz4qHm>eC|_X!_E#?=`M(bZ{$cvU zZpMbr|4omp`s9mrgz@>4=Fk3~8Y7q$G{T@?oE0<(I91_t+U}xYlT{c&6}zPAE8ikT z3DP!l#>}i!A(eGT+@;fWdK#(~CTkwjs?*i4SJVBuNB2$6!bCRmcm6AnpHHvnN8G<| zuh4YCYC%5}Zo;BO1>L0hQ8p>}tRVx~O89!${_NXhT!HUoGj0}bLvL2)qRNt|g*q~B z7U&U7E+8Ixy1U`QT^&W@ZSRN|`_Ko$-Mk^^c%`YzhF(KY9l5))1jSyz$&>mWJHZzHt0Jje%BQFxEV}C00{|qo5_Hz7c!FlJ|T(JD^0*yjkDm zL}4S%JU(mBV|3G2jVWU>DX413;d+h0C3{g3v|U8cUj`tZL37Sf@1d*jpwt4^B)`bK zZdlwnPB6jfc7rIKsldW81$C$a9BukX%=V}yPnaBz|i6(h>S)+Bn44@i8RtBZf0XetH&kAb?iAL zD%Ge{>Jo3sy2hgrD?15PM}X_)(6$LV`&t*D`IP)m}bzM)+x-xRJ zavhA)>hu2cD;LUTvN38FEtB94ee|~lIvk~3MBPzmTsN|7V}Kzi!h&za#NyY zX^0BnB+lfBuW!oR#8G&S#Er2bCVtA@5FI`Q+a-e?G)LhzW_chWN-ZQmjtR

eWu-UOPu^G}|k=o=;ffg>8|Z*qev7qS&oqA7%Z{4Ezb!t$f3& z^NuT8CSNp`VHScyikB1YO{BgaBVJR&>dNIEEBwYkfOkWN;(I8CJ|vIfD}STN z{097)R9iC@6($s$#dsb*4BXBx7 zb{6S2O}QUk>upEfij9C2tjqWy7%%V@Xfpe)vo6}PG+hmuY1Tc}peynUJLLmm)8pshG zb}HWl^|sOPtYk)CD-7{L+l(=F zOp}fX8)|n{JDa&9uI!*@jh^^9qP&SbZ(xxDhR)y|bjnn|K3MeR3gl6xcvh9uqzb#K zYkVjnK$;lUky~??mcqN-)d5~mk{wXhrf^<)!Jjqc zG~hX0P_@KvOKwV=X9H&KR3GnP3U)DfqafBt$e10}iuVRFBXx@uBQ)sn0J%%c<;R+! zQz;ETTVa+ma>+VF%U43w?_F6s0=x@N2(oisjA7LUOM<$|6iE|$WcO67W|KY8JUV_# zg7P9K3Yo-c*;EmbsqT!M4(WT`%9uk+s9Em-yB0bE{B%F4X<8fT!%4??vezaJ(wJhj zfOb%wKfkY3RU}7^FRq`UEbB-#A-%7)NJQwQd1As=!$u#~2vQ*CE~qp`u=_kL<`{OL zk>753UqJVx1-4~+d@(pnX-i zV4&=eRWbJ)9YEGMV53poXpv$vd@^yd05z$$@i5J7%>gYKBx?mR2qGv&BPn!tE-_aW zg*C!Z&!B zH>3J16dTJC(@M0*kIc}Jn}jf=f*agba|!HVm|^@+7A?V>Woo!$SJko*Jv1mu>;d}z z^vF{3u5Mvo_94`4kq2&R2`32oyoWc2lJco3`Ls0Ew4E7*AdiMbn^LCV%7%mU)hr4S3UVJjDLUoIKRQ)gm?^{1Z}OYzd$1?a~tEY ztjXmIM*2_qC|OC{7V%430T?RsY?ZLN$w!bkDOQ0}wiq69){Kdu3SqW?NMC))S}zq^ zu)w!>E1!;OrXO!RmT?m&PA;YKUjJy5-Seu=@o;m4*Vp$0OipBl4~Ub)1xBdWkZ47=UkJd$`Z}O8ZbpGN$i_WtY^00`S8=EHG#Ff{&MU1L(^wYjTchB zMTK%1LZ(eLLP($0UR2JVLaL|C2~IFbWirNjp|^=Fl48~Sp9zNOCZ@t&;;^avfN(NpNfq}~VYA{q%yjHo4D>JB>XEv(~Z!`1~SoY=9v zTq;hrjObE_h)cmHXLJ>LC_&XQ2BgGfV}e#v}ZF}iF97bG`Nog&O+SA`2zsn%bbB309}I$ zYi;vW$k@fC^muYBL?XB#CBuhC&^H)F4E&vw(5Q^PF{7~}(b&lF4^%DQzL0(BVk?lM zTHXTo4?Ps|dRICEiux#y77_RF8?5!1D-*h5UY&gRY`WO|V`xxB{f{DHzBwvt1W==r zdfAUyd({^*>Y7lObr;_fO zxDDw7X^dO`n!PLqHZ`by0h#BJ-@bAFPs{yJQ~Ylj^M5zWsxO_WFHG}8hH>OK{Q)9` zSRP94d{AM(q-2x0yhK@aNMv!qGA5@~2tB;X?l{Pf?DM5Y*QK`{mGA? zjx;gwnR~#Nep12dFk<^@-U{`&`P1Z}Z3T2~m8^J&7y}GaMElsTXg|GqfF3>E#HG=j zMt;6hfbfjHSQ&pN9(AT8q$FLKXo`N(WNHDY!K6;JrHZCO&ISBdX`g8sXvIf?|8 zX$-W^ut!FhBxY|+R49o44IgWHt}$1BuE|6|kvn1OR#zhyrw}4H*~cpmFk%K(CTGYc zNkJ8L$eS;UYDa=ZHWZy`rO`!w0oIcgZnK&xC|93#nHvfb^n1xgxf{$LB`H1ao+OGb zKG_}>N-RHSqL(RBdlc7J-Z$Gaay`wEGJ_u-lo88{`aQ*+T~+x(H5j?Q{uRA~>2R+} zB+{wM2m?$->unwg8-GaFrG%ZmoHEceOj{W21)Mi2lAfT)EQuNVo+Do%nHPuq7Ttt7 z%^6J5Yo64dH671tOUrA7I2hL@HKZq;S#Ejxt;*m-l*pPj?=i`=E~FAXAb#QH+a}-% z#3u^pFlg%p{hGiIp>05T$RiE*V7bPXtkz(G<+^E}Risi6F!R~Mbf(Qz*<@2&F#vDr zaL#!8!&ughWxjA(o9xtK{BzzYwm_z2t*c>2jI)c0-xo8ahnEqZ&K;8uF*!Hg0?Gd* z=eJK`FkAr>7$_i$;kq3Ks5NNJkNBnw|1f-&Ys56c9Y@tdM3VTTuXOCbWqye9va6+ZSeF0eh} zYb^ct&4lQTfNZ3M3(9?{;s><(zq%hza7zcxlZ+`F8J*>%4wq8s$cC6Z=F@ zhbvdv;n$%vEI$B~B)Q&LkTse!8Vt};7Szv2@YB!_Ztp@JA>rc(#R1`EZcIdE+JiI% zC2!hgYt+~@%xU?;ir+g92W`*j z3`@S;I6@2rO28zqj&SWO^CvA5MeNEhBF+8-U0O0Q1Co=I^WvPl%#}UFDMBVl z5iXV@d|`QTa$>iw;m$^}6JeuW zjr;{)S2TfK0Q%xgHvONSJb#NA|LOmg{U=k;R?&1tQbylMEY4<1*9mJh&(qo`G#9{X zYRs)#*PtEHnO;PV0G~6G`ca%tpKgb6<@)xc^SQY58lTo*S$*sv5w7bG+8YLKYU`8{ zNBVlvgaDu7icvyf;N&%42z2L4(rR<*Jd48X8Jnw zN>!R$%MZ@~Xu9jH?$2Se&I|ZcW>!26BJP?H7og0hT(S`nXh6{sR36O^7%v=31T+eL z)~BeC)15v>1m#(LN>OEwYFG?TE0_z)MrT%3SkMBBjvCd6!uD+03Jz#!s#Y~b1jf>S z&Rz5&8rbLj5!Y;(Hx|UY(2aw~W(8!3q3D}LRE%XX(@h5TnP@PhDoLVQx;6|r^+Bvs zaR55cR%Db9hZ<<|I%dDkone+8Sq7dqPOMnGoHk~-R*#a8w$c)`>4U`k+o?2|E>Sd4 zZ0ZVT{95pY$qKJ54K}3JB!(WcES>F+x56oJBRg))tMJ^#Qc(2rVcd5add=Us6vpBNkIg9b#ulk%!XBU zV^fH1uY(rGIAiFew|z#MM!qsVv%ZNb#why9%9In4Kj-hDYtMdirWLFzn~de!nnH(V zv0>I3;X#N)bo1$dFzqo(tzmvqNUKraAz~?)OSv42MeM!OYu;2VKn2-s7#fucX`|l~ zplxtG1Pgk#(;V=`P_PZ`MV{Bt4$a7;aLvG@KQo%E=;7ZO&Ws-r@XL+AhnPn>PAKc7 zQ_iQ4mXa-a4)QS>cJzt_j;AjuVCp8g^|dIV=DI0>v-f_|w5YWAX61lNBjZEZax3aV znher(j)f+a9_s8n#|u=kj0(unR1P-*L7`{F28xv054|#DMh}q=@rs@-fbyf(2+52L zN>hn3v!I~%jfOV=j(@xLOsl$Jv-+yR5{3pX)$rIdDarl7(C3)})P`QoHN|y<<2n;` zJ0UrF=Zv}d=F(Uj}~Yv9(@1pqUSRa5_bB*AvQ|Z-6YZ*N%p(U z<;Bpqr9iEBe^LFF!t{1UnRtaH-9=@p35fMQJ~1^&)(2D|^&z?m z855r&diVS6}jmt2)A7LZDiv;&Ys6@W5P{JHY!!n7W zvj3(2{1R9Y=TJ|{^2DK&be*ZaMiRHw>WVI^701fC) zAp1?8?oiU%Faj?Qhou6S^d11_7@tEK-XQ~%q!!7hha-Im^>NcRF7OH7s{IO7arZQ{ zE8n?2><7*!*lH}~usWPWZ}2&M+)VQo7C!AWJSQc>8g_r-P`N&uybK5)p$5_o;+58Q z-Ux2l<3i|hxqqur*qAfHq=)?GDchq}ShV#m6&w|mi~ar~`EO_S=fb~<}66U>5i7$H#m~wR;L~4yHL2R&;L*u7-SPdHxLS&Iy76q$2j#Pe)$WulRiCICG*t+ zeehM8`!{**KRL{Q{8WCEFLXu3+`-XF(b?c1Z~wg?c0lD!21y?NLq?O$STk3NzmrHM zsCgQS5I+nxDH0iyU;KKjzS24GJmG?{D`08|N-v+Egy92lBku)fnAM<}tELA_U`)xKYb=pq|hejMCT1-rg0Edt6(*E9l9WCKI1a=@c99swp2t6Tx zFHy`8Hb#iXS(8c>F~({`NV@F4w0lu5X;MH6I$&|h*qfx{~DJ*h5e|61t1QP}tZEIcjC%!Fa)omJTfpX%aI+OD*Y(l|xc0$1Zip;4rx; zV=qI!5tSuXG7h?jLR)pBEx!B15HCoVycD&Z2dlqN*MFQDb!|yi0j~JciNC!>){~ zQQgmZvc}0l$XB0VIWdg&ShDTbTkArryp3x)T8%ulR;Z?6APx{JZyUm=LC-ACkFm`6 z(x7zm5ULIU-xGi*V6x|eF~CN`PUM%`!4S;Uv_J>b#&OT9IT=jx5#nydC4=0htcDme zDUH*Hk-`Jsa>&Z<7zJ{K4AZE1BVW%zk&MZ^lHyj8mWmk|Pq8WwHROz0Kwj-AFqvR)H2gDN*6dzVk>R3@_CV zw3Z@6s^73xW)XY->AFwUlk^4Q=hXE;ckW=|RcZFchyOM0vqBW{2l*QR#v^SZNnT6j zZv|?ZO1-C_wLWVuYORQryj29JA; zS4BsxfVl@X!W{!2GkG9fL4}58Srv{$-GYngg>JuHz!7ZPQbfIQr4@6ZC4T$`;Vr@t zD#-uJ8A!kSM*gA&^6yWi|F}&59^*Rx{qn3z{(JYxrzg!X2b#uGd>&O0e=0k_2*N?3 zYXV{v={ONL{rW~z_FtFj7kSSJZ?s);LL@W&aND7blR8rlvkAb48RwJZlOHA~t~RfC zOD%ZcOzhYEV&s9%qns0&ste5U!^MFWYn`Od()5RwIz6%@Ek+Pn`s79unJY-$7n-Uf z&eUYvtd)f7h7zG_hDiFC!psCg#q&0c=GHKOik~$$>$Fw*k z;G)HS$IR)Cu72HH|JjeeauX;U6IgZ_IfxFCE_bGPAU25$!j8Etsl0Rk@R`$jXuHo8 z3Hhj-rTR$Gq(x)4Tu6;6rHQhoCvL4Q+h0Y+@Zdt=KTb0~wj7-(Z9G%J+aQu05@k6JHeCC|YRFWGdDCV}ja;-yl^9<`>f=AwOqML1a~* z9@cQYb?!+Fmkf}9VQrL8$uyq8k(r8)#;##xG9lJ-B)Fg@15&To(@xgk9SP*bkHlxiy8I*wJQylh(+9X~H-Is!g&C!q*eIYuhl&fS&|w)dAzXBdGJ&Mp$+8D| zZaD<+RtjI90QT{R0YLk6_dm=GfCg>7;$ zlyLsNYf@MfLH<}ott5)t2CXiQos zFLt^`%ygB2Vy^I$W3J_Rt4olRn~Gh}AW(`F@LsUN{d$sR%bU&3;rsD=2KCL+4c`zv zlI%D>9-)U&R3;>d1Vdd5b{DeR!HXDm44Vq*u?`wziLLsFUEp4El;*S0;I~D#TgG0s zBXYZS{o|Hy0A?LVNS)V4c_CFwyYj-E#)4SQq9yaf`Y2Yhk7yHSdos~|fImZG5_3~~o<@jTOH@Mc7`*xn-aO5F zyFT-|LBsm(NbWkL^oB-Nd31djBaYebhIGXhsJyn~`SQ6_4>{fqIjRp#Vb|~+Qi}Mdz!Zsw= zz?5L%F{c{;Cv3Q8ab>dsHp)z`DEKHf%e9sT(aE6$az?A}3P`Lm(~W$8Jr=;d8#?dm_cmv>2673NqAOenze z=&QW`?TQAu5~LzFLJvaJ zaBU3mQFtl5z?4XQDBWNPaH4y)McRpX#$(3o5Nx@hVoOYOL&-P+gqS1cQ~J;~1roGH zVzi46?FaI@w-MJ0Y7BuAg*3;D%?<_OGsB3)c|^s3A{UoAOLP8scn`!5?MFa|^cTvq z#%bYG3m3UO9(sH@LyK9-LSnlVcm#5^NRs9BXFtRN9kBY2mPO|@b7K#IH{B{=0W06) zl|s#cIYcreZ5p3j>@Ly@35wr-q8z5f9=R42IsII=->1stLo@Q%VooDvg@*K(H@*5g zUPS&cM~k4oqp`S+qp^*nxzm^0mg3h8ppEHQ@cXyQ=YKV-6)FB*$KCa{POe2^EHr{J zOxcVd)s3Mzs8m`iV?MSp=qV59blW9$+$P+2;PZDRUD~sr*CQUr&EDiCSfH@wuHez+ z`d5p(r;I7D@8>nbZ&DVhT6qe+accH;<}q$8Nzz|d1twqW?UV%FMP4Y@NQ`3(+5*i8 zP9*yIMP7frrneG3M9 zf>GsjA!O#Bifr5np-H~9lR(>#9vhE6W-r`EjjeQ_wdWp+rt{{L5t5t(Ho|4O24@}4 z_^=_CkbI`3;~sXTnnsv=^b3J}`;IYyvb1gM>#J9{$l#Zd*W!;meMn&yXO7x`Epx_Y zm-1wlu~@Ii_7D}>%tzlXW;zQT=uQXSG@t$<#6-W*^vy7Vr2TCpnix@7!_|aNXEnN<-m?Oq;DpN*x6f>w za1Wa5entFEDtA0SD%iZv#3{wl-S`0{{i3a9cmgNW`!TH{J*~{@|5f%CKy@uk*8~af zt_d34U4y&3y9IZ5cXxLQ?(XjH5?q3Z0KxK~y!-CUyWG6{<)5lkhbox0HnV&7^zNBn zjc|?X!Y=63(Vg>#&Wx%=LUr5{i@~OdzT#?P8xu#P*I_?Jl7xM4dq)4vi}3Wj_c=XI zSbc)@Q2Et4=(nBDU{aD(F&*%Ix!53_^0`+nOFk)}*34#b0Egffld|t_RV91}S0m)0 zap{cQDWzW$geKzYMcDZDAw480!1e1!1Onpv9fK9Ov~sfi!~OeXb(FW)wKx335nNY! za6*~K{k~=pw`~3z!Uq%?MMzSl#s%rZM{gzB7nB*A83XIGyNbi|H8X>a5i?}Rs+z^; z2iXrmK4|eDOu@{MdS+?@(!-Ar4P4?H_yjTEMqm7`rbV4P275(-#TW##v#Dt14Yn9UB-Sg3`WmL0+H~N;iC`Mg%pBl?1AAOfZ&e; z*G=dR>=h_Mz@i;lrGpIOQwezI=S=R8#);d*;G8I(39ZZGIpWU)y?qew(t!j23B9fD z?Uo?-Gx3}6r8u1fUy!u)7LthD2(}boE#uhO&mKBau8W8`XV7vO>zb^ZVWiH-DOjl2 zf~^o1CYVU8eBdmpAB=T%i(=y}!@3N%G-*{BT_|f=egqtucEtjRJJhSf)tiBhpPDpgzOpG12UgvOFnab&16Zn^2ZHjs)pbd&W1jpx%%EXmE^ zdn#R73^BHp3w%&v!0~azw(Fg*TT*~5#dJw%-UdxX&^^(~V&C4hBpc+bPcLRZizWlc zjR;$4X3Sw*Rp4-o+a4$cUmrz05RucTNoXRINYG*DPpzM&;d1GNHFiyl(_x#wspacQ zL)wVFXz2Rh0k5i>?Ao5zEVzT)R(4Pjmjv5pzPrav{T(bgr|CM4jH1wDp6z*_jnN{V ziN56m1T)PBp1%`OCFYcJJ+T09`=&=Y$Z#!0l0J2sIuGQtAr>dLfq5S;{XGJzNk@a^ zk^eHlC4Gch`t+ue3RviiOlhz81CD9z~d|n5;A>AGtkZMUQ#f>5M14f2d}2 z8<*LNZvYVob!p9lbmb!0jt)xn6O&JS)`}7v}j+csS3e;&Awj zoNyjnqLzC(QQ;!jvEYUTy73t_%16p)qMb?ihbU{y$i?=a7@JJoXS!#CE#y}PGMK~3 zeeqqmo7G-W_S97s2eed^erB2qeh4P25)RO1>MH7ai5cZJTEevogLNii=oKG)0(&f` z&hh8cO{of0;6KiNWZ6q$cO(1)9r{`}Q&%p*O0W7N--sw3Us;)EJgB)6iSOg(9p_mc zRw{M^qf|?rs2wGPtjVKTOMAfQ+ZNNkb$Ok0;Pe=dNc7__TPCzw^H$5J0l4D z%p(_0w(oLmn0)YDwrcFsc*8q)J@ORBRoZ54GkJpxSvnagp|8H5sxB|ZKirp%_mQt_ z81+*Y8{0Oy!r8Gmih48VuRPwoO$dDW@h53$C)duL4_(osryhwZSj%~KsZ?2n?b`Z* z#C8aMdZxYmCWSM{mFNw1ov*W}Dl=%GQpp90qgZ{(T}GOS8#>sbiEU;zYvA?=wbD5g+ahbd1#s`=| zV6&f#ofJC261~Ua6>0M$w?V1j##jh-lBJ2vQ%&z`7pO%frhLP-1l)wMs=3Q&?oth1 zefkPr@3Z(&OL@~|<0X-)?!AdK)ShtFJ;84G2(izo3cCuKc{>`+aDoziL z6gLTL(=RYeD7x^FYA%sPXswOKhVa4i(S4>h&mLvS##6-H?w8q!B<8Alk>nQEwUG)SFXK zETfcTwi=R3!ck|hSM`|-^N3NWLav&UTO{a9=&Tuz-Kq963;XaRFq#-1R18fi^Gb-; zVO>Q{Oe<^b0WA!hkBi9iJp3`kGwacXX2CVQ0xQn@Y2OhrM%e4)Ea7Y*Df$dY2BpbL zv$kX}*#`R1uNA(7lk_FAk~{~9Z*Si5xd(WKQdD&I?8Y^cK|9H&huMU1I(251D7(LL z+){kRc=ALmD;#SH#YJ+|7EJL6e~w!D7_IrK5Q=1DCulUcN(3j`+D_a|GP}?KYx}V+ zx_vLTYCLb0C?h;e<{K0`)-|-qfM16y{mnfX(GGs2H-;-lRMXyb@kiY^D;i1haxoEk zsQ7C_o2wv?;3KS_0w^G5#Qgf*>u)3bT<3kGQL-z#YiN9QH7<(oDdNlSdeHD zQJN-U*_wJM_cU}1YOH=m>DW~{%MAPxL;gLdU6S5xLb$gJt#4c2KYaEaL8ORWf=^(l z-2`8^J;&YG@vb9em%s~QpU)gG@24BQD69;*y&-#0NBkxumqg#YYomd2tyo0NGCr8N z5<5-E%utH?Ixt!(Y4x>zIz4R^9SABVMpLl(>oXnBNWs8w&xygh_e4*I$y_cVm?W-^ ze!9mPy^vTLRclXRGf$>g%Y{(#Bbm2xxr_Mrsvd7ci|X|`qGe5=54Zt2Tb)N zlykxE&re1ny+O7g#`6e_zyjVjRi5!DeTvSJ9^BJqQ*ovJ%?dkaQl!8r{F`@KuDEJB3#ho5 zmT$A&L=?}gF+!YACb=%Y@}8{SnhaGCHRmmuAh{LxAn0sg#R6P_^cJ-9)+-{YU@<^- zlYnH&^;mLVYE+tyjFj4gaAPCD4CnwP75BBXA`O*H(ULnYD!7K14C!kGL_&hak)udZ zkQN8)EAh&9I|TY~F{Z6mBv7sz3?<^o(#(NXGL898S3yZPTaT|CzZpZ~pK~*9Zcf2F zgwuG)jy^OTZD`|wf&bEdq4Vt$ir-+qM7BosXvu`>W1;iFN7yTvcpN_#at)Q4n+(Jh zYX1A-24l9H5jgY?wdEbW{(6U1=Kc?Utren80bP`K?J0+v@{-RDA7Y8yJYafdI<7-I z_XA!xeh#R4N7>rJ_?(VECa6iWhMJ$qdK0Ms27xG&$gLAy(|SO7_M|AH`fIY)1FGDp zlsLwIDshDU;*n`dF@8vV;B4~jRFpiHrJhQ6TcEm%OjWTi+KmE7+X{19 z>e!sg0--lE2(S0tK}zD&ov-{6bMUc%dNFIn{2^vjXWlt>+uxw#d)T6HNk6MjsfN~4 zDlq#Jjp_!wn}$wfs!f8NX3Rk#9)Q6-jD;D9D=1{$`3?o~caZjXU*U32^JkJ$ZzJ_% zQWNfcImxb!AV1DRBq`-qTV@g1#BT>TlvktYOBviCY!13Bv?_hGYDK}MINVi;pg)V- z($Bx1Tj`c?1I3pYg+i_cvFtcQ$SV9%%9QBPg&8R~Ig$eL+xKZY!C=;M1|r)$&9J2x z;l^a*Ph+isNl*%y1T4SviuK1Nco_spQ25v5-}7u?T9zHB5~{-+W*y3p{yjn{1obqf zYL`J^Uz8zZZN8c4Dxy~)k3Ws)E5eYi+V2C!+7Sm0uu{xq)S8o{9uszFTnE>lPhY=5 zdke-B8_*KwWOd%tQs_zf0x9+YixHp+Qi_V$aYVc$P-1mg?2|_{BUr$6WtLdIX2FaF zGmPRTrdIz)DNE)j*_>b9E}sp*(1-16}u za`dgT`KtA3;+e~9{KV48RT=CGPaVt;>-35}%nlFUMK0y7nOjoYds7&Ft~#>0$^ciZ zM}!J5Mz{&|&lyG^bnmh?YtR z*Z5EfDxkrI{QS#Iq752aiA~V)DRlC*2jlA|nCU!@CJwxO#<=j6ssn;muv zhBT9~35VtwsoSLf*(7vl&{u7d_K_CSBMbzr zzyjt&V5O#8VswCRK3AvVbS7U5(KvTPyUc0BhQ}wy0z3LjcdqH8`6F3!`)b3(mOSxL z>i4f8xor(#V+&#ph~ycJMcj#qeehjxt=~Na>dx#Tcq6Xi4?BnDeu5WBBxt603*BY& zZ#;o1kv?qpZjwK-E{8r4v1@g*lwb|8w@oR3BTDcbiGKs)a>Fpxfzh&b ziQANuJ_tNHdx;a*JeCo^RkGC$(TXS;jnxk=dx++D8|dmPP<0@ z$wh#ZYI%Rx$NKe-)BlJzB*bot0ras3I%`#HTMDthGtM_G6u-(tSroGp1Lz+W1Y`$@ zP`9NK^|IHbBrJ#AL3!X*g3{arc@)nuqa{=*2y+DvSwE=f*{>z1HX(>V zNE$>bbc}_yAu4OVn;8LG^naq5HZY zh{Hec==MD+kJhy6t=Nro&+V)RqORK&ssAxioc7-L#UQuPi#3V2pzfh6Ar400@iuV5 z@r>+{-yOZ%XQhsSfw%;|a4}XHaloW#uGluLKux0II9S1W4w=X9J=(k&8KU()m}b{H zFtoD$u5JlGfpX^&SXHlp$J~wk|DL^YVNh2w(oZ~1*W156YRmenU;g=mI zw({B(QVo2JpJ?pJqu9vijk$Cn+%PSw&b4c@uU6vw)DjGm2WJKt!X}uZ43XYlDIz%& z=~RlgZpU-tu_rD`5!t?289PTyQ zZgAEp=zMK>RW9^~gyc*x%vG;l+c-V?}Bm;^{RpgbEnt_B!FqvnvSy)T=R zGa!5GACDk{9801o@j>L8IbKp#!*Td5@vgFKI4w!5?R{>@^hd8ax{l=vQnd2RDHopo zwA+qb2cu4Rx9^Bu1WNYT`a(g}=&&vT`&Sqn-irxzX_j1=tIE#li`Hn=ht4KQXp zzZj`JO+wojs0dRA#(bXBOFn**o+7rPY{bM9m<+UBF{orv$#yF8)AiOWfuas5Fo`CJ zqa;jAZU^!bh8sjE7fsoPn%Tw11+vufr;NMm3*zC=;jB{R49e~BDeMR+H6MGzDlcA^ zKg>JEL~6_6iaR4i`tSfUhkgPaLXZ<@L7poRF?dw_DzodYG{Gp7#24<}=18PBT}aY` z{)rrt`g}930jr3^RBQNA$j!vzTh#Mo1VL`QCA&US?;<2`P+xy8b9D_Hz>FGHC2r$m zW>S9ywTSdQI5hh%7^e`#r#2906T?))i59O(V^Rpxw42rCAu-+I3y#Pg6cm#&AX%dy ze=hv0cUMxxxh1NQEIYXR{IBM&Bk8FK3NZI3z+M>r@A$ocd*e%x-?W;M0pv50p+MVt zugo<@_ij*6RZ;IPtT_sOf2Zv}-3R_1=sW37GgaF9Ti(>V z1L4ju8RzM%&(B}JpnHSVSs2LH#_&@`4Kg1)>*)^i`9-^JiPE@=4l$+?NbAP?44hX&XAZy&?}1;=8c(e0#-3bltVWg6h=k!(mCx=6DqOJ-I!-(g;*f~DDe={{JGtH7=UY|0F zNk(YyXsGi;g%hB8x)QLpp;;`~4rx>zr3?A|W$>xj>^D~%CyzRctVqtiIz7O3pc@r@JdGJiH@%XR_9vaYoV?J3K1cT%g1xOYqhXfSa`fg=bCLy% zWG74UTdouXiH$?H()lyx6QXt}AS)cOa~3IdBxddcQp;(H-O}btpXR-iwZ5E)di9Jf zfToEu%bOR11xf=Knw7JovRJJ#xZDgAvhBDF<8mDu+Q|!}Z?m_=Oy%Ur4p<71cD@0OGZW+{-1QT?U%_PJJ8T!0d2*a9I2;%|A z9LrfBU!r9qh4=3Mm3nR_~X-EyNc<;?m`?dKUNetCnS)}_-%QcWuOpw zAdZF`4c_24z&m{H9-LIL`=Hrx%{IjrNZ~U<7k6p{_wRkR84g>`eUBOQd3x5 zT^kISYq)gGw?IB8(lu1=$#Vl?iZdrx$H0%NxW)?MO$MhRHn8$F^&mzfMCu>|`{)FL z`ZgOt`z%W~^&kzMAuWy9=q~$ldBftH0}T#(K5e8;j~!x$JjyspJ1IISI?ON5OIPB$ z-5_|YUMb+QUsiv3R%Ys4tVYW+x$}dg;hw%EdoH%SXMp`)v?cxR4wic{X9pVBH>=`#`Kcj!}x4 zV!`6tj|*q?jZdG(CSevn(}4Ogij5 z-kp;sZs}7oNu0x+NHs~(aWaKGV@l~TBkmW&mPj==N!f|1e1SndS6(rPxsn7dz$q_{ zL0jSrihO)1t?gh8N zosMjR3n#YC()CVKv zos2TbnL&)lHEIiYdz|%6N^vAUvTs6?s|~kwI4uXjc9fim`KCqW3D838Xu{48p$2?I zOeEqQe1}JUZECrZSO_m=2<$^rB#B6?nrFXFpi8jw)NmoKV^*Utg6i8aEW|^QNJuW& z4cbXpHSp4|7~TW(%JP%q9W2~@&@5Y5%cXL#fMhV59AGj<3$Hhtfa>24DLk{7GZUtr z5ql**-e58|mbz%5Kk~|f!;g+Ze^b);F+5~^jdoq#m+s?Y*+=d5ruym%-Tnn8htCV; zDyyUrWydgDNM&bI{yp<_wd-q&?Ig+BN-^JjWo6Zu3%Eov^Ja>%eKqrk&7kUqeM8PL zs5D}lTe_Yx;e=K`TDya!-u%y$)r*Cr4bSfN*eZk$XT(Lv2Y}qj&_UaiTevxs_=HXjnOuBpmT> zBg|ty8?|1rD1~Ev^6=C$L9%+RkmBSQxlnj3j$XN?%QBstXdx+Vl!N$f2Ey`i3p@!f zzqhI3jC(TZUx|sP%yValu^nzEV96o%*CljO>I_YKa8wMfc3$_L()k4PB6kglP@IT#wBd*3RITYADL}g+hlzLYxFmCt=_XWS}=jg8`RgJefB57z(2n&&q>m ze&F(YMmoRZW7sQ;cZgd(!A9>7mQ2d#!-?$%G8IQ0`p1|*L&P$GnU0i0^(S;Rua4v8 z_7Qhmv#@+kjS-M|($c*ZOo?V2PgT;GKJyP1REABlZhPyf!kR(0UA7Bww~R<7_u6#t z{XNbiKT&tjne(&=UDZ+gNxf&@9EV|fblS^gxNhI-DH;|`1!YNlMcC{d7I{u_E~cJOalFEzDY|I?S3kHtbrN&}R3k zK(Ph_Ty}*L3Et6$cUW`0}**BY@44KtwEy(jW@pAt`>g> z&8>-TmJiDwc;H%Ae%k6$ndZlfKruu1GocgZrLN=sYI52}_I%d)~ z6z40!%W4I6ch$CE2m>Dl3iwWIbcm27QNY#J!}3hqc&~(F8K{^gIT6E&L!APVaQhj^ zjTJEO&?**pivl^xqfD(rpLu;`Tm1MV+Wtd4u>X6u5V{Yp%)xH$k410o{pGoKdtY0t@GgqFN zO=!hTcYoa^dEPKvPX4ukgUTmR#q840gRMMi%{3kvh9gt(wK;Fniqu9A%BMsq?U&B5DFXC8t8FBN1&UIwS#=S zF(6^Eyn8T}p)4)yRvs2rCXZ{L?N6{hgE_dkH_HA#L3a0$@UMoBw6RE9h|k_rx~%rB zUqeEPL|!Pbp|up2Q=8AcUxflck(fPNJYP1OM_4I(bc24a**Qnd-@;Bkb^2z8Xv?;3yZp*| zoy9KhLo=;8n0rPdQ}yAoS8eb zAtG5QYB|~z@Z(Fxdu`LmoO>f&(JzsO|v0V?1HYsfMvF!3| zka=}6U13(l@$9&=1!CLTCMS~L01CMs@Abl4^Q^YgVgizWaJa%{7t)2sVcZg0mh7>d z(tN=$5$r?s={yA@IX~2ot9`ZGjUgVlul$IU4N}{ zIFBzY3O0;g$BZ#X|VjuTPKyw*|IJ+&pQ` z(NpzU`o=D86kZ3E5#!3Ry$#0AW!6wZe)_xZ8EPidvJ0f+MQJZ6|ZJ$CEV6;Yt{OJnL`dewc1k>AGbkK9Gf5BbB-fg? zgC4#CPYX+9%LLHg@=c;_Vai_~#ksI~)5|9k(W()g6ylc(wP2uSeJ$QLATtq%e#zpT zp^6Y)bV+e_pqIE7#-hURQhfQvIZpMUzD8&-t$esrKJ}4`ZhT|woYi>rP~y~LRf`*2!6 z6prDzJ~1VOlYhYAuBHcu9m>k_F>;N3rpLg>pr;{EDkeQPHfPv~woj$?UTF=txmaZy z?RrVthxVcqUM;X*(=UNg4(L|0d250Xk)6GF&DKD@r6{aZo;(}dnO5@CP7pMmdsI)- zeYH*@#+|)L8x7)@GNBu0Npyyh6r z^~!3$x&w8N)T;|LVgnwx1jHmZn{b2V zO|8s#F0NZhvux?0W9NH5;qZ?P_JtPW86)4J>AS{0F1S0d}=L2`{F z_y;o;17%{j4I)znptnB z%No1W>o}H2%?~CFo~0j?pzWk?dV4ayb!s{#>Yj`ZJ!H)xn}*Z_gFHy~JDis)?9-P=z4iOQg{26~n?dTms7)+F}? zcXvnHHnnbNTzc!$t+V}=<2L<7l(84v1I3b;-)F*Q?cwLNlgg{zi#iS)*rQ5AFWe&~ zWHPPGy{8wEC9JSL?qNVY76=es`bA{vUr~L7f9G@mP}2MNF0Qhv6Sgs`r_k!qRbSXK zv16Qqq`rFM9!4zCrCeiVS~P2e{Pw^A8I?p?NSVR{XfwlQo*wj|Ctqz4X-j+dU7eGkC(2y`(P?FM?P4gKki3Msw#fM6paBq#VNc>T2@``L{DlnnA-_*i10Kre&@-H!Z7gzn9pRF61?^^ z8dJ5kEeVKb%Bly}6NLV}<0(*eZM$QTLcH#+@iWS^>$Of_@Mu1JwM!>&3evymgY6>C_)sK+n|A5G6(3RJz0k>(z2uLdzXeTw)e4*g!h} zn*UvIx-Ozx<3rCF#C`khSv`Y-b&R4gX>d5osr$6jlq^8vi!M$QGx05pJZoY#RGr*J zsJmOhfodAzYQxv-MoU?m_|h^aEwgEHt5h_HMkHwtE+OA03(7{hm1V?AlYAS7G$u5n zO+6?51qo@aQK5#l6pM`kD5OmI28g!J2Z{5kNlSuKl=Yj3QZ|bvVHU}FlM+{QV=<=) z+b|%Q!R)FE z@ycDMSKV2?*XfcAc5@IOrSI&3&aR$|oAD8WNA6O;p~q-J@ll{x`jP<*eEpIYOYnT zer_t=dYw6a0avjQtKN&#n&(KJ5Kr$RXPOp1@Fq#0Of zTXQkq4qQxKWR>x#d{Hyh?6Y)U07;Q$?BTl7mx2bSPY_juXub1 z%-$)NKXzE<%}q>RX25*oeMVjiz&r_z;BrQV-(u>!U>C*OisXNU*UftsrH6vAhTEm@ zoKA`?fZL1sdd!+G@*NNvZa>}37u^x8^T>VH0_6Bx{3@x5NAg&55{2jUE-w3zCJNJi z^IlU=+DJz-9K&4c@7iKj(zlj@%V}27?vYmxo*;!jZVXJMeDg;5T!4Y1rxNV-e$WAu zkk6^Xao8HC=w2hpLvM(!xwo|~$eG6jJj39zyQHf)E+NPJlfspUhzRv&_qr8+Z1`DA zz`EV=A)d=;2&J;eypNx~q&Ir_7e_^xXg(L9>k=X4pxZ3y#-ch$^TN}i>X&uwF%75c(9cjO6`E5 z16vbMYb!lEIM?jxn)^+Ld8*hmEXR4a8TSfqwBg1(@^8$p&#@?iyGd}uhWTVS`Mlpa zGc+kV)K7DJwd46aco@=?iASsx?sDjbHoDVU9=+^tk46|Fxxey1u)_}c1j z^(`5~PU%og1LdSBE5x4N&5&%Nh$sy0oANXwUcGa>@CCMqP`4W$ZPSaykK|giiuMIw zu#j)&VRKWP55I(5K1^cog|iXgaK1Z%wm%T;;M3X`-`TTWaI}NtIZj;CS)S%S(h}qq zRFQ#{m4Qk$7;1i*0PC^|X1@a1pcMq1aiRSCHq+mnfj^FS{oxWs0McCN-lK4>SDp#` z7=Duh)kXC;lr1g3dqogzBBDg6>et<<>m>KO^|bI5X{+eMd^-$2xfoP*&e$vdQc7J% zmFO~OHf7aqlIvg%P`Gu|3n;lKjtRd@;;x#$>_xU(HpZos7?ShZlQSU)bY?qyQM3cHh5twS6^bF8NBKDnJgXHa)? zBYv=GjsZuYC2QFS+jc#uCsaEPEzLSJCL=}SIk9!*2Eo(V*SAUqKw#?um$mUIbqQQb zF1Nn(y?7;gP#@ws$W76>TuGcG=U_f6q2uJq?j#mv7g;llvqu{Yk~Mo>id)jMD7;T> zSB$1!g)QpIf*f}IgmV;!B+3u(ifW%xrD=`RKt*PDC?M5KI)DO`VXw(7X-OMLd3iVU z0CihUN(eNrY;m?vwK{55MU`p1;JDF=6ITN$+!q8W#`iIsN8;W7H?`htf%RS9Lh+KQ z_p_4?qO4#*`t+8l-N|kAKDcOt zoHsqz_oO&n?@4^Mr*4YrkDX44BeS*0zaA1j@*c}{$;jUxRXx1rq7z^*NX6d`DcQ}L z6*cN7e%`2#_J4z8=^GM6>%*i>>X^_0u9qn%0JTUo)c0zIz|7a`%_UnB)-I1cc+ z0}jAK0}jBl|6-2VT759oxBnf%-;7vs>7Mr}0h3^$0`5FAy}2h{ps5%RJA|^~6uCqg zxBMK5bQVD{Aduh1lu4)`Up*&( zCJQ>nafDb#MuhSZ5>YmD@|TcrNv~Q%!tca;tyy8Iy2vu2CeA+AsV^q*Wohg%69XYq zP0ppEDEYJ9>Se&X(v=U#ibxg()m=83pLc*|otbG;`CYZ z*YgsakGO$E$E_$|3bns7`m9ARe%myU3$DE;RoQ<6hR8e;%`pxO1{GXb$cCZl9lVnJ$(c` z``G?|PhXaz`>)rb7jm2#v7=(W?@ zjUhrNndRFMQ}%^^(-nmD&J>}9w@)>l;mhRr@$}|4ueOd?U9ZfO-oi%^n4{#V`i}#f zqh<@f^%~(MnS?Z0xsQI|Fghrby<&{FA+e4a>c(yxFL!Pi#?DW!!YI{OmR{xEC7T7k zS_g*9VWI}d0IvIXx*d5<7$5Vs=2^=ews4qZGmAVyC^9e;wxJ%BmB(F5*&!yyABCtLVGL@`qW>X9K zpv=W~+EszGef=am3LG+#yIq5oLXMnZ_dxSLQ_&bwjC^0e8qN@v!p?7mg02H<9`uaJ zy0GKA&YQV2CxynI3T&J*m!rf4@J*eo235*!cB1zEMQZ%h5>GBF;8r37K0h?@|E*0A zIHUg0y7zm(rFKvJS48W7RJwl!i~<6X2Zw+Fbm9ekev0M;#MS=Y5P(kq^(#q11zsvq zDIppe@xOMnsOIK+5BTFB=cWLalK#{3eE>&7fd11>l2=MpNKjsZT2kmG!jCQh`~Fu0 z9P0ab`$3!r`1yz8>_7DYsO|h$kIsMh__s*^KXv?Z1O8|~sEz?Y{+GDzze^GPjk$E$ zXbA-1gd77#=tn)YKU=;JE?}De0)WrT%H9s3`fn|%YibEdyZov3|MJ>QWS>290eCZj z58i<*>dC9=kz?s$sP_9kK1p>nV3qvbleExyq56|o+oQsb{ZVmuu1n~JG z0sUvo_i4fSM>xRs8rvG$*+~GZof}&ISxn(2JU*K{L<3+b{bBw{68H&Uiup@;fWWl5 zgB?IWMab0LkXK(Hz#yq>scZbd2%=B?DO~^q9tarlzZysN+g}n0+v);JhbjUT8AYrt z3?;0r%p9zLJv1r$%q&HKF@;3~0wVwO!U5m;J`Mm|`Nc^80sZd+Wj}21*SPoF82hCF zoK?Vw;4ioafdAkZxT1er-LLVi-*0`@2Ur&*!b?0U>R;no+S%)xoBuBxRw$?weN-u~tKE}8xb@7Gs%(aC;e1-LIlSfXDK(faFW)mnHdrLc3`F z6ZBsT^u0uVS&il=>YVX^*5`k!P4g1)2LQmz{?&dgf`7JrA4ZeE0sikL`k!Eb6r=g0 z{aCy_0I>fxSAXQYz3lw5G|ivg^L@(x-uch!AphH+d;E4`175`R0#b^)Zp>EM1Ks=zx6_261>!7 z{7F#a{Tl@Tpw9S`>7_i|PbScS-(dPJv9_0-FBP_aa@Gg^2IoKNZM~#=sW$SH3MJ|{ zsQy8F43lX7hYx<{v^Q9`2QsMzeen3cGpiTgzVp- z`aj3&Wv0(he1qKI!2jpGpO-i0Wpcz%vdn`2o9x&3;^nsZPt3c \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 00000000..9991c503 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,100 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem http://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/iofog-agent-client/build.gradle b/iofog-agent-client/build.gradle new file mode 100644 index 00000000..8adc015a --- /dev/null +++ b/iofog-agent-client/build.gradle @@ -0,0 +1,38 @@ +plugins { + id "com.github.johnrengelman.shadow" +} + +description = 'iofog-agent-client' +configurations { + // configuration that holds jars to include in the jar + extraLibs +} +dependencies { + compile 'org.slf4j:slf4j-nop:1.7.13' + extraLibs ('javax.json:javax.json-api:1.0', 'org.glassfish:javax.json:1.1.4') + configurations.compile.extendsFrom(configurations.extraLibs) +} + +processResources { + expand(project.properties) +} + +build { + dependsOn shadowJar +} + +task copyJar(type: Copy) { + from ("$buildDir/libs/") { + include "*-all.jar" + } + into file('../packaging/iofog-agent/usr/bin/') + rename('.*?(jar$)', 'iofog-agent.jar') +} + +jar { + from { + configurations.extraLibs.collect { it.isDirectory() ? it : zipTree(it) } + } + manifest.attributes["Main-Class"] = 'org.eclipse.iofog.Client' + manifest.attributes["Implementation-Version"] = project.property('version') +} diff --git a/iofog-agent-client/src/main/java/org/eclipse/iofog/Client.java b/iofog-agent-client/src/main/java/org/eclipse/iofog/Client.java new file mode 100644 index 00000000..a2d55cdc --- /dev/null +++ b/iofog-agent-client/src/main/java/org/eclipse/iofog/Client.java @@ -0,0 +1,280 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.text.ParseException; +import java.util.Properties; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; + +public class Client { + + private static final String PROPERTIES_FILE_PATH = "/version.properties"; + + private static final String LOCAL_API_ENDPOINT = "http://localhost:54321/v2/commandline"; + private static final String WINDOWS_IOFOG_PATH = System.getenv("IOFOG_PATH") != null ? System.getenv("IOFOG_PATH") : "./"; + private static final String SNAP_COMMON = System.getenv("SNAP_COMMON") != null ? System.getenv("SNAP_COMMON") : ""; + private static final String CONFIG_DIR = isWindows() ? WINDOWS_IOFOG_PATH : SNAP_COMMON + "/etc/iofog-agent/"; + private static final String LOCAL_API_TOKEN_PATH = CONFIG_DIR + "local-api"; + + private static final Properties cmdProperties; + + + static { + cmdProperties = new Properties(); + try (InputStream in = Client.class.getResourceAsStream(PROPERTIES_FILE_PATH)) { + cmdProperties.load(in); + } catch (IOException e) { + System.out.println(e.getMessage()); + } + } + + private static String getVersion() { + return cmdProperties.getProperty("version"); + } + + /** + * check if another instance of iofog is running + * + * @return boolean + */ + private static boolean isAnotherInstanceRunning() { + try { + HttpURLConnection conn = postRequest("status"); + conn.disconnect(); + return true; + } catch (Exception e) { + return false; + } + } + + private static HttpURLConnection postRequest(String... args) throws Exception { + StringBuilder params = new StringBuilder("{\"command\":\""); + for (String arg : args) { + params.append(arg).append(" "); + } + params = new StringBuilder(params.toString().trim() + "\"}"); + byte[] postData = params.toString().trim().getBytes(StandardCharsets.UTF_8); + + String accessToken = fetchAccessToken(); + + URL url = new URL(LOCAL_API_ENDPOINT); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Accept", "text/plain"); + conn.setRequestProperty("Content-Length", Integer.toString(postData.length)); + conn.setRequestProperty("Authorization", accessToken); + conn.setDoOutput(true); + try (DataOutputStream wr = new DataOutputStream(conn.getOutputStream())) { + wr.write(postData); + wr.flush(); + } + + return conn; + } + + /** + * send command-line parameters to ioFog daemon + * + * @param args - parameters + */ + private static boolean sendCommandlineParameters(String... args) { + try { + HttpURLConnection conn = postRequest(args); + int statusCode; + + BufferedReader br = null; + if (conn.getResponseCode() == 200) { + br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + statusCode = 0; + } else { + br = new BufferedReader(new InputStreamReader(conn.getErrorStream())); + statusCode = 1; + } + StringBuilder result = new StringBuilder(); + String output; + while ((output = br.readLine()) != null) { + result.append(output); + } + String commandLineOutput = result.toString(); + JsonReader reader = Json.createReader(new StringReader(commandLineOutput.split("}")[0] + "}")); + JsonObject commandLineJsonResponse = reader.readObject(); + System.out.println(commandLineJsonResponse.getString("response").replace("\\n", "\n")); + System.exit(statusCode); + conn.disconnect(); + return conn.getResponseCode() == 200; + } catch (Exception e) { + System.out.println(e); + return false; + } + } + + private static String fetchAccessToken() { + String line = ""; + try (BufferedReader reader = new BufferedReader(new FileReader(LOCAL_API_TOKEN_PATH))) { + line = reader.readLine(); + } catch (IOException e) { + System.out.println("Local API access token is missing, try to re-install Agent."); + } + + return line; + } + + /** + * returns help + * + * @return String + */ + private static String showHelp() { + return ("Usage 1: iofog-agent [OPTION]\n" + + "Usage 2: iofog-agent [COMMAND] \n" + + "Usage 3: iofog-agent [COMMAND] [Parameter] \n" + + "\n" + + "Option GNU long option Meaning\n" + + "====== =============== =======\n" + + "-h, -? --help Show this message\n" + + "-v --version Display the software version and\n" + + " license information\n" + + "\n" + + "\n" + + "Command Arguments Meaning\n" + + "======= ========= =======\n" + + "help Show this message\n" + + "version Display the software version and\n" + + " license information\n" + + "status Display current status information\n" + + " about the software\n" + + "provision Attach this software to the\n" + + " configured ioFog controller\n" + + "deprovision Detach this software from all\n" + + " ioFog controllers\n" + + "info Display the current configuration\n" + + " and other information about the\n" + + " software\n" + + "switch Switch to different config \n" + + "config [Parameter] [VALUE] Change the software configuration\n" + + " according to the options provided\n" + + " defaults Reset configuration to default values\n" + + " -d <#GB Limit> Set the limit, in GiB, of disk space\n" + + " that the message archive is allowed to use\n" + + " -dl

Set the message archive directory to use for disk\n" + + " storage\n" + + " -m <#MB Limit> Set the limit, in MiB, of RAM memory that\n" + + " the software is allowed to use for\n" + + " messages\n" + + " -p <#cpu % Limit> Set the limit, in percentage, of CPU\n" + + " time that the software is allowed\n" + + " to use\n" + + " -a Set the uri of the fog controller\n" + + " to which this software connects\n" + + " -ac Set the file path of the SSL/TLS\n" + + " certificate for validating the fog\n" + + " controller identity\n" + + " -c Set the UNIX socket or network address\n" + + " that the Docker daemon is using\n" + + " -n Set the name of the network adapter\n" + + " that holds the correct IP address of \n" + + " this machine\n" + + " -l <#GB Limit> Set the limit, in GiB, of disk space\n" + + " that the log files can consume\n" + + " -ld Set the directory to use for log file\n" + + " storage\n" + + " -lc <#log files> Set the number of log files to evenly\n" + + " split the log storage limit\n" + + " -ll Set the standard logging levels that\\n"+ + " can be used to control logging output" + + " -sf <#seconds> Set the status update frequency\n" + + " -cf <#seconds> Set the get changes frequency\n" + + " -df <#seconds> Set the post diagnostics frequency\n" + + " -sd <#seconds> Set the scan devices frequency\n" + + " -uf <#hours> Set the isReadyToUpgradeScan frequency\\n" + + " -dt <#percentage> Set the available disk threshold\\n" + + " -idc Set the mode on which any not\n" + + " registered docker container will be\n" + + " shut down\n" + + " -gps Use auto to detect fog type by system commands,\n" + + " use arm or intel_amd to set it manually\n" + + " -sec Set the secure mode without using ssl \\n" + + " certificates. \\n" + + " -dev Set the developer's mode\\n" + + "\n" + + "\n" + + "Report bugs to: edgemaster@iofog.org\n" + + "ioFog home page: http://iofog.org\n" + + "For users with Eclipse accounts, report bugs to: https://bugs.eclipse.org/bugs/enter_bug.cgi?product=iofog"); + } + + private static String version() { + return "ioFog Agent " + getVersion() + " " + + "\nCopyright (C) 2018-2022 Edgeworx, Inc." + + "\nEclipse ioFog is provided under the Eclipse Public License (EPL2)" + + "\nhttps://www.eclipse.org/legal/epl-v20.html"; + } + + + public static void main(String[] args) throws ParseException { + if (args == null || args.length == 0) + args = new String[]{"help"}; + + if (isAnotherInstanceRunning()) { + switch (args[0]) { + case "stop": + System.out.println("Enter \"service iofog-agent stop\""); + break; + case "start": + System.out.println("ioFog Agent is already running."); + break; + default: + sendCommandlineParameters(args); + break; + } + } else { + switch (args[0]) { + case "help": + case "--help": + case "-h": + case "-?": + System.out.println(showHelp()); + break; + case "version": + case "--version": + case "-v": + System.out.println(version()); + break; + case "start": + System.out.println("Enter \"service iofog-agent start\""); + break; + default: + System.out.println("ioFog Agent is not running."); + } + } + } + + private static boolean isWindows() { + String osName = System.getProperty("os.name"); + return osName != null && osName.startsWith("Windows"); + } + +} diff --git a/iofog-agent-client/src/main/resources/version.properties b/iofog-agent-client/src/main/resources/version.properties new file mode 100644 index 00000000..a50bf5c8 --- /dev/null +++ b/iofog-agent-client/src/main/resources/version.properties @@ -0,0 +1 @@ +version=${version} diff --git a/iofog-agent-daemon/build.gradle b/iofog-agent-daemon/build.gradle new file mode 100644 index 00000000..da772d48 --- /dev/null +++ b/iofog-agent-daemon/build.gradle @@ -0,0 +1,91 @@ +plugins { + id "com.github.johnrengelman.shadow" + id 'jacoco' +} + +description = 'iofog-agent-daemon' + +dependencies { + compile 'com.github.docker-java:docker-java:3.2.5' + compile 'io.netty:netty-all:4.1.34.Final' + compile 'org.jboss.logmanager:jboss-logmanager:2.0.3.Final' + compile 'com.jcraft:jsch:0.1.55' + compile 'com.fasterxml.jackson.core:jackson-databind:2.8.7' + compile 'org.apache.httpcomponents:httpmime:4.5.7' + compile 'junit:junit:4.12' + compile 'com.github.oshi:oshi-core:3.13.0' + compile 'org.slf4j:slf4j-nop:1.7.13' + compile 'org.apache.qpid:qpid-jms-client:0.52.0' + compile 'javax.json:javax.json-api:1.1.4' + compile 'org.glassfish:javax.json:1.1.4' + testCompile 'org.mockito:mockito-core:2.9.0' + testCompile 'org.powermock:powermock-module-junit4:2.0.2' + testCompile 'org.powermock:powermock-api-mockito2:2.0.2' + testCompile 'org.powermock:powermock-core:2.0.2' +} + +processResources { + expand(project.properties) +} + +build { + dependsOn shadowJar +} + +task copyJar(type: Copy) { + from ("$buildDir/libs/") { + include "*-all.jar" + } + into file('../packaging/iofog-agent/usr/bin/') + rename('.*?(jar$)', 'iofog-agentd.jar') +} + +jar { + manifest.attributes["Main-Class"] = 'org.eclipse.iofog.Daemon' + manifest.attributes["Implementation-Version"] = rootProject.property('version') +} + +jacoco { + toolVersion = "0.8.4" +} + +jacocoTestReport { + reports { + xml.enabled true + csv.enabled false + html.destination file("${buildDir}/reports/jacocoHtml") + } +} + +project.ext.jacocoOfflineSourceSets = [ 'main' ] +task doJacocoOfflineInstrumentation(dependsOn: [ classes, project.configurations.jacocoAnt ]) { + inputs.files classes.outputs.files + File outputDir = new File(project.buildDir, 'instrumentedClasses') + outputs.dir outputDir + doFirst { + project.delete(outputDir) + ant.taskdef( + resource: 'org/jacoco/ant/antlib.xml', + classpath: project.configurations.jacocoAnt.asPath, + uri: 'jacoco' + ) + def instrumented = false + jacocoOfflineSourceSets.each { sourceSetName -> + if (file(sourceSets[sourceSetName].java.outputDir).exists()) { + def instrumentedClassedDir = "${outputDir}/${sourceSetName}" + ant.'jacoco:instrument'(destdir: instrumentedClassedDir) { + fileset(dir: sourceSets[sourceSetName].java.outputDir, includes: '**/*.class') + } + sourceSets.test.runtimeClasspath -= files(sourceSets[sourceSetName].java.outputDir) + sourceSets.test.runtimeClasspath += files(instrumentedClassedDir) + instrumented = true + } + } + if (instrumented) { + test.jvmArgs += '-noverify' + } + } +} + +test.dependsOn(doJacocoOfflineInstrumentation) +test.finalizedBy jacocoTestReport \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/Daemon.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/Daemon.java new file mode 100644 index 00000000..8642daf9 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/Daemon.java @@ -0,0 +1,199 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog; + +import org.apache.commons.lang.exception.ExceptionUtils; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.ParseException; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class Daemon { + private static final String MODULE_NAME = "MAIN_DAEMON"; + + private static final String LOCAL_API_ENDPOINT = "http://localhost:54321/v2/commandline"; + + /** + * check if another instance of iofog is running + * + * @return boolean + */ + private static boolean isAnotherInstanceRunning() { + + try { + URL url = new URL(LOCAL_API_ENDPOINT); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.getResponseCode(); + conn.disconnect(); + return true; + } catch (IOException e) { + return false; + } + } + + /** + * send command-line parameters to ioFog daemon + * + * @param args - parameters + */ + private static boolean sendCommandlineParameters(String... args) { + if (args[0].equals("stop")) { + System.out.println("Stopping iofog service..."); + System.out.flush(); + } + + try { + StringBuilder params = new StringBuilder("{\"command\":\""); + for (String arg : args) { + params.append(arg).append(" "); + } + params = new StringBuilder(params.toString().trim() + "\"}"); + byte[] postData = params.toString().trim().getBytes(UTF_8); + + String accessToken = fetchAccessToken(); + + URL url = new URL(LOCAL_API_ENDPOINT); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Content-Type", "application/json"); + conn.setRequestProperty("Accept", "text/plain"); + conn.setRequestProperty("Content-Length", Integer.toString(postData.length)); + conn.setRequestProperty("Authorization", accessToken); + conn.setDoOutput(true); + try (DataOutputStream wr = new DataOutputStream(conn.getOutputStream())) { + wr.write(postData); + } + + if (conn.getResponseCode() != 200) { + return false; + } + StringBuilder result = new StringBuilder(); + + try (BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream()), + UTF_8))) { + String output; + while ((output = br.readLine()) != null) { + result.append(output); + } + } + + + conn.disconnect(); + + System.out.println(result.toString().replace("\\n", "\n")); + return true; + } catch (IOException e) { + return false; + } + } + + /** + * creates and grants permission to daemon files directory + */ + private static void setupEnvironment() { + final File daemonFilePath = new File(Constants.VAR_RUN); + daemonFilePath.mkdirs(); + } + + /** + * starts logging service + */ + private static void startLoggingService() { + try { + LoggingService.setupLogger(); + } catch (IOException e) { + System.out.println("Error starting logging service"); + System.out.println(e.getMessage()); + System.out.println(ExceptionUtils.getFullStackTrace(e)); + System.exit(1); + } + LoggingService.logInfo(MODULE_NAME, "Configuration loaded."); + + } + + /** + * ports standard output to null + */ + private static void outToNull() { + Constants.systemOut = System.out; + try { + if (!Configuration.debugging) { + System.setOut(new PrintStream(new OutputStream() { + @Override + public void write(int b) { + // DO NOTHING + } + }, false, UTF_8.name())); + + System.setErr(new PrintStream(new OutputStream() { + @Override + public void write(int b) { + // DO NOTHING + } + }, false, UTF_8.name())); + } + } catch (UnsupportedEncodingException ex) { + LoggingService.logInfo(MODULE_NAME, ex.getMessage()); + } + } + + public static void main(String[] args) throws ParseException { + + try { + Configuration.load(); + + setupEnvironment(); + + if (args == null || args.length == 0) + System.exit(0); + + if (isAnotherInstanceRunning()) { + if (args[0].equals("start")) { + System.out.println("ioFog Agent is already running."); + } else if (args[0].equals("stop")) { + sendCommandlineParameters(args); + } + } else if (args[0].equals("start")) { + startLoggingService(); + + outToNull(); + + Configuration.setupSupervisor(); + + System.setOut(Constants.systemOut); + } + } catch (Exception e) { + System.out.println(e.getMessage()); + System.out.println(ExceptionUtils.getFullStackTrace(e)); + } + } + + private static String fetchAccessToken() { + String line = ""; + try (BufferedReader reader = new BufferedReader(new FileReader(Constants.LOCAL_API_TOKEN_PATH))) { + line = reader.readLine(); + } catch (IOException e) { + System.out.println("Local API access token is missing, try to re-install Agent."); + } + + return line; + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/IOFogModule.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/IOFogModule.java new file mode 100644 index 00000000..f3f59b1c --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/IOFogModule.java @@ -0,0 +1,46 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog; + +import org.eclipse.iofog.utils.logging.LoggingService; + +/** + * Common Interface for all ioFog modules + * + * @since 1/25/18. + * @author ekrylovich + */ +public interface IOFogModule { + + void start() throws Exception; + int getModuleIndex(); + String getModuleName(); + + default void logInfo(String message) { + LoggingService.logInfo(this.getModuleName(), message); + } + + default void logDebug(String message) { + LoggingService.logDebug(this.getModuleName(), message); + } + + default void logWarning(String message) { + LoggingService.logWarning(this.getModuleName(), message); + } + + default void logError(String message, Exception e) { + LoggingService.logError(this.getModuleName(), message, e); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineAction.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineAction.java new file mode 100644 index 00000000..e5fa4a5b --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineAction.java @@ -0,0 +1,401 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.command_line; + +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.process_manager.ProcessManager; +import org.eclipse.iofog.pruning.DockerPruningManager; +import org.eclipse.iofog.utils.Constants.ConfigSwitcherState; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.JsonObject; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.String.format; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.eclipse.iofog.command_line.CommandLineConfigParam.CONTROLLER_CERT; +import static org.eclipse.iofog.command_line.CommandLineConfigParam.existParam; +import static org.eclipse.iofog.status_reporter.StatusReporter.getStatusReport; +import static org.eclipse.iofog.utils.CmdProperties.*; +import static org.eclipse.iofog.utils.Constants.systemOut; +import static org.eclipse.iofog.utils.configuration.Configuration.*; + +/** + * Command Line Action Enum + * + * @since 1/24/18. + * @author ilaryionava + */ +public enum CommandLineAction { + + STOP_ACTION { + @Override + public List getKeys() { + return singletonList("stop"); + } + + @Override + public String perform(String[] args) { + try { + Thread.sleep(30000); + } catch (InterruptedException e) { + LoggingService.logError(MODULE_NAME, "Error stopping running microservices", e); + return "Error stopping running microservices."; + } + if(Configuration.getIofogUuid() != ""){ + try { + ProcessManager.getInstance().stopRunningMicroservices(true, Configuration.getIofogUuid()); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error stopping running microservices", e); + return "Error stopping running microservices."; + } + } + System.setOut(systemOut); + System.exit(0); + return EMPTY; + } + }, + START_ACTION { + @Override + public List getKeys() { + return singletonList("start"); + } + + @Override + public String perform(String[] args) { + return EMPTY; + } + }, + HELP_ACTION { + @Override + public List getKeys() { + return asList("help", "--help", "-h", "-?"); + } + + @Override + public String perform(String[] args) { + return showHelp(); + } + }, + VERSION_ACTION { + @Override + public List getKeys() { + return asList("version", "--version", "-v"); + } + + @Override + public String perform(String[] args) { + return format(getVersionMessage(), getVersion()); + } + }, + STATUS_ACTION { + @Override + public List getKeys() { + return singletonList("status"); + } + + @Override + public String perform(String[] args) { + return getStatusReport(); + } + }, + DE_PROVISION_ACTION { + @Override + public List getKeys() { + return singletonList("deprovision"); + } + + @Override + public String perform(String[] args) throws AgentUserException { + String status; + try { + status = FieldAgent.getInstance().deProvision(false); + } catch (Exception e) { + status = "Error"; + LoggingService.logError(MODULE_NAME, "error de-provisioning", new AgentUserException(e.getMessage(), e)); + throw new AgentUserException(format(getDeprovisionMessage(), status)); + } + if (status.equalsIgnoreCase("\nFailure - not provisioned")) { + LoggingService.logError(MODULE_NAME, "error de-provisioning", new AgentUserException("error de-provisioning")); + throw new AgentUserException(format(getDeprovisionMessage(), status)); + } + return format(getDeprovisionMessage(), status); + } + }, + INFO_ACTION { + @Override + public List getKeys() { + return singletonList("info"); + } + + @Override + public String perform(String[] args) { + return getConfigReport(); + } + }, + SWITCH_ACTION { + @Override + public List getKeys() { + return singletonList("switch"); + } + + @Override + public String perform(String[] args) { + if (args.length < 2) { + return showHelp(); + } + + String environment = args[1]; + try { + ConfigSwitcherState state = ConfigSwitcherState.parse(environment); + return Configuration.setupConfigSwitcher(state); + } catch(Exception e) { + return e.getMessage(); + } + } + }, + PROVISION_ACTION { + @Override + public List getKeys() { + return singletonList("provision"); + } + + @Override + public String perform(String[] args) throws AgentUserException { + if (args.length < 2) { + return showHelp(); + } + String provisionKey = args[1]; + JsonObject provisioningResult = FieldAgent.getInstance().provision(provisionKey); + String result; + if (provisioningResult == null) { + result = getProvisionCommonErrorMessage(); + throw new AgentUserException(result); + } else if (provisioningResult.containsKey("uuid")) { + result = format(getProvisionStatusSuccessMessage(), provisioningResult.getString("uuid")); + } else { + result = format(getProvisionStatusErrorMessage(), provisioningResult.getString("errorMessage")); + throw new AgentUserException(result); + } + return format(getProvisionMessage(), provisionKey, result); + } + }, + CONFIG_ACTION { + @Override + public List getKeys() { + return singletonList("config"); + } + + @Override + public String perform(String[] args) { + if (args.length == 1) { + return showHelp(); + } + + StringBuilder result = new StringBuilder(); + if (args.length == 2) { + if (CMD_CONFIG_DEFAULTS.equals(args[1])) { + try { + resetToDefault(); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error resetting configurtion", e); + return "Error resetting configuration."; + } + return "Configuration has been reset to its defaults."; + } else if (!CONTROLLER_CERT.getCmdText().equals(args[1])) { + return showHelp(); + } + } + + Map config = new HashMap<>(); + int i = 1; + while (i < args.length) { + String configParamOption = args[i]; + String value; + boolean isCertificateOption = configParamOption.equals(CONTROLLER_CERT.getCmdText()); + + if ((args.length - i < 2 && !isCertificateOption) || !existParam(configParamOption)) { + return showHelp(); + } + + if (isCertificateOption && (args.length == 2 || existParam(args[i + 1]))) { + value = ""; + i += 1; + } else { + value = args[i + 1]; + i += 2; + } + config.put(configParamOption.substring(1), value); + } + + try { + + HashMap oldValuesMap = getOldNodeValuesForParameters(config.keySet(), + Configuration.getCurrentConfig()); + HashMap errorMap = setConfig(config, false); + + for (Map.Entry e : errorMap.entrySet()) + result.append("\\n\tError : " + e.getValue()); + + for (Map.Entry e : config.entrySet()){ + if(!errorMap.containsKey(e.getKey())){ + String newValue = e.getValue().toString(); + if(e.getValue().toString().startsWith("+")) newValue = e.getValue().toString().substring(1); + result.append("\\n\tChange accepted for Parameter : - ") + .append(e.getKey()) + .append(", Old value was :") + .append(oldValuesMap.get(e.getKey())) + .append(", New Value is : ").append(newValue); + } + } + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error updating new config.", e); + result.append("Error updating new config : " + e.getMessage()); + } + + return result.toString(); + } + }, + PRUNE_ACTION { + @Override + public List getKeys() { + return singletonList("prune"); + } + + @Override + public String perform(String[] args) { + return DockerPruningManager.getInstance().pruneAgent(); + } + }, + CHECK_UPGRADE_READY_ACTION { + @Override + public List getKeys() { + return singletonList("checkUpgradeReady"); + } + + @Override + public String perform(String[] args) { + return FieldAgent.getInstance().getCheckUpgradeReadyReport(); + } + }; + + public abstract List getKeys(); + + public abstract String perform(String[] args) throws AgentUserException; + + public static CommandLineAction getActionByKey(String cmdKey) { + for (CommandLineAction action: + CommandLineAction.values()) { + if(action.getKeys().contains(cmdKey)) { + return action; + } + } + return HELP_ACTION; + } + + private static final String CMD_CONFIG_DEFAULTS = "defaults"; + public static final String MODULE_NAME = "Command Line Parser"; + + private static String showHelp() { + return ("Usage 1: iofog-agent [OPTION]\\n" + + "Usage 2: iofog-agent [COMMAND] \\n" + + "Usage 3: iofog-agent [COMMAND] [Parameter] \\n" + + "\\n" + + "Option GNU long option Meaning\\n" + + "====== =============== =======\\n" + + "-h, -? --help Show this message\\n" + + "-v --version Display the software version and\\n" + + " license information\\n" + + "\\n" + + "\\n" + + "Command Arguments Meaning\\n" + + "======= ========= =======\\n" + + "help Show this message\\n" + + "version Display the software version and\\n" + + " license information\\n" + + "status Display current status information\\n" + + " about the software\\n" + + "provision Attach this software to the\\n" + + " configured ioFog controller\\n" + + "deprovision Detach this software from all\\n" + + " ioFog controllers\\n" + + "info Display the current configuration\\n" + + " and other information about the\\n" + + " software\\n" + + "switch Switch to different config \\n" + + "config [Parameter] [VALUE] Change the software configuration\\n" + + " according to the options provided\\n" + + " defaults Reset configuration to default values\\n" + + " -d <#GB Limit> Set the limit, in GiB, of disk space\\n" + + " that the message archive is allowed to use\\n" + + " -dl Set the message archive directory to use for disk\\n" + + " storage\\n" + + " -m <#MB Limit> Set the limit, in MiB, of RAM memory that\\n" + + " the software is allowed to use for\\n" + + " messages\\n" + + " -p <#cpu % Limit> Set the limit, in percentage, of CPU\\n" + + " time that the software is allowed\\n" + + " to use\\n" + + " -a Set the uri of the fog controller\\n" + + " to which this software connects\\n" + + " -ac Set the file path of the SSL/TLS\\n" + + " certificate for validating the fog\\n" + + " controller identity\\n" + + " -c Set the UNIX socket or network address\\n" + + " that the Docker daemon is using\\n" + + " -n Set the name of the network adapter\\n" + + " that holds the correct IP address of \\n" + + " this machine\\n" + + " -l <#GB Limit> Set the limit, in GiB, of disk space\\n" + + " that the log files can consume\\n" + + " -ld Set the directory to use for log file\\n" + + " storage\\n" + + " -lc <#log files> Set the number of log files to evenly\\n" + + " split the log storage limit\\n" + + " -ll Set the standard logging levels that\\n"+ + " can be used to control logging output\\n" + + " -sf <#seconds> Set the status update frequency\\n" + + " -cf <#seconds> Set the get changes frequency\\n" + + " -df <#seconds> Set the post diagnostics frequency\\n" + + " -sd <#seconds> Set the scan devices frequency\\n" + + " -uf <#hours> Set the isReadyToUpgradeScan frequency\\n" + + " -dt <#percentage> Set the available disk threshold\\n" + + " -idc Set the mode on which any not\\n" + + " registered docker container will be\\n" + + " shut down\\n" + + " -gps Use auto to detect fog type by system commands,\\n" + + " use arm or intel_amd to set it manually\\n" + + " -sec Set the secure mode without using ssl \\n" + + " certificates. \\n" + + " -dev Set the developer's mode\\n" + + " -tz Set the device timeZone\\n" + + "\\n" + + "\\n" + + "Report bugs to: edgemaster@iofog.org\\n" + + "ioFog home page: http://iofog.org\\n" + + "For users with Eclipse accounts, report bugs to: https://bugs.eclipse.org/bugs/enter_bug.cgi?product=iofog"); + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineConfigParam.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineConfigParam.java new file mode 100644 index 00000000..111a6123 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineConfigParam.java @@ -0,0 +1,111 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.command_line; + +import org.eclipse.iofog.gps.GpsMode; + +import java.util.List; +import java.util.Optional; + +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.toList; + +/** + * Enum that represent parameters for 'config' option for cmd. + * + * @author ilaryionava + * @since 1/19/18. + */ +public enum CommandLineConfigParam { + + ACCESS_TOKEN("", "", "access_token", ""), + IOFOG_UUID("", "", "iofog_uuid", ""), + + DISK_CONSUMPTION_LIMIT ("10", "d","disk_consumption_limit", "diskLimit"), + DISK_DIRECTORY ("/var/lib/iofog-agent/", "dl","disk_directory", "diskDirectory"), + MEMORY_CONSUMPTION_LIMIT ("4096", "m", "memory_consumption_limit", "memoryLimit"), + PROCESSOR_CONSUMPTION_LIMIT ("80", "p","processor_consumption_limit", "cpuLimit"), + CONTROLLER_URL("https://fogcontroller1.iofog.org:54421/api/v2/", "a", "controller_url", ""), + CONTROLLER_CERT ("/etc/iofog-agent/cert.crt", "ac","controller_cert", ""), + DOCKER_URL ("unix:///var/run/docker.sock", "c","docker_url", "dockerUrl"), + NETWORK_INTERFACE ("dynamic", "n","network_interface", "networkInterface"), + LOG_DISK_CONSUMPTION_LIMIT ("10", "l","log_disk_consumption_limit", "logLimit"), + LOG_DISK_DIRECTORY ("/var/log/iofog-agent/", "ld","log_disk_directory", "logDirectory"), + LOG_FILE_COUNT ("10", "lc","log_file_count", "logFileCount"), + LOG_LEVEL ("INFO", "ll","log_level", "logLevel"), + STATUS_FREQUENCY("10", "sf", "status_update_freq", "statusFrequency"), + CHANGE_FREQUENCY("20", "cf", "get_changes_freq", "changeFrequency"), + DEVICE_SCAN_FREQUENCY("60", "sd", "scan_devices_freq", "deviceScanFrequency"), + WATCHDOG_ENABLED("off", "idc", "isolated_docker_container", "watchdogEnabled"), + GPS_MODE (GpsMode.AUTO.name().toLowerCase(), "gps", "gps", "gpsMode"), + GPS_COORDINATES ("", "", "gps_coordinates", "gpscoordinates"), + POST_DIAGNOSTICS_FREQ ("10", "df", "post_diagnostics_freq", "postdiagnosticsfreq"), + FOG_TYPE ("auto", "ft", "fog_type", ""), + SECURE_MODE ("off", "sec", "secure_mode", ""), + ROUTER_HOST ("", "", "router_host", "routerHost"), + ROUTER_PORT ("0", "", "router_port", "routerPort"), + DOCKER_PRUNING_FREQUENCY ("1", "pf", "docker_pruning_freq", "dockerPruningFrequency"), + AVAILABLE_DISK_THRESHOLD ("20", "dt", "available_disk_threshold", "availableDiskThreshold"), + READY_TO_UPGRADE_SCAN_FREQUENCY ("24", "uf", "upgrade_scan_frequency", "readyToUpgradeScanFrequency"), + DEV_MODE ("off", "dev", "dev_mode", ""), + TIME_ZONE("", "tz", "time_zone", "timeZone"); + + private final String commandName; + private final String xmlTag; + private final String jsonProperty; + private final String defaultValue; + + CommandLineConfigParam(String defaultValue, String commandName, String xmlTag, String jsonProperty) { + this.commandName = commandName; + this.xmlTag = xmlTag; + this.jsonProperty = jsonProperty; + this.defaultValue = defaultValue; + } + + public String getCommandName() { + return commandName; + } + + public String getXmlTag() { + return xmlTag; + } + + public String getJsonProperty() { + return jsonProperty; + } + + public String getDefaultValue() { + return defaultValue; + } + + public String getCmdText() { + return "-" + commandName; + } + + public static Optional getCommandByName(String commandName) { + return stream(CommandLineConfigParam.values()) + .filter(cmdParameter -> cmdParameter.getCommandName().equals(commandName)) + .findFirst(); + } + + public static List getAllCmdTextNames(){ + return stream(CommandLineConfigParam.values()) + .map(CommandLineConfigParam::getCmdText) + .collect(toList()); + } + + public static boolean existParam(String paramOption) { + return getAllCmdTextNames().contains(paramOption); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineParser.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineParser.java new file mode 100644 index 00000000..ea82084e --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineParser.java @@ -0,0 +1,37 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.command_line; + +import org.eclipse.iofog.exception.AgentUserException; + +/** + * to parse command-line parameters + * + * @author saeid + * + */ +public final class CommandLineParser { + + /** + * Private constructor - to prevent creation of class instance + */ + private CommandLineParser(){ + throw new UnsupportedOperationException(this.getClass() + " could not be instantiated"); + } + + public static String parse(String command) throws AgentUserException{ + String[] args = command.split(" "); + return CommandLineAction.getActionByKey(args[0]).perform(args); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellExecutor.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellExecutor.java new file mode 100755 index 00000000..56b1ffdc --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellExecutor.java @@ -0,0 +1,169 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.command_line.util; + +import org.apache.commons.lang.SystemUtils; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import java.util.stream.Stream; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Created by ekrylovich + * on 2/7/18. + */ +public class CommandShellExecutor { + private static final String MODULE_NAME = "CommandShellExecutor"; + private static final String CMD = "/bin/sh"; + private static final String CMD_WIN = "powershell"; + + + public static CommandShellResultSet, List> executeCommand(String command) { + String[] fullCommand = computeCommand(command); + return execute(fullCommand); + } + + public static CommandShellResultSet, List> executeScript(String script, String... args) { + String[] fullCommand = computeScript(script, args); + return execute(fullCommand); + } + + public static Process executeDynamicCommand(String command, + CommandShellResultSet, List> resultSet, + AtomicBoolean isRun, + Runnable killOrphanedProcessesRunnable) { + String[] fullCommand = computeCommand(command); + return executeDynamic(fullCommand, resultSet, isRun, killOrphanedProcessesRunnable); + } + + private static CommandShellResultSet, List> execute(String[] fullCommand) { + CommandShellResultSet, List> resultSet = null; + try { + Process process = Runtime.getRuntime().exec(fullCommand); + List value = readOutput(process, Process::getInputStream); + List errors = readOutput(process, Process::getErrorStream); + resultSet = new CommandShellResultSet<>(value, errors); + } catch (IOException e) { + LoggingService.logError(MODULE_NAME, e.getMessage(), e); + } + return resultSet; + } + + private static Process executeDynamic(String[] fullCommand, + CommandShellResultSet, List> resultSet, + AtomicBoolean isRun, + Runnable killOrphanedProcessesRunnable) { + try { + Process process = Runtime.getRuntime().exec(fullCommand); + + Runnable readVal = () -> { + readOutputDynamic(process, Process::getInputStream, resultSet.getValue(), isRun, killOrphanedProcessesRunnable); + }; + new Thread(readVal).start(); + + Runnable readErr = () -> { + readOutputDynamic(process, Process::getErrorStream, resultSet.getError(), isRun, killOrphanedProcessesRunnable); + }; + new Thread(readErr).start(); + return process; + } catch (IOException e) { + LoggingService.logError(MODULE_NAME, e.getMessage(), e); + return null; + } + } + + + public static CommandShellResultSet executeCommand(String command, Function, List>, CommandShellResultSet> mapper) { + return executeCommand(command).map(mapper); + } + + private static String[] computeCommand(String command) { + return new String[]{ + SystemUtils.IS_OS_WINDOWS ? CMD_WIN : CMD, + "-c", + command + }; + } + + private static String[] computeScript(String script, String... args) { + String[] command = { + SystemUtils.IS_OS_WINDOWS ? CMD_WIN : CMD, + script + }; + + Stream s1 = Arrays.stream(command); + Stream s2 = Arrays.stream(args); + return Stream.concat(s1, s2).toArray(String[]::new); + } + + private static List readOutput(Process process, Function streamExtractor) throws IOException { + List result = new ArrayList<>(); + String line; + try (BufferedReader stdInput = new BufferedReader(new + InputStreamReader(streamExtractor.apply(process), UTF_8))) { + while ((line = stdInput.readLine()) != null) { + result.add(line); + } + } + return result; + } + + private static void readOutputDynamic(Process process, + Function streamExtractor, + List result, + AtomicBoolean isRun, + Runnable killOrphanedProcessesRunnable) { + StringBuilder line = new StringBuilder(); + if (result == null) { + return; + } + try (BufferedReader reader = new BufferedReader(new + InputStreamReader(streamExtractor.apply(process)))) { + + while (isRun != null && isRun.get()) { + if (reader.ready()) { + int c = reader.read(); + if (c == -1) { + break; + } + if (System.lineSeparator().contains(Character.toString((char)c)) && line.length() != 0) { + result.add(line.toString()); + line.setLength(0); + } else { + line.append((char)c); + } + } else { + Thread.sleep(3000); + } + } + } catch (InterruptedException | IOException e) { + LoggingService.logError(MODULE_NAME, e.getMessage(), e); + } finally { + process.destroy(); + if (killOrphanedProcessesRunnable != null) { + killOrphanedProcessesRunnable.run(); + } + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellResultSet.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellResultSet.java new file mode 100644 index 00000000..9792a681 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellResultSet.java @@ -0,0 +1,66 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.command_line.util; + +import java.util.function.Function; + +/** + * Created by ekrylovich + * on 2/7/18. + */ +public class CommandShellResultSet { + private final E error; + private final V value; + + public CommandShellResultSet(V value, E error) { + this.value = value; + this.error = error; + } + + public E getError() { + return error; + } + + public V getValue() { + return value; + } + + public CommandShellResultSet map(Function, CommandShellResultSet> mapper){ + return mapper.apply(this); + } + + @Override + public String toString() { + return "error=" + error + + ", value=" + value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CommandShellResultSet that = (CommandShellResultSet) o; + + if (error != null ? !error.equals(that.error) : that.error != null) return false; + return value != null ? value.equals(that.value) : that.value == null; + } + + @Override + public int hashCode() { + int result = error != null ? error.hashCode() : 0; + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/ImageDownloadManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/ImageDownloadManager.java new file mode 100644 index 00000000..77937136 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/ImageDownloadManager.java @@ -0,0 +1,92 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.diagnostics; + +import com.github.dockerjava.api.model.Container; +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.process_manager.DockerUtil; +import org.eclipse.iofog.utils.Orchestrator; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.JsonObject; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.List; +import java.util.Optional; + +import static org.eclipse.iofog.utils.logging.LoggingService.logError; +import static org.eclipse.iofog.utils.logging.LoggingService.logInfo; +import static org.eclipse.iofog.utils.logging.LoggingService.logWarning; + +public class ImageDownloadManager { + + private static final String MODULE_NAME = "Image Download Manager"; + + public static void createImageSnapshot(Orchestrator orchestrator, String microserviceUuid) { + LoggingService.logDebug(MODULE_NAME, String.format("\"Start Create image snapshot \"%s :" , microserviceUuid)); + Optional containerOptional = DockerUtil.getInstance().getContainer(microserviceUuid); + String image; + if (containerOptional != null && containerOptional.isPresent()) { + Container container = containerOptional.get(); + image = container.getImage(); + } else { + logWarning(MODULE_NAME, "Image snapshot: container not running."); + return; + } + + String imageZip = microserviceUuid + ".tar.gz"; + String imagePath = "/tmp/" + imageZip; + CommandShellExecutor.executeCommand("docker save " + image + " | gzip -c > " + imagePath); + + CommandShellResultSet, List> resultSetWithPath = + CommandShellExecutor.executeCommand("readlink -f " + imagePath); + if (resultSetWithPath.getError().size() > 0) { + LoggingService.logWarning(MODULE_NAME, resultSetWithPath.toString()); + } else { + if(!resultSetWithPath.getValue().isEmpty()){ + String path = resultSetWithPath.getValue().get(0); + try { + //TODO: think about send few files + File imageFile = getFileByImagePath(path); + orchestrator.sendFileToController("image-snapshot", imageFile); + imageFile.delete(); + logInfo(MODULE_NAME, "Image snapshot " + imageFile.getName() + " deleted"); + } catch (Exception e) { + logError(MODULE_NAME, "Unable send image snapshot path", + new AgentSystemException(e.getMessage(), e)); + } + } + } + LoggingService.logDebug(MODULE_NAME, "Finished Create image snapshot"); + } + + private static File getFileByImagePath(String path) { + URL url = null; + try { + url = new URL("file://" + path); + } catch (MalformedURLException e) { + logError(MODULE_NAME, "Unable to load image", + new AgentSystemException(e.getMessage(), e)); + } + File file = null; + if ((url != null ? url.getPath() : null) != null) { + file = new File(url.getPath()); + } + return file; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceData.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceData.java new file mode 100644 index 00000000..9d774cee --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceData.java @@ -0,0 +1,96 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.diagnostics.strace; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +public class MicroserviceStraceData { + + private final String microserviceUuid; + private int pid; + private final AtomicBoolean straceRun = new AtomicBoolean(); + private List resultBuffer = new CopyOnWriteArrayList<>(); + + public MicroserviceStraceData(String microserviceUuid, int pid, boolean straceRun) { + this.microserviceUuid = microserviceUuid; + this.pid = pid; + this.straceRun.set(straceRun); + } + + public String getMicroserviceUuid() { + return microserviceUuid; + } + + public List getResultBuffer() { + return resultBuffer; + } + + public void setResultBuffer(List resultBuffer) { + this.resultBuffer = resultBuffer; + } + + public int getPid() { + return pid; + } + + public void setPid(int pid) { + this.pid = pid; + } + + public AtomicBoolean getStraceRun() { + return straceRun; + } + + public void setStraceRun(boolean straceRun) { + this.straceRun.set(straceRun); + } + + @Override + public String toString() { + return "MicroserviceStraceData{" + + "microserviceUuid='" + microserviceUuid + '\'' + + ", pid=" + pid + + ", straceRun=" + straceRun + + ", resultBuffer=" + resultBuffer + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MicroserviceStraceData that = (MicroserviceStraceData) o; + return pid == that.pid && + Objects.equals(microserviceUuid, that.microserviceUuid) && + Objects.equals(straceRun.get(), that.straceRun.get()) && + Objects.equals(resultBuffer, that.resultBuffer); + } + + @Override + public int hashCode() { + + return Objects.hash(microserviceUuid, pid, straceRun.get(), resultBuffer); + } + + public String getResultBufferAsString() { + StringBuilder stringBuilder = new StringBuilder(""); + for (String line: this.resultBuffer) { + stringBuilder.append(line).append("\n"); + } + return stringBuilder.toString(); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManager.java new file mode 100644 index 00000000..e5b8a729 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManager.java @@ -0,0 +1,150 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.diagnostics.strace; + +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.process_manager.DockerUtil; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonValue; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.CopyOnWriteArrayList; + +import static org.eclipse.iofog.utils.logging.LoggingService.logError; + +public class StraceDiagnosticManager { + + private static final String MODULE_NAME = "STrace Diagnostic Manager"; + + private final List monitoringMicroservices; + private static StraceDiagnosticManager instance = null; + + public static StraceDiagnosticManager getInstance() { + if (instance == null) { + synchronized (StraceDiagnosticManager.class) { + if (instance == null) + instance = new StraceDiagnosticManager(); + } + } + return instance; + } + + private StraceDiagnosticManager() { + this.monitoringMicroservices = new CopyOnWriteArrayList<>(); + } + + public List getMonitoringMicroservices() { + return monitoringMicroservices; + } + + public void updateMonitoringMicroservices(JsonObject diagnosticData) { + LoggingService.logDebug(MODULE_NAME, "Trying to update strace monitoring microservices"); + + if (diagnosticData !=null && diagnosticData.containsKey("straceValues")) { + JsonArray straceMicroserviceChanges = diagnosticData.getJsonArray("straceValues"); + if(straceMicroserviceChanges != null){ + for (JsonValue microserviceValue : straceMicroserviceChanges) { + JsonObject microservice = (JsonObject) microserviceValue; + if (microservice.containsKey("microserviceUuid")) { + String microserviceUuid = microservice.getString("microserviceUuid"); + boolean strace = microservice.getBoolean("straceRun"); + manageMicroservice(microserviceUuid, strace); + } + } + } + } + LoggingService.logDebug(MODULE_NAME, "Finished update strace monitoring microservices"); + } + + private void manageMicroservice(String microserviceUuid, boolean strace) { + if (strace) { + enableMicroserviceStraceDiagnostics(microserviceUuid); + } else { + disableMicroserviceStraceDiagnostics(microserviceUuid); + } + } + + private Optional getStraceDataByMicroserviceUuid(String microserviceUuid) { + LoggingService.logDebug(MODULE_NAME, "Start getting Strace Data By MicroserviceUuid : "+ microserviceUuid); + return this.monitoringMicroservices.stream() + .filter(microservice -> microservice.getMicroserviceUuid().equals(microserviceUuid)) + .findFirst(); + } + + public void enableMicroserviceStraceDiagnostics(String microserviceUuid) { + LoggingService.logInfo(MODULE_NAME, "Start enable microservice for strace diagnostics : " + microserviceUuid); + try { + int pid = getPidByContainerName(DockerUtil.getIoFogContainerName(microserviceUuid)); + MicroserviceStraceData newMicroserviceStraceData = new MicroserviceStraceData(microserviceUuid, pid, true); + this.monitoringMicroservices.removeIf( + oldMicroserviceStraceData -> oldMicroserviceStraceData.getMicroserviceUuid().equals(microserviceUuid) + ); + this.monitoringMicroservices.add(newMicroserviceStraceData); + runStrace(newMicroserviceStraceData); + } catch (IllegalArgumentException e) { + logError(MODULE_NAME, "Can't get pid of process", + new AgentSystemException("Can't get pid of process", e)); + } + LoggingService.logInfo(MODULE_NAME, "Finished enable microservice for strace diagnostics : " + microserviceUuid); + } + + public void disableMicroserviceStraceDiagnostics(String microserviceUuid) { + LoggingService.logDebug(MODULE_NAME, "Disabling microservice strace diagnostics for miroservice : " + microserviceUuid); + getStraceDataByMicroserviceUuid(microserviceUuid).ifPresent(microserviceStraceData -> { + microserviceStraceData.setStraceRun(false); + this.monitoringMicroservices.remove(microserviceStraceData); + }); + } + + private int getPidByContainerName(String containerName) throws IllegalArgumentException { + LoggingService.logDebug(MODULE_NAME, "Start getting pid of microservice by container name : "+ containerName); + CommandShellResultSet, List> resultSet = CommandShellExecutor.executeCommand("docker top " + containerName); + if (resultSet.getValue() != null && resultSet.getValue().size() > 1 && resultSet.getValue().get(1) != null) { + String pid = resultSet.getValue().get(1).split("\\s+")[1]; + LoggingService.logInfo(MODULE_NAME, "Finished getting pid of microservice by container name :" + Integer.parseInt(pid)); + return Integer.parseInt(pid); + } else { + throw new IllegalArgumentException(); + } + } + + private void runStrace(MicroserviceStraceData microserviceStraceData) { + LoggingService.logDebug(MODULE_NAME, "Start running strace "); + String straceCommand = "strace -p " + microserviceStraceData.getPid(); + CommandShellResultSet, List> resultSet = new CommandShellResultSet<>(null, microserviceStraceData.getResultBuffer()); + CommandShellExecutor.executeDynamicCommand( + straceCommand, + resultSet, + microserviceStraceData.getStraceRun(), + killOrphanedStraceProcessesRunnable() + ); + LoggingService.logDebug(MODULE_NAME, "Finished running strace "); + } + + private Runnable killOrphanedStraceProcessesRunnable() { + LoggingService.logDebug(MODULE_NAME, "killing orphaned strace processes."); + return () -> { + CommandShellResultSet, List> resultSet = CommandShellExecutor.executeCommand("pgrep strace"); + if (resultSet.getValue() != null) { + resultSet.getValue().forEach(value -> CommandShellExecutor.executeCommand(String.format("kill -9 %s", value))); + } + }; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/Display.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/Display.java new file mode 100644 index 00000000..14d9c94d --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/Display.java @@ -0,0 +1,45 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.edge_resources; + +public class Display { + + private String name; + private String icon; + private String color; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getIcon() { + return icon; + } + + public void setIcon(String icon) { + this.icon = icon; + } + + public String getColor() { + return color; + } + + public void setColor(String color) { + this.color = color; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeEndpoints.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeEndpoints.java new file mode 100644 index 00000000..4d51879f --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeEndpoints.java @@ -0,0 +1,108 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.edge_resources; + +public class EdgeEndpoints { + + private int id; + private String name; + private String description; + private String method; + private String url; + private String requestType; + private String responseType; + private String requestPayloadExample; + private String responsePayloadExample; + private int interfaceId; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getMethod() { + return method; + } + + public void setMethod(String method) { + this.method = method; + } + + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + public String getRequestType() { + return requestType; + } + + public void setRequestType(String requestType) { + this.requestType = requestType; + } + + public String getResponseType() { + return responseType; + } + + public void setResponseType(String responseType) { + this.responseType = responseType; + } + + public String getRequestPayloadExample() { + return requestPayloadExample; + } + + public void setRequestPayloadExample(String requestPayloadExample) { + this.requestPayloadExample = requestPayloadExample; + } + + public String getResponsePayloadExample() { + return responsePayloadExample; + } + + public void setResponsePayloadExample(String responsePayloadExample) { + this.responsePayloadExample = responsePayloadExample; + } + + public int getInterfaceId() { + return interfaceId; + } + + public void setInterfaceId(int interfaceId) { + this.interfaceId = interfaceId; + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeInterface.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeInterface.java new file mode 100644 index 00000000..2fffd9fb --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeInterface.java @@ -0,0 +1,47 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.edge_resources; + +import java.util.List; + +public class EdgeInterface { + + private int id; + private int edgeResourceId; + private List endpoints; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public int getEdgeResourceId() { + return edgeResourceId; + } + + public void setEdgeResourceId(int edgeResourceId) { + this.edgeResourceId = edgeResourceId; + } + + public List getEndpoints() { + return endpoints; + } + + public void setEndpoints(List endpoints) { + this.endpoints = endpoints; + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResource.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResource.java new file mode 100644 index 00000000..5d73ffd3 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResource.java @@ -0,0 +1,121 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.edge_resources; + +import java.util.Map; +import java.util.Objects; + +public class EdgeResource { + + private int id; + private String name; + private Map custom; + private String description; + private String version; + private String interfaceProtocol; + private Display display; + private String[] orchestrationTags; + private EdgeInterface edgeInterface; + + public EdgeResource(int id, String name, String version){ + this.id = id; + this.name = name; + this.version = version; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getInterfaceProtocol() { + return interfaceProtocol; + } + + public void setInterfaceProtocol(String interfaceProtocol) { + this.interfaceProtocol = interfaceProtocol; + } + + public Display getDisplay() { + return display; + } + + public void setDisplay(Display display) { + this.display = display; + } + + public String[] getOrchestrationTags() { + return orchestrationTags; + } + + public void setOrchestrationTags(String[] orchestrationTags) { + this.orchestrationTags = orchestrationTags; + } + + public EdgeInterface getEdgeInterface() { + return edgeInterface; + } + + public void setEdgeInterface(EdgeInterface edgeInterface) { + this.edgeInterface = edgeInterface; + } + + public Map getCustom() { + return custom; + } + + public void setCustom(Map custom) { + this.custom = custom; + } + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (! (o instanceof EdgeResource)) return false; + EdgeResource that = (EdgeResource) o; + return Objects.equals(this.name, that.name) && + Objects.equals(this.version, that.version); + } + + @Override + public int hashCode() { + return Objects.hash(this.name, this.version); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResourceManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResourceManager.java new file mode 100644 index 00000000..1ca0541c --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResourceManager.java @@ -0,0 +1,75 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.edge_resources; + + +import org.eclipse.iofog.resource_consumption_manager.ResourceConsumptionManager; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class EdgeResourceManager { + private List latestEdgeResources = new ArrayList<>(); + private List currentEdgeResources = new ArrayList<>(); + private static final String MODULE_NAME = "EdgeResource Manager"; + private static EdgeResourceManager instance; + + private EdgeResourceManager() { + } + + public static EdgeResourceManager getInstance() { + if (instance == null) { + synchronized (EdgeResourceManager.class) { + if (instance == null) + instance = new EdgeResourceManager(); + } + } + return instance; + } + public List getLatestEdgeResources() { + synchronized (EdgeResource.class) { + return Collections.unmodifiableList(latestEdgeResources); + } + } + + public void setLatestEdgeResources(List latestEdgeResources) { + synchronized (EdgeResource.class) { + this.latestEdgeResources = new ArrayList<>(latestEdgeResources); + } + } + + public List getCurrentEdgeResources() { + synchronized (EdgeResource.class) { + return Collections.unmodifiableList(currentEdgeResources); + } + } + + public void setCurrentEdgeResources(List currentEdgeResources) { + synchronized (EdgeResource.class) { + this.currentEdgeResources = new ArrayList<>(currentEdgeResources); + } + } + + public void clear() { + LoggingService.logDebug(MODULE_NAME ,"Start clearing EdgeResources, size of latestEdgeResources and " + + "currentEdgeResources is respectively : " + latestEdgeResources.size() + " , " + currentEdgeResources.size()); + synchronized (EdgeResource.class) { + latestEdgeResources.clear(); + currentEdgeResources.clear(); + } + LoggingService.logDebug(MODULE_NAME ,"Finished clearing EdgeResources"); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentException.java new file mode 100644 index 00000000..73e2a06a --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentException.java @@ -0,0 +1,33 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.exception; + +/** + * Agent Exception + * @author nehanaithani + * + */ +public class AgentException extends Exception{ + + + private static final long serialVersionUID = 1L; + + public AgentException(String message, Throwable innerException) { + super(message, innerException); + } + + public AgentException(String message) { + super(message); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentSystemException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentSystemException.java new file mode 100644 index 00000000..9e525b45 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentSystemException.java @@ -0,0 +1,32 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.exception; + +/** + * Agent system Exception + * @author nehanaithani + * + */ +public class AgentSystemException extends AgentException{ + + private static final long serialVersionUID = 1L; + + public AgentSystemException(String message, Throwable cause) { + super(message, cause); + } + + public AgentSystemException(String message) { + super(message); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentUserException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentUserException.java new file mode 100644 index 00000000..3f94a4b4 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentUserException.java @@ -0,0 +1,32 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.exception; + +/** + * Agent system Exception + * @author nehanaithani + * + */ +public class AgentUserException extends AgentException{ + + private static final long serialVersionUID = 1L; + + public AgentUserException(String message, Throwable cause) { + super(message, cause); + } + + public AgentUserException(String message) { + super(message); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgent.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgent.java new file mode 100644 index 00000000..21ae19ba --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgent.java @@ -0,0 +1,1802 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.field_agent; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.commons.lang.SystemUtils; +import org.eclipse.iofog.IOFogModule; +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.diagnostics.ImageDownloadManager; +import org.eclipse.iofog.diagnostics.strace.MicroserviceStraceData; +import org.eclipse.iofog.diagnostics.strace.StraceDiagnosticManager; +import org.eclipse.iofog.edge_resources.*; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.enums.RequestType; +import org.eclipse.iofog.local_api.LocalApi; +import org.eclipse.iofog.message_bus.MessageBus; +import org.eclipse.iofog.microservice.*; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.process_manager.ProcessManager; +import org.eclipse.iofog.proxy.SshConnection; +import org.eclipse.iofog.proxy.SshProxyManager; +import org.eclipse.iofog.pruning.DockerPruningManager; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.Orchestrator; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.functional.Pair; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.*; +import javax.net.ssl.SSLHandshakeException; +import javax.ws.rs.ForbiddenException; +import javax.ws.rs.HttpMethod; +import java.io.*; +import java.net.*; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.security.MessageDigest; +import java.security.cert.CertificateException; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import static io.netty.util.internal.StringUtil.isNullOrEmpty; +import static java.nio.charset.StandardCharsets.UTF_8; +import static java.nio.file.LinkOption.NOFOLLOW_LINKS; +import static java.util.stream.Collectors.toList; +import static org.apache.commons.lang.StringUtils.rightPad; +import static org.eclipse.iofog.command_line.CommandLineConfigParam.*; +import static org.eclipse.iofog.resource_manager.ResourceManager.*; +import static org.eclipse.iofog.utils.CmdProperties.*; +import static org.eclipse.iofog.utils.Constants.*; +import static org.eclipse.iofog.utils.Constants.ControllerStatus.*; + +/** + * Field Agent module + * + * @author saeid + */ +public class FieldAgent implements IOFogModule { + + private final String MODULE_NAME = "Field Agent"; + private final String filesPath = SystemUtils.IS_OS_WINDOWS ? SNAP_COMMON + "./etc/iofog-agent/" : SNAP_COMMON + "/etc/iofog-agent/"; + + private Orchestrator orchestrator; + private SshProxyManager sshProxyManager; + private long lastGetChangesList; + private MicroserviceManager microserviceManager; + private static FieldAgent instance; + private boolean initialization; + private boolean connected = false; + private ReentrantLock provisioningLock = new ReentrantLock(); + private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5); + private ScheduledFuture futureTask; + private EdgeResourceManager edgeResourceManager; + + private FieldAgent() { + lastGetChangesList = 0; + initialization = true; + } + + @Override + public int getModuleIndex() { + return FIELD_AGENT; + } + + @Override + public String getModuleName() { + return MODULE_NAME; + } + + public static FieldAgent getInstance() { + if (instance == null) { + synchronized (FieldAgent.class) { + if (instance == null) + instance = new FieldAgent(); + } + } + return instance; + } + + /** + * creates IOFog status report + * + * @return Map + */ + private JsonObject getFogStatus() { + logDebug("get Fog Status"); + return Json.createObjectBuilder() + .add("daemonStatus", StatusReporter.getSupervisorStatus().getDaemonStatus().toString() == null ? + "UNKNOWN" : StatusReporter.getSupervisorStatus().getDaemonStatus().toString()) + .add("daemonOperatingDuration", StatusReporter.getSupervisorStatus().getOperationDuration()) + .add("daemonLastStart", StatusReporter.getSupervisorStatus().getDaemonLastStart()) + .add("memoryUsage", StatusReporter.getResourceConsumptionManagerStatus().getMemoryUsage()) + .add("diskUsage", StatusReporter.getResourceConsumptionManagerStatus().getDiskUsage()) + .add("cpuUsage", StatusReporter.getResourceConsumptionManagerStatus().getCpuUsage()) + .add("memoryViolation", StatusReporter.getResourceConsumptionManagerStatus().isMemoryViolation()) + .add("diskViolation", StatusReporter.getResourceConsumptionManagerStatus().isDiskViolation()) + .add("cpuViolation", StatusReporter.getResourceConsumptionManagerStatus().isCpuViolation()) + .add("systemAvailableDisk", StatusReporter.getResourceConsumptionManagerStatus().getAvailableDisk()) + .add("systemAvailableMemory", StatusReporter.getResourceConsumptionManagerStatus().getAvailableMemory()) + .add("systemTotalCpu", StatusReporter.getResourceConsumptionManagerStatus().getTotalCpu()) + .add("microserviceStatus", StatusReporter.getProcessManagerStatus().getJsonMicroservicesStatus() == null ? + Json.createObjectBuilder().add("status","UNKNOWN").build().toString() : + StatusReporter.getProcessManagerStatus().getJsonMicroservicesStatus()) + .add("repositoryCount", StatusReporter.getProcessManagerStatus().getRegistriesCount()) + .add("repositoryStatus", StatusReporter.getProcessManagerStatus().getJsonRegistriesStatus() == null ? + "UNKNOWN" : StatusReporter.getProcessManagerStatus().getJsonRegistriesStatus()) + .add("systemTime", StatusReporter.getStatusReporterStatus().getSystemTime()) + .add("lastStatusTime", StatusReporter.getStatusReporterStatus().getLastUpdate()) + .add("ipAddress", IOFogNetworkInterfaceManager.getInstance().getCurrentIpAddress() == null ? + "UNKNOWN" : IOFogNetworkInterfaceManager.getInstance().getCurrentIpAddress()) + .add("ipAddressExternal", Configuration.getIpAddressExternal() == null ? + "UNKNOWN" : Configuration.getIpAddressExternal()) + .add("processedMessages", StatusReporter.getMessageBusStatus().getProcessedMessages()) + .add("microserviceMessageCounts", StatusReporter.getMessageBusStatus().getJsonPublishedMessagesPerMicroservice() == null ? + "UNKNOWN" : StatusReporter.getMessageBusStatus().getJsonPublishedMessagesPerMicroservice()) + .add("messageSpeed", StatusReporter.getMessageBusStatus().getAverageSpeed()) + .add("lastCommandTime", StatusReporter.getFieldAgentStatus().getLastCommandTime()) + .add("tunnelStatus", StatusReporter.getSshManagerStatus().getJsonProxyStatus() == null ? + "UNKNOWN" : StatusReporter.getSshManagerStatus().getJsonProxyStatus()) + .add("version", getVersion() == null ? + "UNKNOWN" : getVersion()) + .add("isReadyToUpgrade", StatusReporter.getFieldAgentStatus().isReadyToUpgrade()) + .add("isReadyToRollback", StatusReporter.getFieldAgentStatus().isReadyToRollback()) + .build(); + } + + /** + * executes actions after successful status post request + */ + private void onPostStatusSuccess() { + StatusReporter.getProcessManagerStatus().removeNotRunningMicroserviceStatus(); + } + + /** + * checks if IOFog is not provisioned + * + * @return boolean + */ + private boolean notProvisioned() { + logDebug("Started checking provisioned"); + boolean notProvisioned = StatusReporter.getFieldAgentStatus().getControllerStatus().equals(NOT_PROVISIONED); + if (notProvisioned) { + logWarning("Not provisioned"); + } + logDebug("Finished checking provisioned : " + notProvisioned); + return notProvisioned; + } + + /** + * sends IOFog instance status to IOFog controller + */ + private void postStatusHelper() { + logDebug("posting ioFog status"); + try { + JsonObject status = getFogStatus(); + if (Configuration.debugging) { + logInfo(status.toString()); + } + connected = isControllerConnected(false); + if (!connected) + return; + + orchestrator.request("status", RequestType.PUT, null, status); + onPostStatusSuccess(); + } catch (CertificateException | SSLHandshakeException | ConnectException e) { + verificationFailed(e); + logError("Unable to send status due to broken certificate", + new AgentSystemException(e.getMessage(), e)); + } catch (ForbiddenException e) { + deProvision(true); + logError("Unable to send status due to broken certificate", + new AgentSystemException(e.getMessage(), e)); + }catch (SocketTimeoutException e) { + try { + IOFogNetworkInterfaceManager.getInstance().updateIOFogNetworkInterface(); + } catch (SocketException | MalformedURLException ex) { + logError("Unable to update Network interface", new AgentSystemException(ex.getMessage(), ex)); + } + } catch (Exception e) { + logError("Unable to send status ", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finished posting ioFog status"); + } + + private final Runnable postStatus = () -> { + while (true) { + try { + if (microserviceManager.getCurrentMicroservices().size() == StatusReporter.getProcessManagerStatus().getRunningMicroservicesCount()) { + Thread.sleep(Configuration.getStatusFrequency() * 1000); + } else { + Thread.sleep(1 * 1000); + ProcessManager.getInstance().updateMicroserviceStatus(); + } + postStatusHelper(); + } catch (Exception e) { + logError("Unable to send status ", new AgentSystemException(e.getMessage(), e)); + } + + } + }; + + private final Runnable postDiagnostics = () -> { + while (true) { + logDebug("Start posting diagnostic"); + if (StraceDiagnosticManager.getInstance().getMonitoringMicroservices().size() > 0) { + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonArrayBuilder arrayBuilder = factory.createArrayBuilder(); + + for (MicroserviceStraceData microservice : StraceDiagnosticManager.getInstance().getMonitoringMicroservices()) { + arrayBuilder.add(factory.createObjectBuilder() + .add("microserviceUuid", microservice.getMicroserviceUuid()) + .add("buffer", microservice.getResultBufferAsString()) + ); + microservice.getResultBuffer().clear(); + } + + JsonObject json = factory.createObjectBuilder() + .add("straceData", arrayBuilder).build(); + + try { + orchestrator.request("strace", RequestType.PUT, null, json); + } catch (Exception e) { + logError("Unable send strace logs", new AgentSystemException("Unable send strace logs", e)); + } + } + + try { + Thread.sleep(Configuration.getPostDiagnosticsFreq() * 1000); + } catch (InterruptedException e) { + logError("Error posting diagnostic", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finished posting diagnostic"); + } + }; + + public final void postTracking(JsonObject events) { + logDebug("Start posting tracking"); + try { + orchestrator.request("tracking", RequestType.POST, null, events); + } catch (Exception e) { + logError("Unable send tracking logs", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finished posting tracking"); + } + + /** + * logs and sets appropriate status when controller + * certificate is not verified + */ + private void verificationFailed(Exception e) { + logDebug("Start verification Failed of controller"); + connected = false; + if (!notProvisioned()) { + ControllerStatus controllerStatus; + if (e instanceof CertificateException || e instanceof SSLHandshakeException) { + controllerStatus = BROKEN_CERTIFICATE; + } else { + controllerStatus = NOT_CONNECTED; + } + StatusReporter.setFieldAgentStatus().setControllerStatus(controllerStatus); + logWarning("controller verification failed: " + controllerStatus.name()); + } + StatusReporter.setFieldAgentStatus().setControllerVerified(false); + logDebug("Finished verification Failed of Controller"); + } + + private final Future processChanges(JsonObject changes) { + ExecutorService executor = Executors.newSingleThreadExecutor(); + return executor.submit(() -> { + boolean resetChanges = true; + + if (changes.getBoolean("deleteNode",false) && !initialization) { + try { + deleteNode(); + } catch (Exception e) { + logError("Unable to delete node", e); + resetChanges = false; + } + } else { + if (changes.getBoolean("reboot",false) && !initialization) { + try { + reboot(); + } catch (Exception e) { + logError("Unable to perform reboot", e); + resetChanges = false; + } + } + if (changes.getBoolean("isImageSnapshot",false) && !initialization) { + try { + createImageSnapshot(); + } catch (Exception e) { + logError("Unable to create snapshot", e); + resetChanges = false; + } + } + if (changes.getBoolean("config",false) && !initialization) { + try { + getFogConfig(); + } catch (Exception e) { + logError("Unable to get config", e); + resetChanges = false; + } + } + if (changes.getBoolean("version",false) && !initialization) { + try { + changeVersion(); + } catch (Exception e) { + logError("Unable to change version", e); + resetChanges = false; + } + } + if (changes.getBoolean("registries",false) || initialization) { + try { + loadRegistries(false); + ProcessManager.getInstance().update(); + } catch (Exception e) { + logError("Unable to update registries", e); + resetChanges = false; + } + } + if (changes.getBoolean("prune", false) && !initialization) { + try { + DockerPruningManager.getInstance().pruneAgent(); + } catch (Exception e) { + logError("Unable to update registries", e); + resetChanges = false; + } + } + if (changes.getBoolean("microserviceConfig",false) || changes.getBoolean("microserviceList",false) || + changes.getBoolean("routing",false) || initialization) { + boolean microserviceConfig = changes.getBoolean("microserviceConfig"); + boolean routing = changes.getBoolean("routing"); + int defaultFreq = Configuration.getStatusFrequency(); + Configuration.setStatusFrequency(1); + try { + List microservices = loadMicroservices(false); + + if (microserviceConfig) { + try { + processMicroserviceConfig(microservices); + LocalApi.getInstance().update(); + } catch (Exception e) { + logError("Unable to update microservices config", e); + resetChanges = false; + } + } + + if (routing) { + try { + processRoutes(microservices); + if (!changes.getBoolean("routerChanged",false) || initialization) { + MessageBus.getInstance().update(); + } + } catch (Exception e) { + logError("Unable to update microservices routes", e); + resetChanges = false; + } + } + } catch (Exception e) { + logError("Unable to get microservices list", e); + resetChanges = false; + } finally { + Configuration.setStatusFrequency(defaultFreq); + } + } + + if (changes.getBoolean("tunnel",false) && !initialization) { + try { + sshProxyManager.update(getProxyConfig()); + } catch (Exception e) { + logError("Unable to create tunnel", e); + resetChanges = false; + } + } + if (changes.getBoolean("diagnostics",false) && !initialization) { + try { + updateDiagnostics(); + } catch (Exception e) { + logError("Unable to update diagnostics", e); + resetChanges = false; + } + } + if (changes.getBoolean("routerChanged",false) && !initialization) { + try { + MessageBus.getInstance().update(); + } catch (Exception e) { + logError("Unable to update router info", e); + resetChanges = false; + } + } + if (changes.getBoolean("linkedEdgeResources",false) && !initialization) { + boolean linkedEdgeResources = changes.getBoolean("linkedEdgeResources"); + try { + if (linkedEdgeResources) { + loadEdgeResources(false); + LocalApi.getInstance().updateEdgeResource(); + } + } catch (Exception e) { + logError("Unable to update linked edge resources", e); + resetChanges = false; + } + } + } + return resetChanges; + }); + } + + /** + * Loads edge resources from json file + * @return + */ + private JsonArray loadEdgeResourcesJsonFile() { + String filename = EDGE_RESOURCE_FILE; + JsonArray edgeResourcesJson = readFile(filesPath + filename); + return edgeResourcesJson; + } + + /** + * Loads edge resources from file or get from Controller + * @param fromFile + * @return + */ + private List loadEdgeResources(boolean fromFile) { + { + logDebug("Start Loading edge resources..."); + List edgeResourcesList = new ArrayList<>(); + if (notProvisioned() || !isControllerConnected(fromFile)) { + return edgeResourcesList; + } + + String filename = EDGE_RESOURCE_FILE; + JsonArray edgeResourcesJson = null; + try { + if (fromFile) { + edgeResourcesJson = readFile(filesPath + filename); + if (edgeResourcesJson == null) { + return loadEdgeResources(false); + } + } else { + JsonObject result = orchestrator.request("edgeResources", RequestType.GET, null, null); + if(result.containsKey("edgeResources")) { + edgeResourcesJson = result.getJsonArray("edgeResources"); + saveFile(edgeResourcesJson, filesPath + filename); + } else { + logError("Error loading edgeResources from IOFog controller", + new AgentUserException("Error loading microservices from IOFog controller")); + } + } + try { + if (edgeResourcesJson != null){ + List edgeResources = IntStream.range(0, edgeResourcesJson.size()) + .boxed() + .map(edgeResourcesJson::getJsonObject) + .map(containerJsonObjectToEdgeResourcesFunction()) + .collect(toList()); + edgeResourceManager.setLatestEdgeResources(edgeResources); + edgeResourcesList.addAll(edgeResources); + logDebug("Edge resource list size : " + edgeResourcesList.size()); + } + } catch (Exception e) { + logError("Unable to parse edgeResources", new AgentSystemException(e.getMessage(), e)); + } + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + logError("Unable to get edgeResources due to broken certificate", + new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + logError("Unable to get edgeResources", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finished loading edge resources..."); + return edgeResourcesList; + } + } + + /** + * maps json object to EdgeResources + * @return + */ + private Function containerJsonObjectToEdgeResourcesFunction() { + return jsonObj -> { + EdgeResource edgeResource = new EdgeResource(jsonObj.getInt("id"), jsonObj.getString("name"), jsonObj.getString("version")); + JsonObject customData = jsonObj.getJsonObject("custom"); + try { + if (customData != null && !customData.getValueType().equals(JsonValue.ValueType.NULL) && customData.size() > 0) { + HashMap customMap = new ObjectMapper().readValue( + customData.toString(), + new TypeReference>() { + }); + edgeResource.setCustom(customMap); + } + } catch (Exception e) { + logError("Error mapping custom field of edgeResources", new AgentSystemException(e.getMessage(), e)); + } + Display display = new Display(); + EdgeInterface edgeInterface = new EdgeInterface(); + edgeResource.setDescription(jsonObj.getString("description", null)); + edgeResource.setInterfaceProtocol(jsonObj.getString("interfaceProtocol", null)); + JsonArray tags = jsonObj.getJsonArray("orchestrationTags"); + String[] orchestrationTags = null; + if (tags != null && !tags.getValueType().equals(JsonValue.ValueType.NULL) && tags.size() > 0) { + List result = IntStream.range(0, tags.size()) + .boxed() + .map(tags::getString) + .collect(Collectors.toList()); + orchestrationTags = result.toArray(new String[result.size()]); + } + + edgeResource.setOrchestrationTags(orchestrationTags); + JsonObject displayValue = jsonObj.getJsonObject("display"); + if (displayValue != null && !displayValue.getValueType().equals(JsonValue.ValueType.NULL)) { + display.setName(displayValue.getString("name", null)); + display.setIcon(displayValue.getString("icon", null)); + display.setColor(displayValue.getString("color", null)); + } + + edgeResource.setDisplay(display); + JsonObject interfaceObj = jsonObj.getJsonObject("interface"); + if (interfaceObj != null && !interfaceObj.getValueType().equals(JsonValue.ValueType.NULL)) { + edgeInterface.setEdgeResourceId(interfaceObj.getInt("edgeResourceId")); + edgeInterface.setId(interfaceObj.getInt("id")); + JsonArray endpointsArray = interfaceObj.getJsonArray("endpoints"); + if (endpointsArray != null && !endpointsArray.getValueType().equals(JsonValue.ValueType.NULL)) { + List edgeEndpoints = endpointsArray.size() > 0 + ? IntStream.range(0, endpointsArray.size()) + .boxed() + .map(endpointsArray::getJsonObject) + .map(endpoint -> { + EdgeEndpoints edgeEndPoint = new EdgeEndpoints(); + edgeEndPoint.setDescription(endpoint.getString("description", null)); + edgeEndPoint.setId(endpoint.getInt("id")); + edgeEndPoint.setInterfaceId(endpoint.getInt("interfaceId")); + edgeEndPoint.setMethod(endpoint.getString("method", null)); + edgeEndPoint.setName(endpoint.getString("name", null)); + edgeEndPoint.setRequestPayloadExample(endpoint.getString("requestPayloadExample", null)); + edgeEndPoint.setRequestType(endpoint.getString("requestType", null)); + edgeEndPoint.setResponseType(endpoint.getString("responseType", null)); + edgeEndPoint.setResponsePayloadExample(endpoint.getString("responsePayloadExample", null)); + edgeEndPoint.setUrl(endpoint.getString("url", null)); + return edgeEndPoint; + }) + .collect(toList()) + : null; + edgeInterface.setEndpoints(edgeEndpoints); + } + } + edgeResource.setEdgeInterface(edgeInterface); + return edgeResource; + }; + } + + /** + * retrieves IOFog changes list from IOFog controller + */ + private final Runnable getChangesList = () -> { + while (true) { + try { + int frequency = Configuration.getChangeFrequency() * 1000; + Thread.sleep(frequency); + logDebug("Start get IOFog changes list from IOFog controller"); + + if (notProvisioned() || !isControllerConnected(false)) { + logDebug("Cannot get change list due to controller status not provisioned or controller not connected"); + continue; + } + + + JsonObject result; + try { + result = orchestrator.request("config/changes", RequestType.GET, null, null); + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + logError("Unable to get changes due to broken certificate", + new AgentSystemException(e.getMessage(), e)); + continue; + } catch (SocketTimeoutException e) { + IOFogNetworkInterfaceManager.getInstance().updateIOFogNetworkInterface(); + continue; + } catch (Exception e) { + logError("Unable to get changes ", new AgentSystemException(e.getMessage(), e)); + continue; + } + + + StatusReporter.setFieldAgentStatus().setLastCommandTime(lastGetChangesList); + + String lastUpdated = result.getString("lastUpdated", null); + boolean resetChanges; + Future changesProcessor = processChanges(result); + + try { + resetChanges = changesProcessor.get(30, TimeUnit.SECONDS); + } catch (Exception e) { + resetChanges = false; + changesProcessor.cancel(true); + } + + if (lastUpdated != null && resetChanges) { + logDebug("Resetting config changes flags"); + try { + JsonObject req = Json.createObjectBuilder() + .add("lastUpdated", lastUpdated) + .build(); + orchestrator.request("config/changes", RequestType.PATCH, null, req); + } catch (Exception e) { + logError("Resetting config changes has failed", e); + } + } + + initialization = initialization && !resetChanges; + } catch (Exception e) { + logError("Error getting changes list ", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finish get IOFog changes list from IOFog controller"); + } + }; + + /** + * Deletes current fog node from controller and makes deprovision + */ + private void deleteNode() { + logDebug("start deleting current fog node from controller and make it deprovision"); + try { + orchestrator.request("delete-node", RequestType.DELETE, null, null); + } catch (Exception e) { + logError("Can't send delete node command", + new AgentSystemException("Can't send delete node command", e)); + } + deProvision(false); + logDebug("Finish deleting current fog node from controller and make it deprovision"); + } + + /** + * Remote reboot of Linux machine from IOFog controller + */ + private void reboot() { + logInfo("start Remote reboot of Linux machine from IOFog controller"); + LoggingService.logInfo(MODULE_NAME, "Rebooting"); + if (SystemUtils.IS_OS_WINDOWS) { + return; // TODO implement + } + + CommandShellResultSet, List> result = CommandShellExecutor.executeCommand("shutdown -r now"); + if (result == null) { + LoggingService.logError(MODULE_NAME, "Error in Remote reboot of Linux machine from IOFog controller", + new AgentSystemException("Error in Remote reboot of Linux machine from IOFog controller")); + } + if (result != null && result.getError().size() > 0) { + LoggingService.logWarning(MODULE_NAME, result.toString()); + } + logInfo("Finished Remote reboot of Linux machine from IOFog controller"); + } + + /** + * performs change version operation, received from ioFog controller + */ + private void changeVersion() { + LoggingService.logInfo(MODULE_NAME, "Starting change version action"); + + if (notProvisioned() || !isControllerConnected(false)) { + return; + } + + try { + JsonObject result = orchestrator.request("version", RequestType.GET, null, null); + VersionHandler.changeVersion(result); + + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + LoggingService.logError(MODULE_NAME, "Unable to get version command due to broken certificate", + new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Unable to get version command", + new AgentSystemException(e.getMessage(), e)); + } + LoggingService.logInfo(MODULE_NAME, "Finished change version operation, received from ioFog controller"); + } + + private void updateDiagnostics() { + LoggingService.logInfo(MODULE_NAME, "Start update diagnostics"); + if (notProvisioned() || !isControllerConnected(false)) { + return; + } + + if (SystemUtils.IS_OS_WINDOWS) { + return; // TODO implement + } + + try { + JsonObject result = orchestrator.request("strace", RequestType.GET, null, null); + + StraceDiagnosticManager.getInstance().updateMonitoringMicroservices(result); + + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + LoggingService.logError(MODULE_NAME, "Unable to get diagnostics update due to broken certificate", + new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Unable to get diagnostics update", + new AgentSystemException(e.getMessage(), e)); + } + LoggingService.logInfo(MODULE_NAME, "Finished update diagnostics"); + } + + /** + * gets list of registries from file or IOFog controller + * + * @param fromFile - load from file + */ + private void loadRegistries(boolean fromFile) { + logDebug("get registries"); + if (notProvisioned() || !isControllerConnected(fromFile)) { + return; + } + + String filename = "registries.json"; + try { + JsonArray registriesList = null; + if (fromFile) { + registriesList = readFile(filesPath + filename); + if (registriesList == null) { + loadRegistries(false); + return; + } + } else { + JsonObject result = orchestrator.request("registries", RequestType.GET, null, null); + if(result.containsKey("registries")) { + registriesList = result.getJsonArray("registries"); + saveFile(registriesList, filesPath + filename); + } else { + logError("Error loading registries from IOFog controller", + new AgentUserException("Error loading registries from IOFog controller")); + } + } + List registries = new ArrayList<>(); + if (registriesList != null && registriesList.size() != 0) { + for (int i = 0; i < registriesList.size(); i++) { + JsonObject registry = registriesList.getJsonObject(i); + Registry.RegistryBuilder registryBuilder = new Registry.RegistryBuilder() + .setId(registry.getInt("id")) + .setUrl(registry.getString("url")) + .setIsPublic(registry.getBoolean("isPublic", false)); + if (!registry.getBoolean("isPublic", false)) { + registryBuilder.setUserName(registry.getString("username")) + .setPassword(registry.getString("password")) + .setUserEmail(registry.getString("userEmail")); + + } + registries.add(registryBuilder.build()); + } + microserviceManager.setRegistries(registries); + } else { + logInfo("Registries list is empty"); + } + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + logError("Unable to get registries due to broken certificate", + new AgentUserException(e.getMessage(), e)); + } catch (AgentUserException e) { + logError("Unable to get registries", + new AgentUserException(e.getMessage(), e)); + } catch (AgentSystemException e) { + logError("Unable to get registries", + new AgentUserException(e.getMessage(), e)); + } catch (Exception e) { + logError("Unable to get registries", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finished get registries"); + } + + /** + * gets list of Microservice configurations from file or IOFog controller + */ + private void processMicroserviceConfig(List microservices) { + logDebug("Start process microservice configuration"); + Map configs = new HashMap<>(); + for (Microservice microservice : microservices) { + configs.put(microservice.getMicroserviceUuid(), microservice.getConfig()); + } + + microserviceManager.setConfigs(configs); + logDebug("Finished process microservice configuration"); + } + + /** + * gets list of Microservice routings from file or IOFog controller + */ + private void processRoutes(List microservices) { + Map routes = new HashMap<>(); + for (Microservice microservice : microservices) { + List jsonRoutes = microservice.getRoutes(); + if (jsonRoutes == null || jsonRoutes.size() == 0) { + continue; + } + + String microserviceUuid = microservice.getMicroserviceUuid(); + Route microserviceRoute = new Route(); + + for (String jsonRoute : jsonRoutes) { + microserviceRoute.getReceivers().add(jsonRoute); + } + + routes.put(microserviceUuid, microserviceRoute); + } + + microserviceManager.setRoutes(routes); + logDebug("Finished process routes"); + } + + private JsonArray loadMicroservicesJsonFile() { + String filename = MICROSERVICE_FILE; + JsonArray microservicesJson = readFile(filesPath + filename); + return microservicesJson; + } + + /** + * gets list of Microservices from file or IOFog controller + * + * @param fromFile - load from file + */ + private List loadMicroservices(boolean fromFile) { + logDebug("Start Loading microservices..."); + List microserviceList = new ArrayList<>(); + if (notProvisioned() || !isControllerConnected(fromFile)) { + return microserviceList; + } + + String filename = MICROSERVICE_FILE; + JsonArray microservicesJson = null; + try { + if (fromFile) { + microservicesJson = readFile(filesPath + filename); + if (microservicesJson == null) { + return loadMicroservices(false); + } + } else { + JsonObject result = orchestrator.request("microservices", RequestType.GET, null, null); + if(result.containsKey("microservices")) { + microservicesJson = result.getJsonArray("microservices"); + saveFile(microservicesJson, filesPath + filename); + } else { + logError("Error loading microservices from IOFog controller", + new AgentUserException("Error loading microservices from IOFog controller")); + } + } + try { + if (microservicesJson != null){ + List microservices = IntStream.range(0, microservicesJson.size()) + .boxed() + .map(microservicesJson::getJsonObject) + .map(containerJsonObjectToMicroserviceFunction()) + .collect(toList()); + microserviceManager.setLatestMicroservices(microservices); + microserviceList.addAll(microservices); + } + } catch (Exception e) { + logError("Unable to parse microservices", new AgentSystemException(e.getMessage(), e)); + } + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + logError("Unable to get microservices due to broken certificate", + new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + logError("Unable to get microservices", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finished Loading microservices..."); + return microserviceList; + } + + private List getStringList(JsonValue jsonValue) { + if (jsonValue != null && !jsonValue.getValueType().equals(JsonValue.ValueType.NULL)) { + JsonArray valueObj = (JsonArray) jsonValue; + return valueObj.size() > 0 + ? IntStream.range(0, valueObj.size()) + .boxed() + .map(valueObj::getString) + .collect(toList()) + : null; + } + + return null; + } + + private Function containerJsonObjectToMicroserviceFunction() { + return jsonObj -> { + Microservice microservice = new Microservice(jsonObj.getString("uuid"), jsonObj.getString("imageId")); + microservice.setConfig(jsonObj.getString("config")); + microservice.setRebuild(jsonObj.getBoolean("rebuild")); + microservice.setRootHostAccess(jsonObj.getBoolean("rootHostAccess")); + microservice.setRegistryId(jsonObj.getInt("registryId")); + microservice.setLogSize(jsonObj.getJsonNumber("logSize").longValue()); + microservice.setDelete(jsonObj.getBoolean("delete")); + microservice.setDeleteWithCleanup(jsonObj.getBoolean("deleteWithCleanup")); + + JsonValue routesValue = jsonObj.get("routes"); + microservice.setRoutes(getStringList(routesValue)); + + microservice.setConsumer(jsonObj.getBoolean("isConsumer")); + + JsonValue portMappingValue = jsonObj.get("portMappings"); + if (!portMappingValue.getValueType().equals(JsonValue.ValueType.NULL)) { + JsonArray portMappingObjs = (JsonArray) portMappingValue; + List pms = portMappingObjs.size() > 0 + ? IntStream.range(0, portMappingObjs.size()) + .boxed() + .map(portMappingObjs::getJsonObject) + .map(portMapping -> new PortMapping(portMapping.getInt("portExternal"), + portMapping.getInt("portInternal"), + portMapping.getBoolean("isUdp", false))) + .collect(toList()) + : null; + + microservice.setPortMappings(pms); + } + + JsonValue volumeMappingValue = jsonObj.get("volumeMappings"); + if (!volumeMappingValue.getValueType().equals(JsonValue.ValueType.NULL)) { + JsonArray volumeMappingObj = (JsonArray) volumeMappingValue; + List vms = volumeMappingObj.size() > 0 + ? IntStream.range(0, volumeMappingObj.size()) + .boxed() + .map(volumeMappingObj::getJsonObject) + .map(volumeMapping -> { + VolumeMappingType volumeMappingType = volumeMapping.getString("type", "bind").equals("volume") ? VolumeMappingType.VOLUME : VolumeMappingType.BIND; + return new VolumeMapping(volumeMapping.getString("hostDestination"), + volumeMapping.getString("containerDestination"), + volumeMapping.getString("accessMode"), + volumeMappingType); + }).collect(toList()) : null; + + microservice.setVolumeMappings(vms); + } + + JsonValue envVarsValue = jsonObj.get("env"); + if (envVarsValue != null && !envVarsValue.getValueType().equals(JsonValue.ValueType.NULL)) { + JsonArray envVarsObjs = (JsonArray) envVarsValue; + List envs = envVarsObjs.size() > 0 + ? IntStream.range(0, envVarsObjs.size()) + .boxed() + .map(envVarsObjs::getJsonObject) + .map(env -> new EnvVar(env.getString("key"), + env.getString("value"))) + .collect(toList()) + : null; + + microservice.setEnvVars(envs); + } + + JsonValue argsValue = jsonObj.get("cmd"); + microservice.setArgs(getStringList(argsValue)); + + JsonValue extraHostsValue = jsonObj.get("extraHosts"); + microservice.setExtraHosts(getStringList(extraHostsValue)); + + try { + LoggingService.setupMicroserviceLogger(microservice.getMicroserviceUuid(), microservice.getLogSize()); + } catch (IOException e) { + logError("Error at setting up microservice logger", + new AgentSystemException(e.getMessage(), e)); + } + return microservice; + }; + } + + /** + * pings IOFog controller + */ + private boolean ping() { + logDebug("Started Ping"); + if (notProvisioned()) { + logInfo("Finished Ping : " + false); + return false; + } + + try { + if (orchestrator.ping()) { + StatusReporter.setFieldAgentStatus().setControllerStatus(OK); + StatusReporter.setFieldAgentStatus().setControllerVerified(true); + logDebug("Finished Ping : " + true); + return true; + } + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + logError("Error pinging controller due to broken certificate", + new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + verificationFailed(e); + logError("Error pinging controller", new AgentUserException(e.getMessage(), e)); + } + logDebug("Finished Ping : " + false); + return false; + } + + /** + * pings IOFog controller + */ + private final Runnable pingController = () -> { + while (true) { + try { + Thread.sleep(Configuration.getPingControllerFreqSeconds() * 1000); + logDebug("Start Ping controller"); + ping(); + } catch (Exception e) { + logError("Exception pinging controller", new AgentUserException(e.getMessage(), e)); + } + logDebug("Finished Ping controller"); + } + }; + + /** + * computes SHA1 checksum + * + * @param data - input data + * @return String + */ + private String checksum(String data) { + try { + byte[] base64 = Base64.getEncoder().encode(data.getBytes(UTF_8)); + MessageDigest md = MessageDigest.getInstance("SHA1"); + md.update(base64); + byte[] mdbytes = md.digest(); + StringBuilder sb = new StringBuilder(""); + for (byte mdbyte : mdbytes) { + sb.append(Integer.toString((mdbyte & 0xff) + 0x100, 16).substring(1)); + } + return sb.toString(); + } catch (Exception e) { + logError("Error computing checksum", new AgentSystemException(e.getMessage(), e)); + return ""; + } + } + + /** + * reads json data from file and compare data checksum + * if checksum failed, returns null + * + * @param filename - file name to read data from + * @return JsonArray + */ + private JsonArray readFile(String filename) { + logDebug(String.format("Start read file %s :", filename)); + if (!Files.exists(Paths.get(filename), NOFOLLOW_LINKS)) + return null; + + JsonObject object = readObject(filename); + String checksum = object.getString("checksum"); + JsonArray data = object.getJsonArray("data"); + if (!checksum(data.toString()).equals(checksum)) + return null; + long timestamp = object.getJsonNumber("timestamp").longValue(); + if (lastGetChangesList == 0) + lastGetChangesList = timestamp; + else + lastGetChangesList = Long.min(timestamp, lastGetChangesList); + logDebug("Finished read file"); + return data; + } + + private JsonObject readObject(String filename) { + JsonObject object = null; + try (JsonReader reader = Json.createReader(new InputStreamReader(new FileInputStream(filename), UTF_8))) { + object = reader.readObject(); + } catch (FileNotFoundException ex) { + LoggingService.logError(MODULE_NAME, "Invalid file: " + filename, new AgentUserException(ex.getMessage(), ex)); + } + return object; + } + + /** + * saves data and checksum to json file + * + * @param data - data to be written into file + * @param filename - file name + */ + private void saveFile(JsonArray data, String filename) { + logDebug("Start save file name : " + filename); + String checksum = checksum(data.toString()); + JsonObject object = Json.createObjectBuilder() + .add("checksum", checksum) + .add("timestamp", lastGetChangesList) + .add("data", data) + .build(); + try (JsonWriter writer = Json.createWriter(new OutputStreamWriter(new FileOutputStream(filename), UTF_8))) { + writer.writeObject(object); + } catch (IOException e) { + logError("Error saving data to file '" + filename + "'", + new AgentUserException(e.getMessage(), e)); + } + logDebug("Finished save file"); + } + + /** + * gets IOFog instance configuration from IOFog controller + */ + private void getFogConfig() { + logInfo("Starting Get ioFog config"); + boolean hasError = false; + if (notProvisioned() || !isControllerConnected(false)) { + return; + } + + if (initialization) { + postFogConfig(); + return; + } + + try { + JsonObject configs = orchestrator.request("config", RequestType.GET, null, null); + if (configs != null && configs.size() != 0) { + String networkInterface = configs.containsKey(NETWORK_INTERFACE.getJsonProperty()) ? + configs.getString(NETWORK_INTERFACE.getJsonProperty()) : + NETWORK_INTERFACE.getDefaultValue(); + String dockerUrl = configs.containsKey(DOCKER_URL.getJsonProperty()) ? + configs.getString(DOCKER_URL.getJsonProperty()) : + DOCKER_URL.getDefaultValue(); + double diskLimit = configs.containsKey(DISK_CONSUMPTION_LIMIT.getJsonProperty()) ? + configs.getJsonNumber(DISK_CONSUMPTION_LIMIT.getJsonProperty()).doubleValue() : + Double.parseDouble(DISK_CONSUMPTION_LIMIT.getDefaultValue()); + String diskDirectory = configs.containsKey(DISK_DIRECTORY.getJsonProperty()) ? + configs.getString(DISK_DIRECTORY.getJsonProperty()) : + DISK_DIRECTORY.getDefaultValue(); + double memoryLimit = configs.containsKey(MEMORY_CONSUMPTION_LIMIT.getJsonProperty()) ? + configs.getJsonNumber(MEMORY_CONSUMPTION_LIMIT.getJsonProperty()).doubleValue() : + Double.parseDouble(MEMORY_CONSUMPTION_LIMIT.getDefaultValue()); + double cpuLimit = configs.containsKey(PROCESSOR_CONSUMPTION_LIMIT.getJsonProperty()) ? + configs.getJsonNumber(PROCESSOR_CONSUMPTION_LIMIT.getJsonProperty()).doubleValue() : + Double.parseDouble(PROCESSOR_CONSUMPTION_LIMIT.getDefaultValue()); + double logLimit = configs.containsKey(LOG_DISK_CONSUMPTION_LIMIT.getJsonProperty()) ? + configs.getJsonNumber(LOG_DISK_CONSUMPTION_LIMIT.getJsonProperty()).doubleValue() : + Double.parseDouble(LOG_DISK_CONSUMPTION_LIMIT.getDefaultValue()); + String logDirectory = configs.containsKey(LOG_DISK_DIRECTORY.getJsonProperty()) ? + configs.getString(LOG_DISK_DIRECTORY.getJsonProperty()) : + LOG_DISK_DIRECTORY.getDefaultValue(); + int logFileCount = configs.containsKey(LOG_FILE_COUNT.getJsonProperty()) ? + configs.getInt(LOG_FILE_COUNT.getJsonProperty()) : + Integer.parseInt(LOG_FILE_COUNT.getDefaultValue()); + int statusFrequency = configs.containsKey(STATUS_FREQUENCY.getJsonProperty()) ? + configs.getInt(STATUS_FREQUENCY.getJsonProperty()) : + Integer.parseInt(STATUS_FREQUENCY.getDefaultValue()); + int changeFrequency = configs.containsKey(CHANGE_FREQUENCY.getJsonProperty()) ? + configs.getInt(CHANGE_FREQUENCY.getJsonProperty()) : + Integer.parseInt(CHANGE_FREQUENCY.getDefaultValue()); + int deviceScanFrequency = configs.containsKey(DEVICE_SCAN_FREQUENCY.getJsonProperty()) ? + configs.getInt(DEVICE_SCAN_FREQUENCY.getJsonProperty()) : + Integer.parseInt(DEVICE_SCAN_FREQUENCY.getDefaultValue()); + boolean watchdogEnabled = configs.containsKey(WATCHDOG_ENABLED.getJsonProperty()) ? + configs.getBoolean(WATCHDOG_ENABLED.getJsonProperty()) : + WATCHDOG_ENABLED.getDefaultValue().equalsIgnoreCase("OFF") ? false : true; + double latitude = configs.containsKey("latitude") ? + configs.getJsonNumber("latitude").doubleValue() : + 0; + double longitude = configs.containsKey("longitude") ? + configs.getJsonNumber("longitude").doubleValue() : + 0; + String gpsCoordinates = latitude + "," + longitude; + String logLevel = configs.containsKey(LOG_LEVEL.getJsonProperty()) ? + configs.getString(LOG_LEVEL.getJsonProperty()) : + LOG_LEVEL.getDefaultValue(); + + int dockerPruningFrequency = configs.containsKey(DOCKER_PRUNING_FREQUENCY.getJsonProperty()) ? + configs.getInt(DOCKER_PRUNING_FREQUENCY.getJsonProperty()) : + Integer.parseInt(DOCKER_PRUNING_FREQUENCY.getDefaultValue()); + + int availableDiskThreshold = configs.containsKey(AVAILABLE_DISK_THRESHOLD.getJsonProperty()) ? + configs.getInt(AVAILABLE_DISK_THRESHOLD.getJsonProperty()) : + Integer.parseInt(AVAILABLE_DISK_THRESHOLD.getDefaultValue()); + + int readyToUpgradeScanFreq = configs.containsKey(READY_TO_UPGRADE_SCAN_FREQUENCY.getJsonProperty()) ? + configs.getInt(READY_TO_UPGRADE_SCAN_FREQUENCY.getJsonProperty()) : + Integer.parseInt(READY_TO_UPGRADE_SCAN_FREQUENCY.getDefaultValue()); + + String timeZone = configs.containsKey(TIME_ZONE.getJsonProperty()) ? + configs.getString(TIME_ZONE.getJsonProperty()) : + TIME_ZONE.getDefaultValue(); + + Map instanceConfig = new HashMap<>(); + + if (!NETWORK_INTERFACE.getDefaultValue().equals(Configuration.getNetworkInterface()) && + !Configuration.getNetworkInterface().equals(networkInterface)) + instanceConfig.put(NETWORK_INTERFACE.getCommandName(), networkInterface); + + if (Configuration.getDockerUrl() != null && !Configuration.getDockerUrl().equals(dockerUrl)) + instanceConfig.put(DOCKER_URL.getCommandName(), dockerUrl); + + if (Configuration.getDiskLimit() != diskLimit) + instanceConfig.put(DISK_CONSUMPTION_LIMIT.getCommandName(), diskLimit); + + if (Configuration.getDiskDirectory() != null && !Configuration.getDiskDirectory().equals(diskDirectory)) + instanceConfig.put(DISK_DIRECTORY.getCommandName(), diskDirectory); + + if (Configuration.getMemoryLimit() != memoryLimit) + instanceConfig.put(MEMORY_CONSUMPTION_LIMIT.getCommandName(), memoryLimit); + + if (Configuration.getCpuLimit() != cpuLimit) + instanceConfig.put(PROCESSOR_CONSUMPTION_LIMIT.getCommandName(), cpuLimit); + + if (Configuration.getLogDiskLimit() != logLimit) + instanceConfig.put(LOG_DISK_CONSUMPTION_LIMIT.getCommandName(), logLimit); + + if (Configuration.getLogDiskDirectory() != null && !Configuration.getLogDiskDirectory().equals(logDirectory)) + instanceConfig.put(LOG_DISK_DIRECTORY.getCommandName(), logDirectory); + + if (Configuration.getLogFileCount() != logFileCount) + instanceConfig.put(LOG_FILE_COUNT.getCommandName(), logFileCount); + + if (Configuration.getStatusFrequency() != statusFrequency) + instanceConfig.put(STATUS_FREQUENCY.getCommandName(), statusFrequency); + + if (Configuration.getChangeFrequency() != changeFrequency) + instanceConfig.put(CHANGE_FREQUENCY.getCommandName(), changeFrequency); + + if (Configuration.getDeviceScanFrequency() != deviceScanFrequency) + instanceConfig.put(DEVICE_SCAN_FREQUENCY.getCommandName(), deviceScanFrequency); + + if (Configuration.isWatchdogEnabled() != watchdogEnabled) + instanceConfig.put(WATCHDOG_ENABLED.getCommandName(), watchdogEnabled ? "on" : "off"); + + if (Configuration.getGpsCoordinates() != null && !Configuration.getGpsCoordinates().equals(gpsCoordinates)) { + instanceConfig.put(GPS_MODE.getCommandName(), gpsCoordinates); + } + + if (Configuration.getGpsCoordinates() == null && !gpsCoordinates.equals("0.0,0.0")) { + instanceConfig.put(GPS_MODE.getCommandName(), gpsCoordinates); + } + + if (Configuration.getLogLevel() != null && !Configuration.getLogLevel().equals(logLevel)) + instanceConfig.put(LOG_LEVEL.getCommandName(), logLevel); + + if ((Configuration.getDockerPruningFrequency() != dockerPruningFrequency) && (dockerPruningFrequency >= 1)) + instanceConfig.put(DOCKER_PRUNING_FREQUENCY.getCommandName(), dockerPruningFrequency); + + if (Configuration.getAvailableDiskThreshold() != availableDiskThreshold && (availableDiskThreshold >= 1)) { + instanceConfig.put(AVAILABLE_DISK_THRESHOLD.getCommandName(), availableDiskThreshold); + } + if ((Configuration.getReadyToUpgradeScanFrequency() != readyToUpgradeScanFreq) && (readyToUpgradeScanFreq >= 1)) + instanceConfig.put(READY_TO_UPGRADE_SCAN_FREQUENCY.getCommandName(), readyToUpgradeScanFreq); + + if (Configuration.getTimeZone()!= null && !Configuration.getTimeZone().equals(timeZone)) + instanceConfig.put(TIME_ZONE.getCommandName(), timeZone); + + if (!instanceConfig.isEmpty()) + Configuration.setConfig(instanceConfig, false); + } + } catch (CertificateException | SSLHandshakeException e) { + hasError = true; + verificationFailed(e); + logError("Unable to get ioFog config due to broken certificate", + new AgentUserException(e.getMessage(), e)); + } catch (Exception e) { + hasError = true; + try { + logError("Unable to get ioFog config ", new AgentUserException("Unable to get ioFog config", e)); + } catch (Exception ex){ + logError("We should never see this", new AgentUserException("This exception arise while logging the exception")); + } + } finally { + if (!hasError) { + Configuration.updateConfigBackUpFile(); + } + } + logInfo("Finished Get ioFog config"); + } + + /** + * sends IOFog instance configuration to IOFog controller + */ + private void postFogConfig() { + logInfo("Post ioFog config"); + if (notProvisioned() || !isControllerConnected(false)) { + return; + } + + double latitude = 0, longitude = 0; + try { + String gpsCoordinates = Configuration.getGpsCoordinates(); + if (gpsCoordinates != null) { + String[] coords = gpsCoordinates.split(","); + latitude = Double.parseDouble(coords[0]); + longitude = Double.parseDouble(coords[1]); + } + } catch (Exception e) { + logError("Error while parsing GPS coordinates", new AgentSystemException(e.getMessage(), e)); + } + + Pair connectedAddress = IOFogNetworkInterfaceManager.getInstance().getNetworkInterface(); + JsonObject json = Json.createObjectBuilder() + .add(NETWORK_INTERFACE.getJsonProperty(), connectedAddress == null ? "UNKNOWN" : connectedAddress._1().getName()) + .add(DOCKER_URL.getJsonProperty(), Configuration.getDockerUrl() == null ? "UNKNOWN" : Configuration.getDockerUrl()) + .add(DISK_CONSUMPTION_LIMIT.getJsonProperty(), Configuration.getDiskLimit()) + .add(DISK_DIRECTORY.getJsonProperty(), Configuration.getDiskDirectory() == null ? "UNKNOWN" : Configuration.getDiskDirectory()) + .add(MEMORY_CONSUMPTION_LIMIT.getJsonProperty(), Configuration.getMemoryLimit()) + .add(PROCESSOR_CONSUMPTION_LIMIT.getJsonProperty(), Configuration.getCpuLimit()) + .add(LOG_DISK_CONSUMPTION_LIMIT.getJsonProperty(), Configuration.getLogDiskLimit()) + .add(LOG_DISK_DIRECTORY.getJsonProperty(), Configuration.getLogDiskDirectory() == null ? "UNKNOWN" : Configuration.getLogDiskDirectory()) + .add(LOG_FILE_COUNT.getJsonProperty(), Configuration.getLogFileCount()) + .add(STATUS_FREQUENCY.getJsonProperty(), Configuration.getStatusFrequency()) + .add(CHANGE_FREQUENCY.getJsonProperty(), Configuration.getChangeFrequency()) + .add(DEVICE_SCAN_FREQUENCY.getJsonProperty(), Configuration.getDeviceScanFrequency()) + .add(WATCHDOG_ENABLED.getJsonProperty(), Configuration.isWatchdogEnabled()) + .add(GPS_MODE.getJsonProperty(), Configuration.getGpsMode() == null ? "UNKNOWN" : Configuration.getGpsMode().name().toLowerCase()) + .add("latitude", latitude) + .add("longitude", longitude) + .add(LOG_LEVEL.getJsonProperty(), Configuration.getLogLevel().toUpperCase()) + .add(AVAILABLE_DISK_THRESHOLD.getJsonProperty(), Configuration.getAvailableDiskThreshold()) + .add(DOCKER_PRUNING_FREQUENCY.getJsonProperty(), Configuration.getDockerPruningFrequency()) + .add(READY_TO_UPGRADE_SCAN_FREQUENCY.getJsonProperty(), Configuration.getReadyToUpgradeScanFrequency()) + .build(); + + try { + orchestrator.request("config", RequestType.PATCH, null, json); + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + logError("Unable to post ioFog config due to broken certificate ", + new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + logError("Unable to post ioFog config ", new AgentSystemException(e.getMessage(), e)); + } + logInfo("Finished Post ioFog config"); + } + + /** + * gets IOFog proxy configuration from IOFog controller + */ + private JsonObject getProxyConfig() { + JsonObject result = null; + + if (!notProvisioned() && isControllerConnected(false)) { + try { + JsonObject response = orchestrator.request("tunnel", RequestType.GET, null, null); + result = response.getJsonObject("tunnel"); + } catch (Exception e) { + logError("Unable to get proxy config ", new AgentSystemException(e.getMessage(), e)); + } + } + return result; + } + + /** + * does the provisioning. + * If successfully provisioned, updates Iofog UUID and Access Token in + * configuration file and loads Microservice data, otherwise sets appropriate + * status. + * + * @param key - provisioning key sent by command-line + * @return String + */ + public JsonObject provision(String key) { + logInfo("Provisioning ioFog agent"); + JsonObject provisioningResult; + + if (!notProvisioned()) { + try { + logInfo("Agent is already provisioned. Deprovisioning..."); + StatusReporter.setFieldAgentStatus().setControllerStatus(NOT_PROVISIONED); + deProvision(false); + } catch (Exception e) {} + } + + try { + provisioningLock.lock(); + provisioningResult = orchestrator.provision(key); + + microserviceManager.clear(); + try{ + ProcessManager.getInstance().deleteRemainingMicroservices(); + } catch (Exception e) { + logError("Error deleting remaining microservices", + new AgentSystemException(e.getMessage(), e)); + } + StatusReporter.setFieldAgentStatus().setControllerStatus(OK); + Configuration.setIofogUuid(provisioningResult.getString("uuid")); + Configuration.setAccessToken(provisioningResult.getString("token")); + + Configuration.saveConfigUpdates(); + Configuration.updateConfigBackUpFile(); + + postFogConfig(); + loadRegistries(false); + List microservices = loadMicroservices(false); + processMicroserviceConfig(microservices); + processRoutes(microservices); + notifyModules(); + loadEdgeResources(false); + + sendHWInfoFromHalToController(); + + postStatusHelper(); + + logInfo("Provisioning success"); + + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + provisioningResult = buildProvisionFailResponse("Certificate error", e); + } catch (UnknownHostException e) { + StatusReporter.setFieldAgentStatus().setControllerVerified(false); + provisioningResult = buildProvisionFailResponse("Connection error: unable to connect to fog controller.", e); + } catch (Exception e) { + provisioningResult = buildProvisionFailResponse(e.getMessage(), e); + } finally { + provisioningLock.unlock(); + } + return provisioningResult; + } + + private JsonObject buildProvisionFailResponse(String message, Exception e) { + logError("Provisioning failed", + new AgentSystemException("Provisioning failed : " + message, e)); + return Json.createObjectBuilder() + .add("status", "failed") + .add("errorMessage", message) + .build(); + } + + /** + * notifies other modules + */ + private void notifyModules() { + logInfo("Notifying modules for configuration update"); + try { + MessageBus.getInstance().update(); + } catch (Exception e) { + logWarning("Unable to update Message Bus" + " : " + e.getMessage()); + } + LocalApi.getInstance().update(); + ProcessManager.getInstance().update(); + } + + /** + * does de-provisioning + * + * @return String + */ + public String deProvision(boolean isTokenExpired) { + logInfo("Start Deprovisioning"); + + if (!provisioningLock.tryLock()) { + String msg = "Provisioning in progress"; + logInfo(msg); + return msg; + } + + try { + if (notProvisioned()) { + logInfo("Finished Deprovisioning : Failure - not provisioned"); + return "\nFailure - not provisioned"; + } + + if (!isTokenExpired) { + try { + orchestrator.request("deprovision", RequestType.POST, null, getDeprovisionBody()); + } catch (CertificateException | SSLHandshakeException e) { + verificationFailed(e); + logError("Unable to make deprovision request due to broken certificate ", + new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + logError("Unable to make deprovision request ", + new AgentSystemException(e.getMessage(), e)); + } + } + + StatusReporter.setFieldAgentStatus().setControllerStatus(NOT_PROVISIONED); + String iofogUuid = Configuration.getIofogUuid(); + boolean configUpdated = true; + try { + Configuration.setIofogUuid(""); + Configuration.setAccessToken(""); + Configuration.saveConfigUpdates(); + } catch (Exception e) { + configUpdated = false; + try { + logError("Error saving config updates", new AgentSystemException("Error saving config updates", e)); + } catch (Exception ex){ + logError("This error should not print ever!", new AgentSystemException("Error Logging exception in saving config updates on deprovision")); + } + } finally { + if (configUpdated) { + Configuration.updateConfigBackUpFile(); + } + } + microserviceManager.clear(); + try { + ProcessManager.getInstance().stopRunningMicroservices(false, iofogUuid); + } catch (Exception e) { + logError("Error stopping running microservices", + new AgentSystemException(e.getMessage(), e)); + } + notifyModules(); + logInfo("Finished Deprovisioning : Success - tokens, identifiers and keys removed"); + } finally { + provisioningLock.unlock(); + } + return "\nSuccess - tokens, identifiers and keys removed"; + } + + private JsonObject getDeprovisionBody() { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + + Set microserviceUuids = Stream.concat( + microserviceManager.getLatestMicroservices().stream(), + microserviceManager.getCurrentMicroservices().stream() + ) + .map(Microservice::getMicroserviceUuid) + .collect(Collectors.toSet()); + + microserviceUuids.forEach(arrayBuilder::add); + + return Json.createObjectBuilder() + .add("microserviceUuids", arrayBuilder) + .build(); + } + + /** + * sends IOFog configuration when any changes applied + */ + public void instanceConfigUpdated() { + logDebug("Start IOFog configuration update"); + try { + postFogConfig(); + } catch (Exception e) { + logError("Error posting updated for config ", e); + } + orchestrator.update(); + logDebug("Finished IOFog configuration update"); + } + + /** + * starts Field Agent module + */ + public void start() { + logDebug("Start the Field Agent"); + if (isNullOrEmpty(Configuration.getIofogUuid()) || isNullOrEmpty(Configuration.getAccessToken())) + StatusReporter.setFieldAgentStatus().setControllerStatus(NOT_PROVISIONED); + + microserviceManager = MicroserviceManager.getInstance(); + orchestrator = new Orchestrator(); + sshProxyManager = new SshProxyManager(new SshConnection()); + edgeResourceManager = EdgeResourceManager.getInstance(); + + boolean isConnected = ping(); + getFogConfig(); + if (!notProvisioned()) { + loadRegistries(!isConnected); + List microservices = loadMicroservices(!isConnected); + processMicroserviceConfig(microservices); + processRoutes(microservices); + loadEdgeResources(!isConnected); + } + + new Thread(pingController, Constants.FIELD_AGENT_PING_CONTROLLER).start(); + new Thread(getChangesList, Constants.FIELD_AGENT_GET_CHANGE_LIST).start(); + new Thread(postStatus, Constants.FIELD_AGENT_POST_STATUS).start(); + new Thread(postDiagnostics, Constants.FIELD_AGENT_POST_DIAGNOSTIC).start(); + + StatusReporter.setFieldAgentStatus().setReadyToUpgrade(VersionHandler.isReadyToUpgrade()); + StatusReporter.setFieldAgentStatus().setReadyToRollback(VersionHandler.isReadyToRollback()); + futureTask = scheduler.scheduleAtFixedRate(getAgentReadyToUpgradeStatus, 0, Configuration.getReadyToUpgradeScanFrequency(), TimeUnit.HOURS); + logDebug("Field Agent started"); + } + + /** + * checks if IOFog controller connection is broken + * + * @param fromFile + * @return boolean + */ + private boolean isControllerConnected(boolean fromFile) { + logDebug("check is Controller Connected"); + boolean isConnected = false; + if ((!StatusReporter.getFieldAgentStatus().getControllerStatus().equals(OK) && !ping()) && !fromFile) { + handleBadControllerStatus(); + } else { + isConnected = true; + } + logDebug(String.format("checked is Controller Connected : %s ", isConnected) ); + return isConnected; + } + + private void handleBadControllerStatus() { + logDebug("Start handle Bad Controller Status"); + String errMsg = "Connection to controller has broken"; + if (StatusReporter.getFieldAgentStatus().isControllerVerified()) { + logWarning(errMsg); + } else { + verificationFailed(new Exception(errMsg)); + } + logDebug("Finished handling Bad Controller Status"); + } + + public void sendUSBInfoFromHalToController() { + logDebug("Start send USB Info from hal To Controller"); + if (notProvisioned()) { + return; + } + Optional response = getResponse(USB_INFO_URL); + if (isResponseValid(response)) { + String usbInfo = response.get().toString(); + StatusReporter.setResourceManagerStatus().setUsbConnectionsInfo(usbInfo); + + JsonObject json = Json.createObjectBuilder() + .add("info", usbInfo) + .build(); + try { + orchestrator.request(COMMAND_USB_INFO, RequestType.PUT, null, json); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error while sending USBInfo from hal to controller", + new AgentSystemException(e.getMessage(), e)); + } + } + logDebug("Finished send USB Info from hal To Controller"); + } + + public void sendHWInfoFromHalToController() { + logDebug("Start send HW Info from HAL To Controller"); + if (notProvisioned()) { + return; + } + Optional response = getResponse(HW_INFO_URL); + if (isResponseValid(response)) { + String hwInfo = response.get().toString(); + StatusReporter.setResourceManagerStatus().setHwInfo(hwInfo); + + JsonObject json = Json.createObjectBuilder() + .add("info", hwInfo) + .build(); + + JsonObject jsonSendHWInfoResult = null; + try { + jsonSendHWInfoResult = orchestrator.request(COMMAND_HW_INFO, RequestType.PUT, null, json); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error while sending HW Info from hal to controller", + new AgentSystemException(e.getMessage(), e)); + } + + if (jsonSendHWInfoResult == null) { + LoggingService.logInfo(MODULE_NAME, "Can't get HW Info from HAL."); + } + } + logDebug("Finished send HW Info from HAL To Controller"); + } + + private boolean isResponseValid(Optional response) { + return response.isPresent() && !response.get().toString().isEmpty(); + } + + private Optional getResponse(String spec) { + logDebug("Start get response"); + Optional connection = sendHttpGetReq(spec); + StringBuilder content = null; + if (connection.isPresent()) { + content = new StringBuilder(); + try (BufferedReader in = new BufferedReader( + new InputStreamReader(connection.get().getInputStream(), UTF_8))) { + String inputLine; + content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + } catch (IOException exc) { + logDebug("HAL is not enabled for this Iofog Agent at the moment"); + } + connection.get().disconnect(); + } + logDebug("Finished get response"); + return Optional.ofNullable(content); + } + + private Optional sendHttpGetReq(String spec) { + logDebug("Start sending Http request"); + HttpURLConnection connection; + try { + URL url = new URL(spec); + connection = (HttpURLConnection) url.openConnection(); + if(connection != null){ + connection.setRequestMethod(HttpMethod.GET); + connection.getResponseCode(); + } + } catch (IOException exc) { + connection = null; + logDebug("HAL is not enabled for this Iofog Agent at the moment"); + } + logDebug("Finished sending Http request"); + return Optional.ofNullable(connection); + } + + private void createImageSnapshot() { + if (notProvisioned() || !isControllerConnected(false)) { + return; + } + + LoggingService.logDebug(MODULE_NAME, "Create image snapshot"); + + String microserviceUuid = null; + + try { + JsonObject jsonObject = orchestrator.request("image-snapshot", RequestType.GET, null, null); + microserviceUuid = jsonObject.getString("uuid"); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Unable get name of image snapshot", + new AgentSystemException(e.getMessage(), e)); + } + + if (SystemUtils.IS_OS_WINDOWS) { + return; // TODO implement + } + + if (microserviceUuid != null) { + ImageDownloadManager.createImageSnapshot(orchestrator, microserviceUuid); + } + LoggingService.logDebug(MODULE_NAME, "Finished Create image snapshot"); + } + /** + * returns report for "info" about ready to upgrade and ready to rollback + * + * @return info getCheckUpgradeReadyReport + */ + public String getCheckUpgradeReadyReport() { + LoggingService.logDebug(MODULE_NAME, "Start get upgrade ready report"); + + StringBuilder result = new StringBuilder(); + boolean isReadyToUpgrade = VersionHandler.isReadyToUpgrade(); + boolean isReadyToRollback = VersionHandler.isReadyToRollback(); + + // isReadyToUpgrade + result.append(buildReportLine("Ready To Upgrade", String.valueOf(isReadyToUpgrade))); + // isReadyToRollback + result.append(buildReportLine("Ready To Rollback", String.valueOf(isReadyToRollback))); + + LoggingService.logInfo(MODULE_NAME, "Finished get upgrade ready report"); + + return result.toString(); + } + + private String buildReportLine(String messageDescription, String value) { + return rightPad(messageDescription, 40, ' ') + " : " + value + "\\n"; + } + + /** + * get isReadyToUpgrade and isReadyToRollback Status + */ + private Runnable getAgentReadyToUpgradeStatus = () -> { + LoggingService.logDebug(MODULE_NAME, "Start scan of isReadyToUpgrade and isReadyToRollback Status"); + try { + boolean isReadyToRollback = VersionHandler.isReadyToRollback(); + boolean isReadyToUpgrade = VersionHandler.isReadyToUpgrade(); + StatusReporter.setFieldAgentStatus().setReadyToRollback(isReadyToRollback); + StatusReporter.setFieldAgentStatus().setReadyToUpgrade(isReadyToUpgrade); + } catch (Exception e){ + LoggingService.logError(MODULE_NAME,"Error getting isReadyToUpgrade and isReadyToRollback Status", new AgentSystemException(e.getMessage(), e)); + } + LoggingService.logDebug(MODULE_NAME, "Finished scan of isReadyToUpgrade and isReadyToRollback Status"); + + }; + /** + * This method will reschedule "myTask" with the new param time + */ + public void changeReadInterval() + { + if (futureTask != null) + { + futureTask.cancel(true); + } + futureTask = scheduler.scheduleAtFixedRate(getAgentReadyToUpgradeStatus, 0, Configuration.getReadyToUpgradeScanFrequency(), TimeUnit.HOURS); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgentStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgentStatus.java new file mode 100644 index 00000000..4f3b5704 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgentStatus.java @@ -0,0 +1,76 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.field_agent; + +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.Constants.ControllerStatus; + +/** + * represents Field Agent status + * + * @author saeid + * + */ +public class FieldAgentStatus { + + private Constants.ControllerStatus controllerStatus; + private long lastCommandTime; + private boolean controllerVerified; + private boolean readyToUpgrade; + private boolean readyToRollback; + + public FieldAgentStatus() { + controllerStatus = ControllerStatus.NOT_CONNECTED; + } + + public Constants.ControllerStatus getControllerStatus() { + return controllerStatus; + } + + public void setControllerStatus(Constants.ControllerStatus controllerStatus) { + this.controllerStatus = controllerStatus; + } + + public long getLastCommandTime() { + return lastCommandTime; + } + + public void setLastCommandTime(long lastCommandTime) { + this.lastCommandTime = lastCommandTime; + } + + public boolean isControllerVerified() { + return controllerVerified; + } + + public void setControllerVerified(boolean controllerVerified) { + this.controllerVerified = controllerVerified; + } + + public boolean isReadyToUpgrade() { + return readyToUpgrade; + } + + public void setReadyToUpgrade(boolean readyToUpgrade) { + this.readyToUpgrade = readyToUpgrade; + } + + public boolean isReadyToRollback() { + return readyToRollback; + } + + public void setReadyToRollback(boolean readyToRollback) { + this.readyToRollback = readyToRollback; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/VersionHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/VersionHandler.java new file mode 100755 index 00000000..4cbe05ae --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/VersionHandler.java @@ -0,0 +1,212 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.field_agent; + +import org.apache.commons.lang.SystemUtils; +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.field_agent.enums.VersionCommand; +import org.eclipse.iofog.field_agent.exceptions.UnknownVersionCommandException; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.JsonObject; +import java.io.File; +import java.io.IOException; +import java.util.List; + +import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.eclipse.iofog.field_agent.enums.VersionCommand.parseJson; +import static org.eclipse.iofog.utils.Constants.SNAP_COMMON; +import static org.eclipse.iofog.utils.logging.LoggingService.logError; +import static org.eclipse.iofog.utils.logging.LoggingService.logWarning; + +public class VersionHandler { + + private static final String MODULE_NAME = "Version Handler"; + + private final static String PACKAGE_NAME = "iofog-agent"; + private final static String BACKUPS_DIR = SystemUtils.IS_OS_WINDOWS ? SNAP_COMMON + "./var/backups/iofog-agent" : SNAP_COMMON + "/var/backups/iofog-agent"; + private final static String MAX_RESTARTING_TIMEOUT = "60"; + + private final static String GET_LINUX_DISTRIBUTION_NAME = "grep = /etc/os-release | awk -F\"[=]\" '{print $2}' | sed -n 1p"; + private static String GET_IOFOG_PACKAGE_INSTALLED_VERSION; + private static String GET_IOFOG_PACKAGE_DEV_VERSION; + private static String GET_IOFOG_PACKAGE_CANDIDATE_VERSION; + private static String UPDATE_PACKAGE_REPOSITORY; + private static String GET_PACKAGE_MANAGER_LOCK_FILE_CONTENT; + private static String DEV; + + static { + if (SystemUtils.IS_OS_LINUX) { + String distrName = getDistributionName().toLowerCase(); + if (distrName.contains("ubuntu") + || distrName.contains("debian") + || distrName.contains("raspbian")) { + GET_IOFOG_PACKAGE_DEV_VERSION = "(apt-cache policy " + PACKAGE_NAME + "-dev && apt-cache policy " + PACKAGE_NAME + ") | grep -A1 ^iofog | awk '$2 ~ /^[0-9]/ {print a}{a=$0}' | sed -e 's/iofog-agent\\(.*\\):/\\1/'"; + DEV = getIofogPackageDevVersion(); + GET_IOFOG_PACKAGE_INSTALLED_VERSION = "apt-cache policy " + PACKAGE_NAME + DEV + " | grep Installed | awk '{print $2}'"; + GET_IOFOG_PACKAGE_CANDIDATE_VERSION = "apt-cache policy " + PACKAGE_NAME + DEV + " | grep Candidate | awk '{print $2}'"; + UPDATE_PACKAGE_REPOSITORY = "apt-get update -y"; + GET_PACKAGE_MANAGER_LOCK_FILE_CONTENT = "cat /var/lib/apt/lists/lock /var/cache/apt/archives/lock"; + } else if (distrName.contains("fedora")) { + GET_IOFOG_PACKAGE_DEV_VERSION = "(dnf --showduplicates list installed " + PACKAGE_NAME + "-dev && dnf --showduplicates list installed " + PACKAGE_NAME + ") | grep iofog | awk '{print $1}' | sed -e 's/iofog-agent\\(.*\\).noarch/\\1/')"; + DEV = getIofogPackageDevVersion(); + GET_IOFOG_PACKAGE_INSTALLED_VERSION = "dnf --showduplicates list installed " + PACKAGE_NAME + DEV + " | grep iofog | awk '{print $2}'"; + GET_IOFOG_PACKAGE_CANDIDATE_VERSION = "dnf --showduplicates list " + PACKAGE_NAME + DEV + " | grep iofog | awk '{print $2}' | sed -n \\$p\\"; + UPDATE_PACKAGE_REPOSITORY = "dnf update -y"; + GET_PACKAGE_MANAGER_LOCK_FILE_CONTENT = "cat /var/cache/dnf/metadata_lock.pid"; + } else if (distrName.contains("red hat") + || distrName.contains("centos") + || distrName.contains("amazon")) { + GET_IOFOG_PACKAGE_DEV_VERSION = "(yum --showduplicates list installed " + PACKAGE_NAME + "-dev && yum --showduplicates list installed " + PACKAGE_NAME + ") | grep iofog | awk '{print $1}' | sed -e 's/iofog-agent\\(.*\\).noarch/\\1/')"; + DEV = getIofogPackageDevVersion(); + GET_IOFOG_PACKAGE_INSTALLED_VERSION = "yum --showduplicates list installed | grep " + PACKAGE_NAME + DEV + " | awk '{print $2}'"; + GET_IOFOG_PACKAGE_CANDIDATE_VERSION = "yum --showduplicates list | grep " + PACKAGE_NAME + DEV + "| awk '{print $2}' | sed -n \\$p\\"; + UPDATE_PACKAGE_REPOSITORY = "yum update -y"; + GET_PACKAGE_MANAGER_LOCK_FILE_CONTENT = "cat /var/run/yum.pid"; + } else { + logWarning(MODULE_NAME, "it looks like your distribution is not supported"); + } + } + } + + private static String getDistributionName() { + CommandShellResultSet, List> resultSet = CommandShellExecutor.executeCommand(GET_LINUX_DISTRIBUTION_NAME); + return resultSet.getValue().size() > 0 ? resultSet.getValue().get(0) : EMPTY; + } + + private static String getFogInstalledVersion() { + CommandShellResultSet, List> resultSet = CommandShellExecutor.executeCommand(GET_IOFOG_PACKAGE_INSTALLED_VERSION); + return resultSet != null ? parseVersionResult(resultSet) : EMPTY; + } + + private static String getIofogPackageDevVersion(){ + CommandShellResultSet, List> resultSet = CommandShellExecutor.executeCommand(GET_IOFOG_PACKAGE_DEV_VERSION); + return resultSet != null ? parseVersionResult(resultSet) : EMPTY; + } + + private static String getFogCandidateVersion() { + CommandShellResultSet, List> resultSet = CommandShellExecutor.executeCommand(GET_IOFOG_PACKAGE_CANDIDATE_VERSION); + return resultSet != null ? parseVersionResult(resultSet) : EMPTY; + } + + private static boolean isPackageRepositoryUpdated() { + LoggingService.logDebug(MODULE_NAME, "start package repository update"); + boolean isPackageRepositoryUpdated; + CommandShellResultSet, List> resultSet = CommandShellExecutor.executeCommand(GET_PACKAGE_MANAGER_LOCK_FILE_CONTENT); + //if lock file exists and not empty + if (resultSet != null && resultSet.getError().size() == 0 && resultSet.getValue().size() > 0) { + logWarning(MODULE_NAME, "Unable to update package repository. Another app is currently holding package manager lock"); + isPackageRepositoryUpdated = false; + } + // if lock file doesn't exist or empty + else { + CommandShellExecutor.executeCommand(UPDATE_PACKAGE_REPOSITORY); + isPackageRepositoryUpdated = true; + } + LoggingService.logDebug(MODULE_NAME, "Finished package repository update : " + isPackageRepositoryUpdated); + return isPackageRepositoryUpdated; + } + + private static String parseVersionResult(CommandShellResultSet, List> resultSet) { + return resultSet.getError().size() == 0 && resultSet.getValue().size() > 0 ? resultSet.getValue().get(0) : EMPTY; + } + + /** + * performs change version operation, received from ioFog controller + * + */ + static void changeVersion(JsonObject actionData) { + LoggingService.logInfo(MODULE_NAME, "Start performing change version operation, received from ioFog controller"); + if (SystemUtils.IS_OS_WINDOWS) { + return; // TODO implement + } + + try{ + + VersionCommand versionCommand = parseJson(actionData); + String provisionKey = actionData.getString("provisionKey"); + + if (isValidChangeVersionOperation(versionCommand)) { + executeChangeVersionScript(versionCommand, provisionKey); + } + + } catch (UnknownVersionCommandException e) { + logError(MODULE_NAME, "Error performing change version operation : Invalid command", e); + } catch (Exception e){ + logError(MODULE_NAME, "Error performing change version operation", new AgentSystemException(e.getMessage(), e)); + } + LoggingService.logInfo(MODULE_NAME, "Finished performing change version operation, received from ioFog controller"); + } + + /** + * executes sh script to change iofog version + * + * @param command {@link VersionCommand} + * @param provisionKey new provision key (used to restart iofog correctly) + */ + private static void executeChangeVersionScript(VersionCommand command, String provisionKey) { + LoggingService.logInfo(MODULE_NAME, "Start executing sh script to change iofog version"); + String shToExecute = command.getScript(); + + try { + Runtime.getRuntime().exec("java -jar /usr/bin/iofog-agentvc.jar " + shToExecute + " " + provisionKey + " " + MAX_RESTARTING_TIMEOUT); + } catch (IOException e) { + logError(MODULE_NAME, "Error executing sh script to change version", new AgentSystemException(e.getMessage(), e)); + } + LoggingService.logInfo(MODULE_NAME, "Finished executing sh script to change iofog version"); + } + + private static boolean isValidChangeVersionOperation(VersionCommand command) { + switch (command){ + case UPGRADE: + return isReadyToUpgrade(); + case ROLLBACK: + return isReadyToRollback(); + default: + return false; + } + } + + static boolean isReadyToUpgrade() { + boolean isReadyToUpgrade = false; + try{ + LoggingService.logDebug(MODULE_NAME, "Checking is ready to upgrade"); + isReadyToUpgrade = isNotWindows() + && isPackageRepositoryUpdated() + && areNotVersionsSame(); + LoggingService.logDebug(MODULE_NAME, "Is ready to upgrade : " + isReadyToUpgrade); + } catch (Exception e){ + LoggingService.logError(MODULE_NAME, "Error getting is ready to upgrade", new AgentSystemException(e.getMessage(), e)); + } + return isReadyToUpgrade; + } + + private static boolean isNotWindows() { + return !SystemUtils.IS_OS_WINDOWS; + } + + private static boolean areNotVersionsSame() { + return !(getFogInstalledVersion().equals(getFogCandidateVersion())); + } + + static boolean isReadyToRollback() { + LoggingService.logDebug(MODULE_NAME, "Checking is ready to rollback"); + String[] backupsFiles = new File(BACKUPS_DIR).list(); + boolean isReadyToRollback = !(backupsFiles == null || backupsFiles.length == 0); + LoggingService.logDebug(MODULE_NAME, "Is ready to rollback : " + isReadyToRollback); + return isReadyToRollback; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/RequestType.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/RequestType.java new file mode 100644 index 00000000..05b09cb7 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/RequestType.java @@ -0,0 +1,9 @@ +package org.eclipse.iofog.field_agent.enums; + +public enum RequestType { + GET, + POST, + PATCH, + PUT, + DELETE +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/VersionCommand.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/VersionCommand.java new file mode 100755 index 00000000..09f232c9 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/VersionCommand.java @@ -0,0 +1,68 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.field_agent.enums; + +import org.eclipse.iofog.field_agent.exceptions.UnknownVersionCommandException; +import javax.json.JsonObject; +import java.util.Arrays; +import java.util.Optional; + +import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.eclipse.iofog.utils.Constants.SNAP_COMMON; + +public enum VersionCommand { + + UPGRADE { + @Override + public String getScript() { + return UPGRADE_VERSION_SCRIPT; + } + + @Override + public String toString() { + return "upgrade"; + } + }, ROLLBACK { + @Override + public String getScript() { + return ROLLBACK_VERSION_SCRIPT; + } + + @Override + public String toString() { + return "rollback"; + } + }; + + public final static String CHANGE_VERSION_SCRIPTS_DIR = SNAP_COMMON + "/usr/share/iofog-agent/"; + public final static String UPGRADE_VERSION_SCRIPT = CHANGE_VERSION_SCRIPTS_DIR + "upgrade.sh"; + public final static String ROLLBACK_VERSION_SCRIPT = CHANGE_VERSION_SCRIPTS_DIR + "rollback.sh"; + + public static VersionCommand parseCommandString(String commandStr) throws UnknownVersionCommandException { + return Optional.of(Arrays.stream(VersionCommand.values()) + .filter(versionCommand -> versionCommand.toString().equals(commandStr)) + .findFirst() + .orElseThrow(UnknownVersionCommandException::new)) + .get(); + } + + public static VersionCommand parseJson(JsonObject versionData) throws UnknownVersionCommandException { + String versionCommandStr = versionData != null ? versionData.containsKey("versionCommand") ? + versionData.getString("versionCommand") : + EMPTY : EMPTY; + return parseCommandString(versionCommandStr); + } + + public abstract String getScript(); +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/exceptions/UnknownVersionCommandException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/exceptions/UnknownVersionCommandException.java new file mode 100755 index 00000000..06c6aaeb --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/exceptions/UnknownVersionCommandException.java @@ -0,0 +1,21 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.field_agent.exceptions; + +public class UnknownVersionCommandException extends IllegalArgumentException { + + public UnknownVersionCommandException() { + super("Unknown version changing command"); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsMode.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsMode.java new file mode 100644 index 00000000..8bea825b --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsMode.java @@ -0,0 +1,31 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.gps; + +import java.util.Arrays; + +public enum GpsMode { + AUTO, + MANUAL, + OFF, + DYNAMIC; + + + public static GpsMode getModeByValue(String command) { + return Arrays.stream(GpsMode.values()) + .filter(gpsMode -> gpsMode.name().toLowerCase().equals(command)) + .findFirst() + .orElseThrow(IllegalArgumentException::new); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsWebHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsWebHandler.java new file mode 100644 index 00000000..839f6c4f --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsWebHandler.java @@ -0,0 +1,80 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.gps; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.URL; + +import static org.eclipse.iofog.utils.logging.LoggingService.logError; + +public class GpsWebHandler { + + private static final String MODULE_NAME = "GPS Web Handler"; + + /** + * gets GPS coordinates by external ip from http://ip-api.com/ + * + * @return "lat,lon" string. lat and lon in DD GPS format + */ + public static String getGpsCoordinatesByExternalIp() { + String gpsCoord = ""; + try { + JsonObject response = getGeolocationData(); + + double lat = response.getJsonNumber("lat").doubleValue(); + double lon = response.getJsonNumber("lon").doubleValue(); + + gpsCoord = lat + "," + lon; + } catch (Exception e) { + logError( MODULE_NAME,"Unable to set gps coordinates by external API http://ip-api.com/json. " + + "Setting empty gps coordinates.", e); + } + + return gpsCoord; + } + + /** + * gets external ip from http://ip-api.com/ + * + * @return string. external ip address + */ + public static String getExternalIp() { + try { + JsonObject response = getGeolocationData(); + + String externalIp = response.getString("query"); + return externalIp; + } catch (Exception e) { + logError(MODULE_NAME, "Unable to get external ip", e); + return ""; + } + } + + /** + * gets geolocation info from http://ip-api.com/ + * + * @return JsonObject + */ + private static JsonObject getGeolocationData() throws Exception { + BufferedReader ipReader = new BufferedReader( + new InputStreamReader(new URL("http://ip-api.com/json").openStream())); + JsonReader jsonReader = Json.createReader(ipReader); + return jsonReader.readObject(); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ApiHandlerHelpers.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ApiHandlerHelpers.java new file mode 100644 index 00000000..a4adc902 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ApiHandlerHelpers.java @@ -0,0 +1,101 @@ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; +import org.apache.http.util.TextUtils; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; + +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.iofog.utils.Constants.LOCAL_API_TOKEN_PATH; + +public class ApiHandlerHelpers { + private static final String MODULE_NAME = "Api Handler Helpers"; + public static boolean validateMethod(HttpRequest request, HttpMethod expectedMethod) { + return request.method() == expectedMethod; + } + + public static String validateContentType(HttpRequest request, String expectedContentType) { + LoggingService.logDebug(MODULE_NAME, "Validate content type in request : " + request); + try { + final String contentType = request.headers().get(HttpHeaderNames.CONTENT_TYPE, ""); + if (TextUtils.isEmpty(contentType) || !(contentType.trim().split(";")[0].equalsIgnoreCase(expectedContentType))) { + return "Incorrect content type " + contentType; + } + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error validating content type", + new AgentUserException(e.getMessage(), e)); + return "Incorrect content type " ; + } + return null; + } + + public static boolean validateAccessToken(HttpRequest request) { + LoggingService.logDebug(MODULE_NAME, "Validate access token in request : " + request); + final String validAccessToken = fetchAccessToken(); + String accessToken; + try { + accessToken = request.headers().get(HttpHeaderNames.AUTHORIZATION, ""); + return !TextUtils.isEmpty(accessToken) && accessToken.equalsIgnoreCase(validAccessToken); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error validating Access Token", + new AgentUserException(e.getMessage(), e)); + } + return false; + + } + + public static FullHttpResponse successResponse(ByteBuf outputBuffer, String content) { + return createResponse(outputBuffer, content, OK); + } + + public static FullHttpResponse methodNotAllowedResponse() { + return createResponse(null, null, METHOD_NOT_ALLOWED); + } + + public static FullHttpResponse badRequestResponse(ByteBuf outputBuffer, String content) { + return createResponse(outputBuffer, content, BAD_REQUEST); + } + + public static FullHttpResponse unauthorizedResponse(ByteBuf outputBuffer, String content) { + return createResponse(outputBuffer, content, UNAUTHORIZED); + } + + public static FullHttpResponse notFoundResponse(ByteBuf outputBuffer, String content) { + return createResponse(outputBuffer, content, NOT_FOUND); + } + + public static FullHttpResponse internalServerErrorResponse(ByteBuf outputBuffer, String content) { + return createResponse(outputBuffer, content, INTERNAL_SERVER_ERROR); + } + + private static FullHttpResponse createResponse(ByteBuf outputBuffer, String content, HttpResponseStatus status) { + if (outputBuffer != null && content != null) { + outputBuffer.writeBytes(content.getBytes(UTF_8)); + FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, status, outputBuffer); + HttpUtil.setContentLength(res, outputBuffer.readableBytes()); + return res; + } else { + return new DefaultFullHttpResponse(HTTP_1_1, status); + } + } + + private static String fetchAccessToken() { + LoggingService.logDebug(MODULE_NAME, "Fetch access token"); + String line = ""; + try (BufferedReader reader = new BufferedReader(new FileReader(LOCAL_API_TOKEN_PATH))) { + line = reader.readLine(); + } catch (IOException e) { + LoggingService.logError("Local API", "Local API access token is missing, try to re-install Agent.", new AgentSystemException(e.getMessage(), e)); + System.out.println("Local API access token is missing, try to re-install Agent."); + } + return line; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/BluetoothApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/BluetoothApiHandler.java new file mode 100644 index 00000000..c1f20b6a --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/BluetoothApiHandler.java @@ -0,0 +1,112 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.*; +import io.netty.util.ReferenceCountUtil; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.concurrent.Callable; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class BluetoothApiHandler implements Callable { + + private static final String MODULE_NAME = "Local Api : Bluetooth API"; + + private final FullHttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + private FullHttpResponse response; + + + public BluetoothApiHandler(FullHttpRequest req, ByteBuf outputBuffer, byte[] content) { + this.req = req; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Handle Bluetooth api http request"); + + String host = "localhost"; + int port = 10500; + + EventLoopGroup group = new NioEventLoopGroup(1); + + try { + Bootstrap b = new Bootstrap(); + b.option(ChannelOption.SO_REUSEADDR, true); + b.group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ch.pipeline().addLast(new HttpClientCodec()); + ch.pipeline().addLast(new HttpObjectAggregator(1048576)); + ChannelInboundHandler handler = new SimpleChannelInboundHandler() { + protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { + if (msg instanceof HttpResponse) { + FullHttpResponse res = (FullHttpResponse) msg; + + outputBuffer.writeBytes(res.content()); + response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, res.status(), outputBuffer); + HttpUtil.setContentLength(response, outputBuffer.readableBytes()); + response.headers().set(res.headers()); + ctx.channel().close().sync(); + } + } + }; + ch.pipeline().addLast(handler); + } + }); + + ByteBuf requestContent = Unpooled.copiedBuffer(content); + try { + Channel channel = b.connect(host, port).sync().channel(); + String endpoint = req.uri().substring(12); + FullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, req.method(), endpoint, requestContent); + request.headers().set(req.headers()); + channel.writeAndFlush(request).addListener(ChannelFutureListener.CLOSE); + } catch (Exception e) { + throw e; + } finally { + ReferenceCountUtil.release(requestContent); + } + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error unable to reach RESTblue container!", e); + } finally { + group.shutdownGracefully(); + } + + if (response == null) { + String responseString = "{\"error\":\"unable to reach RESTblue container!\"}"; + outputBuffer.writeBytes(responseString.getBytes(UTF_8)); + response = ApiHandlerHelpers.notFoundResponse(outputBuffer, responseString); + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + LoggingService.logInfo(MODULE_NAME, "error unable to reach RESTblue container!"); + } + + LoggingService.logDebug(MODULE_NAME, "Finished processing commandline api request"); + return response; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/CommandLineApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/CommandLineApiHandler.java new file mode 100644 index 00000000..d4a0ea02 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/CommandLineApiHandler.java @@ -0,0 +1,104 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; +import org.apache.http.util.TextUtils; +import org.eclipse.iofog.command_line.CommandLineParser; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.utils.logging.LoggingService; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.iofog.utils.Constants.LOCAL_API_TOKEN_PATH; + +public class CommandLineApiHandler implements Callable { + private static final String MODULE_NAME = "Local API : CommandLineApiHandler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public CommandLineApiHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Handle commandline api http request"); + if (!ApiHandlerHelpers.validateMethod(this.req, POST)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, new AgentUserException(contentTypeError)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + if (!ApiHandlerHelpers.validateAccessToken(this.req)) { + String errorMsg = "Incorrect access token"; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentUserException(errorMsg)); + return ApiHandlerHelpers.unauthorizedResponse(outputBuffer, errorMsg); + } + + try { + String msgString = new String(content, UTF_8); + JsonReader reader = Json.createReader(new StringReader(msgString)); + JsonObject jsonObject = reader.readObject(); + + String command = jsonObject.getString("command"); + Map resultMap = new HashMap<>(); + ObjectMapper objectMapper = new ObjectMapper(); + String result; + try { + result = CommandLineParser.parse(command); + resultMap.put("response", result); + String jsonResult = objectMapper.writeValueAsString(resultMap); + LoggingService.logDebug(MODULE_NAME, "Finished processing commandline api request"); + return ApiHandlerHelpers.successResponse(outputBuffer, jsonResult); + } catch (AgentUserException e) { + result = e.getMessage(); + resultMap.put("response", result); + resultMap.put("error", "Internal server error"); + String jsonResult = objectMapper.writeValueAsString(resultMap); + LoggingService.logError(MODULE_NAME, "Error in handling command line http request", e); + return ApiHandlerHelpers.internalServerErrorResponse(outputBuffer, jsonResult); + } + + } catch (Exception e) { + String errorMsg = " Log message parsing error, " + e.getMessage(); + LoggingService.logError(MODULE_NAME, errorMsg, e); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigApiHandler.java new file mode 100644 index 00000000..5a231ca9 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigApiHandler.java @@ -0,0 +1,145 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.*; +import java.io.StringReader; +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.iofog.utils.configuration.Configuration.getOldNodeValuesForParameters; +import static org.eclipse.iofog.utils.configuration.Configuration.setConfig; + +public class ConfigApiHandler implements Callable { + private static final String MODULE_NAME = "Local API : ConfigApiHandler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + private final Map CONFIG_MAP = new HashMap() {{ + put("d", "disk-limit"); + put("dl", "disk-directory"); + put("m", "memory-limit"); + put("p", "cpu-limit"); + put("a", "controller-url"); + put("ac", "cert-directory"); + put("c", "docker-url"); + put("n", "network-adapter"); + put("l", "logs-limit"); + put("ld", "logs-directory"); + put("lc", "logs-count"); + put("sf", "status-frequency"); + put("cf", "changes-frequency"); + put("df", "diagnostics-frequency"); + put("sd", "device-scan-frequency"); + put("idc", "isolated"); + put("gps", "gps"); + put("ft", "fog-type"); + put("dev", "developer-mode"); + put("ll", "logs-level"); + put("tz", "time-zone"); + }}; + + public ConfigApiHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Handle config Api Handler http request"); + + if (!ApiHandlerHelpers.validateMethod(this.req, POST)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, new AgentUserException(contentTypeError)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + if (!ApiHandlerHelpers.validateAccessToken(this.req)) { + String errorMsg = "Incorrect access token"; + outputBuffer.writeBytes(errorMsg.getBytes(UTF_8)); + LoggingService.logError(MODULE_NAME, errorMsg, new AgentUserException(errorMsg)); + return ApiHandlerHelpers.unauthorizedResponse(outputBuffer, errorMsg); + } + + try { + String msgString = new String(content, UTF_8); + JsonReader reader = Json.createReader(new StringReader(msgString)); + JsonObject config = reader.readObject(); + + Map configMap = new HashMap<>(); + for (String key : CONFIG_MAP.keySet()) { + String propertyName = CONFIG_MAP.get(key); + if (!config.containsKey(propertyName)) { + continue; + } + + String value = config.getString(propertyName); + configMap.put(key, value); + } + + try { + if (configMap.size() != 0) { + HashMap errorMap = setConfig(configMap, false); + HashMap errorMessages = new HashMap<>(); + for (Map.Entry error : errorMap.entrySet()) { + String configName = CONFIG_MAP.get(error.getKey()); + String errorMessage = error.getValue().replaceAll(" \\-[a-z] ", " "); + + errorMessages.put(configName, errorMessage); + } + + ObjectMapper objectMapper = new ObjectMapper(); + String result = objectMapper.writeValueAsString(errorMessages); + FullHttpResponse res = ApiHandlerHelpers.successResponse(outputBuffer, result); + res.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + LoggingService.logDebug(MODULE_NAME, "Finished config Api Handler http request"); + return res; + } else { + String errMsg = "Request not valid"; + LoggingService.logError(MODULE_NAME, errMsg, new AgentSystemException(errMsg)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errMsg); + } + } catch (Exception e) { + String errMsg = "Error updating new config "; + LoggingService.logError(MODULE_NAME, errMsg, new AgentSystemException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errMsg + e.getMessage()); + } + } catch (Exception e) { + String errorMsg = "Log message parsing error, " + e.getMessage(); + LoggingService.logError(MODULE_NAME, errorMsg, e); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigurationMap.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigurationMap.java new file mode 100644 index 00000000..701b1be2 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigurationMap.java @@ -0,0 +1,29 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import java.util.HashMap; +import java.util.Map; + +/** + * Configuration map to store the current containers configurations + * @author ashita + * @since 2016 + */ +public final class ConfigurationMap { + public static Map containerConfigMap = new HashMap<>(); + + private ConfigurationMap(){ + throw new UnsupportedOperationException(ConfigurationMap.class + "could not be instantiated"); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlSignalSentInfo.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlSignalSentInfo.java new file mode 100644 index 00000000..b64d13e2 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlSignalSentInfo.java @@ -0,0 +1,53 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +/** + * Unacknowledged control signals with the try count. + * @author ashita + * @since 2016 + */ +public class ControlSignalSentInfo { + private int sendTryCount = 0; + private long timeMillis; + + ControlSignalSentInfo(int count, long timeMillis){ + this.sendTryCount = count; + this.timeMillis = timeMillis; + } + + public long getTimeMillis() { + return timeMillis; + } + + public void setTimeMillis(long timeMillis) { + this.timeMillis = timeMillis; + } + + /** + * Get message sending trial count + * @return int + */ + public int getSendTryCount() { + return sendTryCount; + } + + /** + * Save message sending trial count + * @return void + */ + public void setSendTryCount(int sendTryCount) { + this.sendTryCount = sendTryCount; + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketHandler.java new file mode 100644 index 00000000..6512ebbb --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketHandler.java @@ -0,0 +1,204 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.websocketx.*; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.ArrayList; +import java.util.Map; + +import static io.netty.handler.codec.http.HttpHeaders.Names.HOST; + +/** + * Handler for the real-time control websocket Open real-time control websocket + * Send control-signals + * + * @author ashita + * @since 2016 + */ +public class ControlWebsocketHandler { + private static final String MODULE_NAME = "Local API"; + + private static final Byte OPCODE_PING = 0x9; + private static final Byte OPCODE_PONG = 0xA; + private static final Byte OPCODE_ACK = 0xB; + private static final Byte OPCODE_CONTROL_SIGNAL = 0xC; + private static final Byte OPCODE_RESOURCE_SIGNAL = 0xF; + + private static final String WEBSOCKET_PATH = "/v2/control/socket"; + + /** + * Handler to open the websocket for the real-time control signals + * + * @param ctx, + * @param req, + * + * @return void + */ + public void handle(ChannelHandlerContext ctx, HttpRequest req) { + try { + LoggingService.logDebug(MODULE_NAME, "Open the websocket for the real-time control signals"); + String uri = req.uri(); + uri = uri.substring(1); + String[] tokens = uri.split("/"); + + String id; + + if (tokens.length < 5) { + LoggingService.logError(MODULE_NAME, " Missing ID or ID value in URL ", + new AgentSystemException(" Missing ID or ID value in URL")); + return; + } else { + id = tokens[4].trim(); + } + + // Handshake + WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req), + null, true, Integer.MAX_VALUE); + WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req); + if (handshaker == null) { + WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); + } else { + handshaker.handshake(ctx.channel(), req); + } + + WebSocketMap.addWebsocket('C', id, ctx); + StatusReporter.setLocalApiStatus().setOpenConfigSocketsCount(WebSocketMap.controlWebsocketMap.size()); + LoggingService.logDebug(MODULE_NAME, "Websocket for the real-time control signals is open"); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error in Handler to open the websocket for the real-time control signals", + new AgentSystemException("Error in Handler to open the websocket for the real-time control signals")); + } + } + + /** + * Handler for the real-time control signals Receive ping and send pong Send + * control signals to container on configuration change + * + * @param ctx, + * @param frame, + * @return void + */ + public void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { + LoggingService.logDebug(MODULE_NAME, "Send control signals to container on configuration change"); + if (frame instanceof PingWebSocketFrame) { + ByteBuf buffer = frame.content(); + if (buffer.readableBytes() == 1) { + Byte opcode = buffer.readByte(); + if (opcode == OPCODE_PING.intValue()) { + if (WebsocketUtil.hasContextInMap(ctx, WebSocketMap.controlWebsocketMap)) { + ByteBuf buffer1 = ctx.alloc().buffer(); + buffer1.writeByte(OPCODE_PONG.intValue()); + ctx.channel().write(new PongWebSocketFrame(buffer1)); + } + } + } else { + LoggingService.logDebug(MODULE_NAME, "Opcode not found for sending control signal"); + } + + return; + } + + if (frame instanceof BinaryWebSocketFrame) { + ByteBuf buffer2 = frame.content(); + if (buffer2.readableBytes() == 1) { + Byte opcode = buffer2.readByte(); + if (opcode == OPCODE_ACK.intValue()) { + WebSocketMap.unackControlSignalsMap.remove(ctx); + return; + } + } + } + + if (frame instanceof CloseWebSocketFrame) { + ctx.channel().close(); + WebsocketUtil.removeWebsocketContextFromMap(ctx, WebSocketMap.controlWebsocketMap); + StatusReporter.setLocalApiStatus().setOpenConfigSocketsCount(WebSocketMap.controlWebsocketMap.size()); + } + } + + /** + * Helper method to compare the configuration map to start control signals + * + * @param oldConfigMap + * @param newConfigMap + * @return void + */ + public void initiateControlSignal(Map oldConfigMap, Map newConfigMap) { + // Compare the old and new config map + Map controlMap = WebSocketMap.controlWebsocketMap; + ArrayList changedConfigElmtsList = new ArrayList<>(); + if (newConfigMap != null && newConfigMap.size() > 0) { + for (Map.Entry newEntry : newConfigMap.entrySet()) { + String newMapKey = newEntry.getKey(); + if (!oldConfigMap.containsKey(newMapKey)) { + changedConfigElmtsList.add(newMapKey); + } else { + + String newConfigValue = newEntry.getValue(); + String oldConfigValue = oldConfigMap.get(newMapKey); + if (!newConfigValue.equals(oldConfigValue)) { + changedConfigElmtsList.add(newMapKey); + } + } + } + } + + + for (String changedConfigElmtId : changedConfigElmtsList) { + if (controlMap.containsKey(changedConfigElmtId)) { + ChannelHandlerContext ctx = controlMap.get(changedConfigElmtId); + WebSocketMap.unackControlSignalsMap.put(ctx, new ControlSignalSentInfo(1, System.currentTimeMillis())); + + ByteBuf buffer1 = ctx.alloc().buffer(); + buffer1.writeByte(OPCODE_CONTROL_SIGNAL); + ctx.channel().writeAndFlush(new BinaryWebSocketFrame(buffer1)); + } + } + } + + /** + * Websocket path + * + * @param req + * @return void + */ + private static String getWebSocketLocation(HttpRequest req) { + String location = req.headers().get(HOST) + WEBSOCKET_PATH; + if (LocalApiServer.SSL) { + return "wss://" + location; + } else { + return "ws://" + location; + } + } + + public void initiateResourceSignal() { + Map controlMap = WebSocketMap.controlWebsocketMap; + for (Map.Entry newEntry : controlMap.entrySet()) { + ChannelHandlerContext ctx = newEntry.getValue(); + WebSocketMap.unackControlSignalsMap.put(ctx, new ControlSignalSentInfo(1, System.currentTimeMillis())); + + ByteBuf buffer1 = ctx.alloc().buffer(); + buffer1.writeByte(OPCODE_RESOURCE_SIGNAL); + ctx.channel().writeAndFlush(new BinaryWebSocketFrame(buffer1)); + } + + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketWorker.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketWorker.java new file mode 100644 index 00000000..113cba4b --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketWorker.java @@ -0,0 +1,82 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import java.util.Map; + +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.logging.LoggingService; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; + +/** + * Helper class for the control websocket + * Enable control signal for the unacknowledged signals in map + * @author ashita + * @since 2016 + */ +public class ControlWebsocketWorker implements Runnable{ + private static final String MODULE_NAME = "Local API"; + private static final Byte OPCODE_CONTROL_SIGNAL = 0xC; + + /** + * Initiating control signals for unacknowledged signals + * If tried for 10 times, then disable real-time service for the channel + * @return void + */ + @Override + public void run() { + Thread.currentThread().setName(Constants.LOCAL_API_CONTROL_WEBSOCKET_WORKER); + LoggingService.logDebug(MODULE_NAME,"Initiating control signals for unacknowledged signals"); + + for(Map.Entry contextEntry : WebSocketMap.unackControlSignalsMap.entrySet()){ + ChannelHandlerContext ctx = contextEntry.getKey(); + ControlSignalSentInfo controlSignalSentInfo = contextEntry.getValue(); + int tryCount = controlSignalSentInfo.getSendTryCount(); + + long lastSendTime = WebSocketMap.unackControlSignalsMap.get(ctx).getTimeMillis(); + long timeEllapsed = (System.currentTimeMillis() - lastSendTime)/1000; + + if (timeEllapsed > 20) { + if (tryCount < 10) { + initiateControlSignal(ctx); + } else { + LoggingService.logDebug(MODULE_NAME, " Initiating control signal expires"); + WebSocketMap.unackControlSignalsMap.remove(ctx); + WebsocketUtil.removeWebsocketContextFromMap(ctx, WebSocketMap.controlWebsocketMap); + StatusReporter.setLocalApiStatus().setOpenConfigSocketsCount(WebSocketMap.controlWebsocketMap.size()); + return; + } + } + } + LoggingService.logDebug(MODULE_NAME,"Finished Initiating control signals for unacknowledged signals"); + } + + /** + * Helper method to initiate control sinals + * @param ctx + * @return void + */ + private void initiateControlSignal(ChannelHandlerContext ctx) { + ControlSignalSentInfo controlSignalSentInfo = WebSocketMap.unackControlSignalsMap.get(ctx); + int tryCount = controlSignalSentInfo.getSendTryCount() + 1; + WebSocketMap.unackControlSignalsMap.put(ctx, new ControlSignalSentInfo(tryCount, System.currentTimeMillis())); + + ByteBuf buffer1 = ctx.alloc().buffer(); + buffer1.writeByte(OPCODE_CONTROL_SIGNAL); + ctx.channel().writeAndFlush(new BinaryWebSocketFrame(buffer1)); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/DeprovisionApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/DeprovisionApiHandler.java new file mode 100644 index 00000000..7867a977 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/DeprovisionApiHandler.java @@ -0,0 +1,83 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.utils.logging.LoggingService; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; +import static io.netty.handler.codec.http.HttpMethod.DELETE; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class DeprovisionApiHandler implements Callable { + private static final String MODULE_NAME = "Local API : DeprovisionApiHandler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public DeprovisionApiHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start deprovison Api Handler http request"); + if (!ApiHandlerHelpers.validateMethod(this.req, DELETE)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", + new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + if (!ApiHandlerHelpers.validateAccessToken(this.req)) { + String errorMsg = "Incorrect access token"; + outputBuffer.writeBytes(errorMsg.getBytes(UTF_8)); + LoggingService.logError(MODULE_NAME, errorMsg, new AgentUserException(errorMsg)); + return ApiHandlerHelpers.unauthorizedResponse(outputBuffer, errorMsg); + } + + try { + String status = FieldAgent.getInstance().deProvision(false); + + ObjectMapper objectMapper = new ObjectMapper(); + Map resultMap = new HashMap() {{ + put("message", status.replaceAll("\n", "")); + put("status", status.contains("Success") ? "success" : "failed"); + }}; + + String jsonResult = objectMapper.writeValueAsString(resultMap); + FullHttpResponse res; + if (resultMap.get("status").equals("failed")) { + res = ApiHandlerHelpers.internalServerErrorResponse(outputBuffer, jsonResult); + } else { + res = ApiHandlerHelpers.successResponse(outputBuffer, jsonResult); + } + res.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + LoggingService.logDebug(MODULE_NAME, "Finished status Api Handler http request"); + return res; + } catch (Exception e) { + String errorMsg = "Log message parsing error, " + e.getMessage(); + LoggingService.logError(MODULE_NAME, errorMsg, e); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/EdgeResourceHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/EdgeResourceHandler.java new file mode 100644 index 00000000..425d2b24 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/EdgeResourceHandler.java @@ -0,0 +1,75 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; +import org.eclipse.iofog.edge_resources.EdgeResource; +import org.eclipse.iofog.edge_resources.EdgeResourceManager; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObjectBuilder; +import java.util.List; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class EdgeResourceHandler implements Callable { + private static final String MODULE_NAME = "Local API : edge resource handler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public EdgeResourceHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Processing edge resources http request"); + if (!ApiHandlerHelpers.validateMethod(this.req, GET)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + try { + List edgeResources = EdgeResourceManager.getInstance().getLatestEdgeResources(); + ObjectMapper objectMapper = new ObjectMapper(); + String jsonResult = objectMapper.writeValueAsString(edgeResources); + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder builder = factory.createObjectBuilder(); + builder.add("edgeResources", jsonResult); + String edgeResourcesResult = builder.build().toString(); + + FullHttpResponse res; + res = ApiHandlerHelpers.successResponse(outputBuffer, edgeResourcesResult); + res.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + LoggingService.logDebug(MODULE_NAME, "Finished processing edge resources http request"); + return res; + } catch (Exception e) { + String errorMsg = "Log message parsing error"; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentUserException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GetConfigurationHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GetConfigurationHandler.java new file mode 100644 index 00000000..b219ee42 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GetConfigurationHandler.java @@ -0,0 +1,127 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.*; +import java.io.StringReader; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Handler to get the current configuration of the container + * + * @author ashita + * @since 2016 + */ +public class GetConfigurationHandler implements Callable { + + private static final String MODULE_NAME = "Local API : GetConfigurationHandler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public GetConfigurationHandler(HttpRequest req, ByteBuf outputBuffer, byte[] content) { + this.req = req; + this.outputBuffer = outputBuffer; + this.content = content; + } + + /** + * Handler method to get the configuration for the container + * + * @return Object + */ + private FullHttpResponse handleGetConfigurationRequest() { + LoggingService.logDebug(MODULE_NAME, "Processing config http request"); + if (!ApiHandlerHelpers.validateMethod(this.req, POST)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, new AgentUserException(contentTypeError)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + String requestBody = new String(content, UTF_8); + JsonReader reader = Json.createReader(new StringReader(requestBody)); + JsonObject jsonObject = reader.readObject(); + + try { + validateRequest(jsonObject); + } catch (AgentUserException e) { + String errorMsg = "Incorrect content/data"; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentSystemException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } catch (Exception e) { + String errorMsg = "Incorrect content/data "; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentSystemException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + + String receiverId = jsonObject.getString("id"); + + if (ConfigurationMap.containerConfigMap.containsKey(receiverId)) { + String containerConfig = ConfigurationMap.containerConfigMap.get(receiverId); + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder builder = factory.createObjectBuilder(); + builder.add("status", "okay"); + builder.add("config", containerConfig); + String result = builder.build().toString(); + + LoggingService.logDebug(MODULE_NAME, "Finished processing config request"); + return ApiHandlerHelpers.successResponse(outputBuffer, result); + } else { + String errorMsg = "No configuration found for the id " + receiverId; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentUserException(errorMsg)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } + + /** + * Validate the request + * + * @param jsonObject + * @return String + */ + private void validateRequest(JsonObject jsonObject) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Validate config request"); + if (!jsonObject.containsKey("id") || + jsonObject.isNull("id") || + jsonObject.getString("id").trim().equals("")) + throw new AgentUserException(" Id value not found "); + } + + /** + * Overriden method of the Callable interface which call the handler method + * + * @return Object + */ + @Override + public FullHttpResponse call() throws Exception { + return handleGetConfigurationRequest(); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GpsApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GpsApiHandler.java new file mode 100644 index 00000000..2611c84a --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GpsApiHandler.java @@ -0,0 +1,123 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.gps.GpsMode; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.*; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.util.Date; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class GpsApiHandler implements Callable { + private static final String MODULE_NAME = "Local API : gps API"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public GpsApiHandler(HttpRequest req, ByteBuf outputBuffer, byte[] content) { + this.req = req; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() { + LoggingService.logDebug(MODULE_NAME, "Processing gps http request"); + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, new Exception()); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + if (req.method() == POST) { + return setAgentGpsCoordinates(); + } else if (req.method() == GET) { + return getAgentGpsCoordinates(); + } else { + String errorMsg = "Not supported method: " + req.method(); + outputBuffer.writeBytes(errorMsg.getBytes()); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } + + private FullHttpResponse setAgentGpsCoordinates() { + LoggingService.logDebug(MODULE_NAME, "Post agent gps coordinates http request"); + String msgString = new String(content, StandardCharsets.UTF_8); + JsonReader reader = Json.createReader(new StringReader(msgString)); + JsonObject jsonObject = reader.readObject(); + + String lat = jsonObject.getString("lat"); + String lon = jsonObject.getString("lon"); + + String gpsCoordinates = lat + "," + lon; + + try { + Configuration.setGpsDataIfValid(GpsMode.DYNAMIC, gpsCoordinates); + Configuration.writeGpsToConfigFile(); + Configuration.saveConfigUpdates(); + } catch (Exception e) { + String errorMsg = " Error with setting GPS"; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentSystemException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder builder = factory.createObjectBuilder(); + builder.add("status", "okay"); + + String sendMessageResult = builder.build().toString(); + + LoggingService.logDebug(MODULE_NAME, "Finished processing gps request"); + + return ApiHandlerHelpers.successResponse(outputBuffer, sendMessageResult); + } + + private FullHttpResponse getAgentGpsCoordinates() { + + LoggingService.logDebug(MODULE_NAME, "Get agent gps coordinates"); + + String gpsCoordinates = Configuration.getGpsCoordinates(); + String[] latLon = gpsCoordinates.split(","); + + String lat = latLon[0]; + String lon = latLon[1]; + + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder builder = factory.createObjectBuilder(); + builder.add("status", "okay"); + builder.add("timestamp", new Date().getTime()); + builder.add("lat", lat); + builder.add("lon", lon); + + String sendMessageResult = builder.build().toString(); + LoggingService.logDebug(MODULE_NAME, "Finished processing gps http request"); + + return ApiHandlerHelpers.successResponse(outputBuffer, sendMessageResult); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/InfoApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/InfoApiHandler.java new file mode 100644 index 00000000..7a60744d --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/InfoApiHandler.java @@ -0,0 +1,94 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; + +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class InfoApiHandler implements Callable { + private static final String MODULE_NAME = "Local API : Info Api handler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public InfoApiHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Processing info http request"); + if (!ApiHandlerHelpers.validateMethod(this.req, GET)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + if (!ApiHandlerHelpers.validateAccessToken(this.req)) { + String errorMsg = "Incorrect access token"; + outputBuffer.writeBytes(errorMsg.getBytes(UTF_8)); + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException(errorMsg)); + return ApiHandlerHelpers.unauthorizedResponse(outputBuffer, errorMsg); + } + + try { + String[] info = Configuration.getConfigReport().split("\\\\n"); + + ObjectMapper objectMapper = new ObjectMapper(); + Map resultMap = new HashMap<>(); + for (String it : info) { + String[] infoItem = it.split(" : "); + String key = infoItem[0].trim().toLowerCase().replace(" ", "-"); + if (key.equals("gps-coordinates(lat,lon)")) { + key = "gps-coordinates"; + } else if (key.equals("developer's-mode")) { + key = "developer-mode"; + } + resultMap.put(key, infoItem[1].trim()); + } + + String jsonResult = objectMapper.writeValueAsString(resultMap); + FullHttpResponse res; + res = ApiHandlerHelpers.successResponse(outputBuffer, jsonResult); + res.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + LoggingService.logDebug(MODULE_NAME, "Finished processing info http request"); + return res; + } catch (Exception e) { + String errorMsg = "Log message parsing error"; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentUserException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApi.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApi.java new file mode 100644 index 00000000..56e6feb4 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApi.java @@ -0,0 +1,128 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.Constants.ModulesStatus; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.HashMap; +import java.util.Map; + +/** + * Local api point of start using iofog. + * Get and update the configuration for local api module. + * @author ashita + * @since 2016 + */ +public class LocalApi implements Runnable { + + private final String MODULE_NAME = "Local API"; + private static volatile LocalApi instance; + private LocalApiServer server; + + private LocalApi() { + + } + + /** + * Instantiate local api - singleton + * @return LocalApi + */ + public static LocalApi getInstance(){ + LocalApi localInstance = instance; + if (localInstance == null) { + synchronized (LocalApi.class) { + localInstance = instance; + if (localInstance == null) { + instance = localInstance = new LocalApi(); + } + } + } + return localInstance; + } + + /** + * Stop local api server + */ + public void stopServer() { + server.stop(); + } + + + /** + * Start local api server + * Instantiate websocket map and configuration map + */ + @Override + public void run() { + LoggingService.logInfo(MODULE_NAME, "Start local api server"); + StatusReporter.setSupervisorStatus().setModuleStatus(Constants.LOCAL_API, ModulesStatus.STARTING); + + StatusReporter.setLocalApiStatus().setOpenConfigSocketsCount(WebSocketMap.controlWebsocketMap.size()); + StatusReporter.setLocalApiStatus().setOpenMessageSocketsCount(WebSocketMap.messageWebsocketMap.size()); + retrieveContainerConfig(); + + server = new LocalApiServer(); + try { + server.start(); + } catch (Exception e) { + stopServer(); + LoggingService.logError(MODULE_NAME, "", new AgentSystemException("Unable to start local api server", e)); + StatusReporter.setSupervisorStatus().setModuleStatus(Constants.LOCAL_API, ModulesStatus.STOPPED); + } + LoggingService.logInfo(MODULE_NAME, "Local api server started"); + } + + /** + * Get the containers configuration and store it. + */ + private void retrieveContainerConfig() { + ConfigurationMap.containerConfigMap = MicroserviceManager.getInstance().getConfigs(); + LoggingService.logDebug(MODULE_NAME, "Container configuration retrieved"); + } + + /** + * Update the containers configuration and store it. + */ + private void updateContainerConfig() { + ConfigurationMap.containerConfigMap = MicroserviceManager.getInstance().getConfigs(); + LoggingService.logDebug(MODULE_NAME, "Container configuration updated"); + } + + /** + * Initiate the real-time control signal when the cofiguration changes. + * Called by field-agent. + */ + public void update(){ + LoggingService.logDebug(MODULE_NAME, "Start the real-time control signal when the configuration updated"); + Map oldConfigMap = new HashMap<>(); + oldConfigMap.putAll(ConfigurationMap.containerConfigMap); + updateContainerConfig(); + Map newConfigMap = new HashMap<>(); + newConfigMap.putAll(ConfigurationMap.containerConfigMap); + ControlWebsocketHandler handler = new ControlWebsocketHandler(); + handler.initiateControlSignal(oldConfigMap, newConfigMap); + LoggingService.logDebug(MODULE_NAME, "Finish the real-time control signal when the configuration updated"); + } + + public void updateEdgeResource() { + LoggingService.logDebug(MODULE_NAME, "Start the real-time control signal when the edge resources are updated"); + ControlWebsocketHandler handler = new ControlWebsocketHandler(); + handler.initiateResourceSignal(); + LoggingService.logDebug(MODULE_NAME, "Finished the real-time control signal when the edge resources are updated"); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServer.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServer.java new file mode 100644 index 00000000..970fa433 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServer.java @@ -0,0 +1,83 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +import org.eclipse.iofog.utils.logging.LoggingService; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.SelfSignedCertificate; + +/** + * Local Api Server + * @author ashita + * @since 2016 + */ +public final class LocalApiServer { + private static final String MODULE_NAME = "Local API"; + + private final EventLoopGroup bossGroup = new NioEventLoopGroup(1); + private final EventLoopGroup workerGroup = new NioEventLoopGroup(10); + + static final boolean SSL = System.getProperty("ssl") != null; + private static final int PORT = 54321; + + /** + * Create and start local api server + */ + public void start() throws Exception { + final SslContext sslCtx; + if (SSL) { + SelfSignedCertificate ssc = new SelfSignedCertificate(); + sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); + } else { + sslCtx = null; + } + try{ + ServerBootstrap b = new ServerBootstrap(); + b.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .childHandler(new LocalApiServerPipelineFactory(sslCtx)); + + Channel ch = b.bind(PORT).sync().channel(); + + LoggingService.logDebug(MODULE_NAME, "Local api server started at port: " + PORT + "\n"); + + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(new ControlWebsocketWorker(), 10, 10, TimeUnit.SECONDS); + scheduler.scheduleAtFixedRate(new MessageWebsocketWorker(), 10, 10, TimeUnit.SECONDS); + ch.closeFuture().sync(); + }finally{ + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } + + /** + * Stop local api server + */ + void stop() { + LoggingService.logDebug(MODULE_NAME, "Stopping Local api server\n"); + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerHandler.java new file mode 100644 index 00000000..43a010dd --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerHandler.java @@ -0,0 +1,380 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.*; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.util.CharsetUtil; +import io.netty.util.ReferenceCountUtil; +import io.netty.util.ReferenceCounted; +import io.netty.util.concurrent.EventExecutorGroup; +import io.netty.util.concurrent.Future; +import io.netty.util.concurrent.GenericFutureListener; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Provide handler for the rest api and real-time websocket depending on the request. + * Send response after processing. + * + * @author ashita + * @since 2016 + */ + +public class LocalApiServerHandler extends SimpleChannelInboundHandler { + + private static final String MODULE_NAME = "Local API : LocalApiServerHandler"; + + private HttpRequest request; + private ByteArrayOutputStream baos; + private byte[] content; + + private final EventExecutorGroup executor; + + public LocalApiServerHandler(EventExecutorGroup executor) { + super(false); + this.executor = executor; + } + + /** + * Method to be called on channel initializing + * Can take requests as HttpRequest or Websocket frame + * + * @param ctx ChannelHandlerContext + * @param msg Object + */ + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) { + LoggingService.logDebug(MODULE_NAME, "Start channel initializing"); + try { + if (msg instanceof FullHttpRequest) { + // full request + ByteBuf content = null; + try { + FullHttpRequest request = (FullHttpRequest) msg; + this.request = request; + content = request.content(); + this.content = new byte[content.readableBytes()]; + content.readBytes(this.content); + handleHttpRequest(ctx); + } finally { + release(content); + release(msg); + } + } else if (msg instanceof HttpRequest) { + // chunked request + if (this.baos == null) + this.baos = new ByteArrayOutputStream(); + request = (HttpRequest) msg; + } else if (msg instanceof WebSocketFrame) { + try { + String mapName = findContextMapName(ctx); + if (mapName != null && mapName.equals("control")) { + ControlWebsocketHandler controlSocket = new ControlWebsocketHandler(); + controlSocket.handleWebSocketFrame(ctx, (WebSocketFrame) msg); + } else if (mapName != null && mapName.equals("message")) { + MessageWebsocketHandler messageSocket = new MessageWebsocketHandler(); + messageSocket.handleWebSocketFrame(ctx, (WebSocketFrame) msg); + } else { + LoggingService.logError(MODULE_NAME, "Cannot initiate real-time service: Context not found", + new AgentSystemException("Cannot initiate real-time service: Context not found")); + } + } finally { + release(msg); + } + } else if (msg instanceof HttpContent) { + HttpContent httpContent = (HttpContent) msg; + ByteBuf content = httpContent.content(); + if (content.isReadable()) { + try { + content.readBytes(this.baos, content.readableBytes()); + } catch (IOException e) { + String errorMsg = "Out of memory"; + LoggingService.logError(MODULE_NAME, errorMsg, e); + ByteBuf errorMsgBytes = ctx.alloc().buffer(); + errorMsgBytes.writeBytes(errorMsg.getBytes(UTF_8)); + sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_FOUND, errorMsgBytes)); + return; + } finally { + release(content); + } + } + + if (msg instanceof LastHttpContent) { // last chunk + this.content = baos.toByteArray(); + baos = null; + try { + handleHttpRequest(ctx); + } finally { + release(request); + } + } + } + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Failed to initialize channel for the request", e); + } + LoggingService.logDebug(MODULE_NAME, "Finished channel initializing"); + } + + /** + * Method to be called if the request is HttpRequest + * Pass the request to the handler call as per the request URI + * + * @param ctx ChannelHandlerContext + */ + private void handleHttpRequest(ChannelHandlerContext ctx) { + LoggingService.logDebug(MODULE_NAME, "Start passig request to the relevant handler"); + + if (request.uri().equals("/v2/config/get")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing config/get request"); + Callable callable = new GetConfigurationHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing config/get request"); + return; + } + + if (request.uri().equals("/v2/messages/next")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing messages/next request"); + Callable callable = new MessageReceiverHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing messages/next request"); + return; + } + + if (request.uri().equals("/v2/messages/new")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing messages/new request"); + Callable callable = new MessageSenderHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing messages/new request"); + return; + } + + if (request.uri().equals("/v2/messages/query")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing messages/query request"); + Callable callable = new QueryMessageReceiverHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing messages/query request"); + return; + } + + if (request.uri().startsWith("/v2/restblue")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing restblue request"); + Callable callable = new BluetoothApiHandler((FullHttpRequest) request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing restblue request"); + return; + } + + if (request.uri().startsWith("/v2/log")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing log request"); + Callable callable = new LogApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing log request"); + return; + } + + if (request.uri().startsWith("/v2/commandline")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing commandline request"); + Callable callable = new CommandLineApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "finished Processing commandline request"); + return; + } + + if (request.uri().equals("/v2/gps")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing gps request"); + Callable callable = new GpsApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing commandline request"); + return; + } + + if (request.uri().startsWith("/v2/control/socket")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing control/socket request"); + ControlWebsocketHandler controlSocket = new ControlWebsocketHandler(); + controlSocket.handle(ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing control/socket request"); + return; + } + + if (request.uri().startsWith("/v2/message/socket")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing message/socket request"); + MessageWebsocketHandler messageSocket = new MessageWebsocketHandler(); + messageSocket.handle(ctx, request); + LoggingService.logInfo(MODULE_NAME, "finished Processing message/socket request"); + return; + } + + if (request.uri().startsWith("/v2/config")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing config request"); + Callable callable = new ConfigApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing config request"); + return; + } + + if (request.uri().startsWith("/v2/provision")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing provision request"); + Callable callable = new ProvisionApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing provision request"); + return; + } + + if (request.uri().startsWith("/v2/deprovision")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing deprovision request"); + Callable callable = new DeprovisionApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing deprovision request"); + return; + } + + if (request.uri().startsWith("/v2/info")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing info request"); + Callable callable = new InfoApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing info request"); + return; + } + + if (request.uri().startsWith("/v2/status")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing status request"); + Callable callable = new StatusApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing status request"); + return; + } + + if (request.uri().startsWith("/v2/version")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing version request"); + Callable callable = new VersionApiHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing version request"); + return; + } + + if (request.uri().startsWith("/v2/edgeResources")) { + LoggingService.logInfo(MODULE_NAME, "Start Processing version request"); + Callable callable = new EdgeResourceHandler(request, ctx.alloc().buffer(), content); + runTask(callable, ctx, request); + LoggingService.logInfo(MODULE_NAME, "Finished Processing version request"); + return; + } + + LoggingService.logError(MODULE_NAME, "Error: Request not found", new AgentSystemException("Error: Request not found")); + ByteBuf errorMsgBytes = ctx.alloc().buffer(); + String errorMsg = " Request not found "; + errorMsgBytes.writeBytes(errorMsg.getBytes(UTF_8)); + sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_FOUND, errorMsgBytes)); + LoggingService.logDebug(MODULE_NAME, "Finished passig request to the relevant handler"); + + } + + private String findContextMapName(ChannelHandlerContext ctx) { + + if (WebsocketUtil.hasContextInMap(ctx, WebSocketMap.controlWebsocketMap)) { + LoggingService.logDebug(MODULE_NAME, "Context map name : control"); + return "control"; + } + else if (WebsocketUtil.hasContextInMap(ctx, WebSocketMap.messageWebsocketMap)) { + LoggingService.logDebug(MODULE_NAME, "Context map name : message"); + return "message"; + } + + else { + LoggingService.logDebug(MODULE_NAME, "Context map name : null"); + return null; + } + + } + + /** + * Method to be called on channel complete + * + * @param ctx ChannelHandlerContext + */ + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + /** + * Helper for request thread + * + * @param callable + * @param ctx + * @param req + */ + private void runTask(Callable callable, ChannelHandlerContext ctx, HttpRequest req) { + final Future future = executor.submit(callable); + future.addListener((GenericFutureListener>) futureListener -> { + if (futureListener.isSuccess()) { + sendHttpResponse(ctx, req, (FullHttpResponse) futureListener.get()); + } else { + ctx.fireExceptionCaught(futureListener.cause()); + ctx.close(); + } + }); + } + + /** + * Provide the response as per the requests + * + * @param ctx + * @param req + * @param res + */ + private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, FullHttpResponse res) { + LoggingService.logDebug(MODULE_NAME, "Start providing response as per the request"); + if (res.status().code() != 200) { + ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8); + res.content().writeBytes(buf); + buf.release(); + HttpUtil.setContentLength(res, res.content().readableBytes()); + } + + ChannelFuture f = ctx.channel().writeAndFlush(res); + if (!HttpUtil.isKeepAlive(req) || res.status().code() != 200) { + f.addListener(ChannelFutureListener.CLOSE); + } + LoggingService.logDebug(MODULE_NAME, "Response sent"); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) + throws Exception { + LoggingService.logError(MODULE_NAME, "Uncaught exception", cause); + FullHttpResponse response = ApiHandlerHelpers.internalServerErrorResponse(ctx.alloc().buffer(), cause.getMessage()); + sendHttpResponse(ctx, request, response); + } + + private void release(Object obj) { + LoggingService.logDebug(MODULE_NAME, "Releasing object lock"); + if ((obj instanceof ReferenceCounted) && ((ReferenceCounted) obj).refCnt() > 0) { + ReferenceCountUtil.release(obj); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactory.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactory.java new file mode 100644 index 00000000..d97e61ec --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactory.java @@ -0,0 +1,58 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import org.eclipse.iofog.utils.logging.LoggingService; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.ssl.SslContext; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; + +/** + * Pipeline factory to initialize the channel and assign handler for the request. + * Thread pool for the performance + * @author ashita + * @since 2016 + */ +public class LocalApiServerPipelineFactory extends ChannelInitializer{ + private final SslContext sslCtx; + private final EventExecutorGroup executor; + private static final String MODULE_NAME = "Local API : LocalApi ServerPipelineFactory"; + + public LocalApiServerPipelineFactory(SslContext sslCtx) { + this.sslCtx = sslCtx; + this.executor = new DefaultEventExecutorGroup(10); + } + + /** + * Initialize channel for communication and assign handler + * @param ch + * @return void + */ + public void initChannel(SocketChannel ch) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start Initialize channel for communication and assign handler"); + ChannelPipeline pipeline = ch.pipeline(); + if (sslCtx != null) { + pipeline.addLast(sslCtx.newHandler(ch.alloc())); + } + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpObjectAggregator(Integer.MAX_VALUE)); + pipeline.addLast(new LocalApiServerHandler(executor)); + LoggingService.logDebug(MODULE_NAME, "Finished Initialize channel for communication and assign handler"); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiStatus.java new file mode 100644 index 00000000..dc32734f --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiStatus.java @@ -0,0 +1,60 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +/** + * Local api status information send to the status reporter + * @author ashita + * @since 2016 + */ +public class LocalApiStatus { + private int openConfigSocketsCount; + private int openMessageSocketsCount; + + + /** + * Get number of open control sockets at instance + * @return int + */ + public int getOpenConfigSocketsCount() { + return openConfigSocketsCount; + } + + /** + * Set number of open control sockets at instance + * @param openConfigSocketsCount + * @return LocalApiStatus + */ + public LocalApiStatus setOpenConfigSocketsCount(int openConfigSocketsCount) { + this.openConfigSocketsCount = openConfigSocketsCount; + return this; + } + + /** + * Get number of open message sockets at instance + * @return int + */ + public int getOpenMessageSocketsCount() { + return openMessageSocketsCount; + } + + /** + * Set number of open message sockets at instance + * @param openMessageSocketsCount + * @return LocalApiStatus + */ + public LocalApiStatus setOpenMessageSocketsCount(int openMessageSocketsCount) { + this.openMessageSocketsCount = openMessageSocketsCount; + return this; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LogApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LogApiHandler.java new file mode 100644 index 00000000..1a50a8ec --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LogApiHandler.java @@ -0,0 +1,91 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; + +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.*; +import java.io.StringReader; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class LogApiHandler implements Callable { + private static final String MODULE_NAME = "Local API : LogApiHandler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public LogApiHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start handling http call of log api"); + if (!ApiHandlerHelpers.validateMethod(this.req, POST)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, new AgentUserException(contentTypeError)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + String msgString = new String(content, UTF_8); + JsonReader reader = Json.createReader(new StringReader(msgString)); + JsonObject jsonObject = reader.readObject(); + + boolean result = false; + if (jsonObject.containsKey("message") && + jsonObject.containsKey("type") && + jsonObject.containsKey("id")){ + String logMessage = jsonObject.getString("message"); + String logType = jsonObject.getString("type"); + String microserviceUuid = jsonObject.getString("id"); + if (logType.equals("info")) { + result = LoggingService.microserviceLogInfo(microserviceUuid, logMessage); + } else { + result = LoggingService.microserviceLogWarning(microserviceUuid, logMessage); + } + } + if (!result) { + String errorMsg = "Log message parsing error, " + "Logger initialized null"; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentUserException(errorMsg)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder builder = factory.createObjectBuilder(); + builder.add("status", "okay"); + + String sendMessageResult = builder.build().toString(); + + LoggingService.logDebug(MODULE_NAME, "Finished handling http call of log api"); + return ApiHandlerHelpers.successResponse(outputBuffer, sendMessageResult); + } + + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageCallback.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageCallback.java new file mode 100644 index 00000000..3b5f71a1 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageCallback.java @@ -0,0 +1,38 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import org.eclipse.iofog.message_bus.Message; + +/** + * Interface for the message bus to send real-time messages + * @author ashita + * @since 2016 + */ +public class MessageCallback { + private final String name; + + public MessageCallback(String name) { + this.name = name; + } + + /** + * Method called from message bus to send real-time messages to the containers + * @param message + * @return void + */ + public void sendRealtimeMessage(Message message) { + MessageWebsocketHandler handler = new MessageWebsocketHandler(); + handler.sendRealTimeMessage(name, message); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageReceiverHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageReceiverHandler.java new file mode 100644 index 00000000..1a965a1b --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageReceiverHandler.java @@ -0,0 +1,129 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; + +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.message_bus.Message; +import org.eclipse.iofog.message_bus.MessageBusUtil; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.*; +import java.io.StringReader; +import java.util.List; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Handler to deliver the messages to the receiver, if found any. + * + * @author ashita + * @since 2016 + */ +public class MessageReceiverHandler implements Callable { + + private static final String MODULE_NAME = "Local API : MessageReceiverHandler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public MessageReceiverHandler(HttpRequest req, ByteBuf outputBuffer, byte[] content) { + this.req = req; + this.outputBuffer = outputBuffer; + this.content = content; + } + + /** + * Handler method to deliver the messages to the receiver. Get the messages + * from message bus + * + * @return Object + */ + private FullHttpResponse handleMessageRecievedRequest() { + LoggingService.logDebug(MODULE_NAME, "Start Handler method to deliver the messages to the receiver."); + if (!ApiHandlerHelpers.validateMethod(this.req, POST)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, new AgentUserException(contentTypeError)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + String requestBody = new String(content, UTF_8); + JsonReader reader = Json.createReader(new StringReader(requestBody)); + JsonObject jsonObject = reader.readObject(); + + try { + validateRequest(jsonObject); + } catch (Exception e) { + String errorMsg = "Incorrect content/data" + e.getMessage(); + LoggingService.logError(MODULE_NAME, errorMsg, new AgentUserException(errorMsg, e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + + String receiverId = jsonObject.getString("id"); + + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder builder = factory.createObjectBuilder(); + JsonArrayBuilder messagesArray = factory.createArrayBuilder(); + + MessageBusUtil bus = new MessageBusUtil(); + List messageList = bus.getMessages(receiverId); + + for (Message msg : messageList) { + JsonObject msgJson = msg.toJson(); + messagesArray.add(msgJson); + } + builder.add("status", "okay"); + builder.add("count", messageList.size()); + builder.add("messages", messagesArray); + + String result = builder.build().toString(); + LoggingService.logDebug(MODULE_NAME, "Finished Handler method to deliver the messages to the receiver."); + return ApiHandlerHelpers.successResponse(outputBuffer, result); + } + + /** + * Validate the request + * MessageWebsocketHandler + * @param jsonObject + * @return String + */ + private void validateRequest(JsonObject jsonObject) throws Exception { + LoggingService.logDebug(MODULE_NAME, "validate the request"); + if (!jsonObject.containsKey("id") || + jsonObject.isNull("id") || + jsonObject.getString("id").trim().equals("")) + throw new AgentUserException(" Id value not found "); + } + + /** + * Overriden method of the Callable interface which call the handler method + * + * @return Object + */ + @Override + public FullHttpResponse call() throws Exception { + return handleMessageRecievedRequest(); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSenderHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSenderHandler.java new file mode 100644 index 00000000..efc7362b --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSenderHandler.java @@ -0,0 +1,179 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; + +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.message_bus.Message; +import org.eclipse.iofog.message_bus.MessageBusUtil; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.*; +import java.io.StringReader; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Handler to publish the messages from the container to message bus + * + * @author ashita + * @since 2016 + */ +public class MessageSenderHandler implements Callable { + private static final String MODULE_NAME = "Local API : MessageSenderHandler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public MessageSenderHandler(HttpRequest req, ByteBuf outputBuffer, byte[] content) { + this.req = req; + this.outputBuffer = outputBuffer; + this.content = content; + } + + /** + * Handler method to publish the messages from the container to message bus + * + * @return Object + */ + private FullHttpResponse handleMessageSenderRequest() { + LoggingService.logDebug(MODULE_NAME, "Publish the messages from the container to message bus"); + if (!ApiHandlerHelpers.validateMethod(this.req, POST)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, new AgentUserException(contentTypeError)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + String msgString = new String(content, UTF_8); + JsonReader reader = Json.createReader(new StringReader(msgString)); + JsonObject jsonObject = reader.readObject(); + + try { + validateMessage(jsonObject); + } catch (Exception e) { + String errorMsg = "Validation Error, " + e.getMessage(); + LoggingService.logError(MODULE_NAME, errorMsg, e); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + + MessageBusUtil bus = new MessageBusUtil(); + Message message; + try { + message = new Message(jsonObject); + } catch (Exception e) { + String errorMsg = " Message Parsing Error, " + e.getMessage(); + LoggingService.logError(MODULE_NAME, errorMsg, e); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + bus.publishMessage(message); + + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder builder = factory.createObjectBuilder(); + builder.add("status", "okay"); + builder.add("timestamp", message.getTimestamp()); + builder.add("id", message.getId()); + + String sendMessageResult = builder.build().toString(); + LoggingService.logDebug(MODULE_NAME, "Finished publish the messages from the container to message bus"); + return ApiHandlerHelpers.successResponse(outputBuffer, sendMessageResult); + } + + /** + * Validate the request and the message to be publish + * + * @param message + */ + private void validateMessage(JsonObject message) throws Exception { + + LoggingService.logDebug(MODULE_NAME, "Validate the request and the message to publish"); + if (!message.containsKey("publisher")) + throw new AgentUserException("Error: Missing input field publisher "); + if (!message.containsKey("version")) + throw new AgentUserException("Error: Missing input field version "); + if (!message.containsKey("infotype")) + throw new AgentUserException("Error: Missing input field infotype "); + if (!message.containsKey("infoformat")) + throw new AgentUserException("Error: Missing input field infoformat "); + if (!message.containsKey("contentdata")) + throw new AgentUserException("Error: Missing input field contentdata "); + + if ((message.getString("publisher").trim().equals(""))) + throw new AgentUserException("Error: Missing input field value publisher "); + if ((message.getString("infotype").trim().equals(""))) + throw new AgentUserException("Error: Missing input field value infotype "); + if ((message.getString("infoformat").trim().equals(""))) + throw new AgentUserException("Error: Missing input field value infoformat "); + + String version = message.get("version").toString(); + if (!(version.matches("[0-9]+"))) { + throw new AgentUserException("Error: Invalid value for version"); + } + + if (message.containsKey("sequencenumber")) { + String sNum = message.get("sequencenumber").toString(); + if (!(sNum.matches("[0-9]+"))) { + throw new AgentUserException("Error: Invalid value for field sequence number "); + } + } + + if (message.containsKey("sequencetotal")) { + String stot = message.get("sequencetotal").toString(); + if (!(stot.matches("[0-9]+"))) { + throw new AgentUserException("Error: Invalid value for field sequence total "); + } + } + + if (message.containsKey("priority")) { + String priority = message.get("priority").toString(); + if (!(priority.matches("[0-9]+"))) { + throw new AgentUserException("Error: Invalid value for field priority "); + } + } + + if (message.containsKey("chainposition")) { + String chainPos = message.get("chainposition").toString(); + if (!(chainPos.matches("[0-9]+"))) { + throw new AgentUserException("Error: Invalid value for field chain position "); + } + } + + if (message.containsKey("difficultytarget")) { + String difftarget = message.get("difficultytarget").toString(); + if (!(difftarget.matches("[0-9]*.?[0-9]*"))) { + throw new AgentUserException("Error: Invalid value for field difficulty target "); + } + } + } + + /** + * Overriden method of the Callable interface which call the handler method + * + * @return Object + */ + @Override + public FullHttpResponse call() { + return handleMessageSenderRequest(); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSentInfo.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSentInfo.java new file mode 100644 index 00000000..85bc2212 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSentInfo.java @@ -0,0 +1,77 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import org.eclipse.iofog.message_bus.Message; + +/** + * Unacknowledged message with the try count. + * @author ashita + * @since 2016 + */ +public class MessageSentInfo { + private Message message; + private int sendTryCount = 0; + private long timeMillis; + + + + MessageSentInfo(Message message, int count, long timeMillis){ + this.message = message; + this.sendTryCount = count; + this.timeMillis = timeMillis; + } + + public long getTimeMillis() { + return timeMillis; + } + + public void setTimeMillis(long timeMillis) { + this.timeMillis = timeMillis; + } + + /** + * Get message + * @return Message + */ + public Message getMessage() { + return message; + } + + /** + * Save message + * @param message + * @return void + */ + public void setMessage(Message message) { + this.message = message; + } + + /** + * Get message sending trial count + * @return int + */ + public int getSendTryCount() { + return sendTryCount; + } + + /** + * Save message sending trial count + * @param sendTryCount + * @return void + */ + public void setSendTryCount(int sendTryCount) { + this.sendTryCount = sendTryCount; + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketHandler.java new file mode 100644 index 00000000..993de1a8 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketHandler.java @@ -0,0 +1,240 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import static io.netty.handler.codec.http.HttpHeaders.Names.HOST; +import static java.nio.charset.StandardCharsets.UTF_8; + +import java.util.Map; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.message_bus.Message; +import org.eclipse.iofog.message_bus.MessageBus; +import org.eclipse.iofog.message_bus.MessageBusUtil; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.BytesUtil; +import org.eclipse.iofog.utils.logging.LoggingService; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; +import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; +import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; +import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; + +/** + * Hadler for the real-time message websocket Open real-time message websocket + * Send and receive real-time messages + * + * @author ashita + * @since 2016 + */ +public class MessageWebsocketHandler { + private static final String MODULE_NAME = "Local api : Message Websocket Handler"; + + private static final Byte OPCODE_PING = 0x9; + private static final Byte OPCODE_PONG = 0xA; + private static final Byte OPCODE_ACK = 0xB; + private static final Byte OPCODE_MSG = 0xD; + private static final Byte OPCODE_RECEIPT = 0xE; + + private static final String WEBSOCKET_PATH = "/v2/message/socket"; + + /** + * Handler to open the websocket for the real-time message websocket + * + * @param ctx,req + * @return void + */ + public void handle(ChannelHandlerContext ctx, HttpRequest req) { + LoggingService.logInfo(MODULE_NAME, "Start Handler to open the websocket for the real-time message websocket"); + String uri = req.uri(); + uri = uri.substring(1); + String[] tokens = uri.split("/"); + String publisherId; + + if (tokens.length < 5) { + LoggingService.logError(MODULE_NAME, " Missing ID or ID value in URL ", new AgentUserException("Missing ID or ID value in URL", null)); + return; + } else { + publisherId = tokens[4].trim().split("\\?")[0]; + } + + // Handshake + WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req), + null, true, Integer.MAX_VALUE); + WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req); + if (handshaker == null) { + WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel()); + } else { + handshaker.handshake(ctx.channel(), req); + } + + Map messageSocketMap = WebSocketMap.messageWebsocketMap; + messageSocketMap.put(publisherId, ctx); + StatusReporter.setLocalApiStatus().setOpenConfigSocketsCount(WebSocketMap.messageWebsocketMap.size()); + MessageBus.getInstance().enableRealTimeReceiving(publisherId); + + LoggingService.logInfo(MODULE_NAME, "Finished Handler to open the websocket for the real-time message websocket. Handshake end...."); + } + + /** + * Handler for the real-time messages Receive ping and send pong Sending and + * receiving real-time messages + * + * @param ctx, frame + * @return void + */ + public void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { + LoggingService.logDebug(MODULE_NAME, "Handle the real-time message receive and sending real time-time messages"); + if (frame instanceof PingWebSocketFrame) { + ByteBuf buffer = frame.content(); + if (buffer.readableBytes() == 1) { + Byte opcode = buffer.readByte(); + if (opcode == OPCODE_PING.intValue()) { + if (WebsocketUtil.hasContextInMap(ctx, WebSocketMap.messageWebsocketMap)) { + ByteBuf buffer1 = ctx.alloc().buffer(); + buffer1.writeByte(OPCODE_PONG.intValue()); + ctx.channel().writeAndFlush(new PongWebSocketFrame(buffer1)); + } + } + } else { + LoggingService.logDebug(MODULE_NAME, "Real-time message, Ping opcode not found"); + } + + return; + } + + if (frame instanceof BinaryWebSocketFrame) { + ByteBuf input = frame.content(); + if (!input.isReadable()) { + return; + } + + byte[] byteArray = new byte[input.readableBytes()]; + int readerIndex = input.readerIndex(); + input.getBytes(readerIndex, byteArray); + Byte opcode; + + if(byteArray.length >= 1){ + opcode = byteArray[0]; + }else{ + return; + } + + if (opcode == OPCODE_MSG.intValue()) { + if (byteArray.length >= 2) { + if (WebsocketUtil.hasContextInMap(ctx, WebSocketMap.messageWebsocketMap)) { + + int totalMsgLength = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(byteArray, 1, 5)); + try { + Message message = new Message(BytesUtil.copyOfRange(byteArray, 5, totalMsgLength + 5)); + + MessageBusUtil messageBus = new MessageBusUtil(); + messageBus.publishMessage(message); + + String messageId = message.getId(); + Long msgTimestamp = message.getTimestamp(); + ByteBuf buffer1 = ctx.alloc().buffer(); + + buffer1.writeByte(OPCODE_RECEIPT.intValue()); + + // send Length + int msgIdLength = messageId.length(); + buffer1.writeByte(msgIdLength); + buffer1.writeByte(Long.BYTES); + + // Send opcode, id and timestamp + buffer1.writeBytes(messageId.getBytes(UTF_8)); + buffer1.writeBytes(BytesUtil.longToBytes(msgTimestamp)); + ctx.channel().write(new BinaryWebSocketFrame(buffer1)); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "wrong message format, validation failed", new AgentSystemException(e.getMessage(), e)); + } + } + return; + } + } else if (opcode == OPCODE_ACK.intValue()) { + WebSocketMap.unackMessageSendingMap.remove(ctx); + return; + } + + return; + } + + // Check for closing frame + if (frame instanceof CloseWebSocketFrame) { + ctx.channel().close(); + MessageBus.getInstance() + .disableRealTimeReceiving(WebsocketUtil.getIdForWebsocket(ctx, WebSocketMap.messageWebsocketMap)); + WebsocketUtil.removeWebsocketContextFromMap(ctx, WebSocketMap.messageWebsocketMap); + StatusReporter.setLocalApiStatus().setOpenConfigSocketsCount(WebSocketMap.messageWebsocketMap.size()); + } + LoggingService.logDebug(MODULE_NAME, "Finished real-time message receive and sending real time-time messages"); + } + + /** + * Helper to send real-time messages + * + * @param receiverId, message + * @return void + */ + public void sendRealTimeMessage(String receiverId, Message message) { + LoggingService.logDebug(MODULE_NAME, "Send real-time messages"); + ChannelHandlerContext ctx; + Map messageSocketMap = WebSocketMap.messageWebsocketMap; + + if (messageSocketMap != null && messageSocketMap.containsKey(receiverId)) { + ctx = messageSocketMap.get(receiverId); + WebSocketMap.unackMessageSendingMap.put(ctx, new MessageSentInfo(message, 1, System.currentTimeMillis())); + + int totalMsgLength; + byte[] bytesMsg = message.getBytes(); + + totalMsgLength = bytesMsg.length; + + ByteBuf buffer1 = ctx.alloc().buffer(totalMsgLength + 5); + // Send Opcode + buffer1.writeByte(OPCODE_MSG); + // Total Length + buffer1.writeBytes(BytesUtil.integerToBytes(totalMsgLength)); + // Message + buffer1.writeBytes(bytesMsg); + ctx.channel().writeAndFlush(new BinaryWebSocketFrame(buffer1)); + } else { + LoggingService.logError(MODULE_NAME, "No active real-time websocket found for " + receiverId, + new AgentSystemException("No active real-time websocket found for " + receiverId, null)); + } + } + + /** + * Websocket path + * + * @param req + * @return void + */ + private static String getWebSocketLocation(HttpRequest req) { + LoggingService.logInfo(MODULE_NAME, "Get web socketLocation"); + String location = req.headers().get(HOST) + WEBSOCKET_PATH; + if (LocalApiServer.SSL) { + return "wss://" + location; + } else { + return "ws://" + location; + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketWorker.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketWorker.java new file mode 100644 index 00000000..79b3397e --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketWorker.java @@ -0,0 +1,96 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import java.util.Map; + +import org.eclipse.iofog.message_bus.Message; +import org.eclipse.iofog.message_bus.MessageBus; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.BytesUtil; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.logging.LoggingService; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; + +/** + * Helper class for the message websocket + * Initiate message sending for the unacknowledged messages in map + * @author ashita + * @since 2016 + */ +public class MessageWebsocketWorker implements Runnable{ + private static final String MODULE_NAME = "Local API"; + private static final Byte OPCODE_MSG = 0xD; +// private static int count = 0; + + /** + * Initiating message sending for the unacknowledged messages + * If tried for 10 times, then disable real-time service for the channel + * @return void + */ + @Override + public void run() { + Thread.currentThread().setName(Constants.LOCAL_API_MESSAGE_WEBSOCKET_WORKER); + LoggingService.logDebug(MODULE_NAME,"Initiating message sending for the unacknowledged messages"); + + for(Map.Entry contextEntry : WebSocketMap.unackMessageSendingMap.entrySet()){ + + ChannelHandlerContext ctx = contextEntry.getKey(); + int tryCount = WebSocketMap.unackMessageSendingMap.get(ctx).getSendTryCount(); + long lastSendTime = WebSocketMap.unackMessageSendingMap.get(ctx).getTimeMillis(); + long timeEllapsed = (System.currentTimeMillis() - lastSendTime)/1000; + + if(timeEllapsed > 20){ + if(tryCount < 10){ + sendRealTimeMessage(ctx); + } else { + WebSocketMap.unackMessageSendingMap.remove(ctx); + MessageBus.getInstance().disableRealTimeReceiving(WebsocketUtil.getIdForWebsocket(ctx, WebSocketMap.messageWebsocketMap)); + WebsocketUtil.removeWebsocketContextFromMap(ctx, WebSocketMap.messageWebsocketMap); + StatusReporter.setLocalApiStatus().setOpenConfigSocketsCount(WebSocketMap.messageWebsocketMap.size()); + return; + } + } + } + LoggingService.logDebug(MODULE_NAME,"Finished message sending for the unacknowledged messages"); + } + + /** + * Helper method to send real-time messages + * @return void + */ + private void sendRealTimeMessage(ChannelHandlerContext ctx){ + LoggingService.logDebug(MODULE_NAME, "Sending real-time messages"); +// count++; + MessageSentInfo messageContextAndCount = WebSocketMap.unackMessageSendingMap.get(ctx); + int tryCount = messageContextAndCount.getSendTryCount(); + Message message = messageContextAndCount.getMessage(); + tryCount = tryCount + 1; + WebSocketMap.unackMessageSendingMap.put(ctx, new MessageSentInfo(message, tryCount, System.currentTimeMillis())); + ByteBuf buffer1 = ctx.alloc().buffer(); + + //Send Opcode + buffer1.writeByte(OPCODE_MSG); + + byte[] bytesMsg = message.getBytes(); + int totalMsgLength = bytesMsg.length; + //Total Length + buffer1.writeBytes(BytesUtil.integerToBytes(totalMsgLength)); + //Message + buffer1.writeBytes(bytesMsg); + ctx.channel().writeAndFlush(new BinaryWebSocketFrame(buffer1)); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ProvisionApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ProvisionApiHandler.java new file mode 100644 index 00000000..b4b99cff --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ProvisionApiHandler.java @@ -0,0 +1,112 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonValue; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static java.lang.String.format; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.iofog.utils.CmdProperties.*; +import static org.eclipse.iofog.utils.configuration.Configuration.setConfig; + +public class ProvisionApiHandler implements Callable { + private static final String MODULE_NAME = "Local API : Provision Api Handler"; + private final String PROVISIONING_KEY = "provisioning-key"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public ProvisionApiHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Processing request in Provision Api Handler"); + if (!ApiHandlerHelpers.validateMethod(this.req, POST)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", + new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, + new AgentUserException(contentTypeError, new Exception())); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + if (!ApiHandlerHelpers.validateAccessToken(this.req)) { + String errorMsg = "Incorrect access token"; + outputBuffer.writeBytes(errorMsg.getBytes(UTF_8)); + LoggingService.logError(MODULE_NAME, contentTypeError, + new AgentUserException(errorMsg)); + return ApiHandlerHelpers.unauthorizedResponse(outputBuffer, errorMsg); + } + + try { + String msgString = new String(content, UTF_8); + JsonReader reader = Json.createReader(new StringReader(msgString)); + JsonObject provisionRequest = reader.readObject(); + + if (!provisionRequest.containsKey(PROVISIONING_KEY)) { + return ApiHandlerHelpers.badRequestResponse(outputBuffer, "Missing required property '" + PROVISIONING_KEY + "'"); + } + + String provisioningKey = provisionRequest.getString(PROVISIONING_KEY); + JsonObject provisioningResult = FieldAgent.getInstance().provision(provisioningKey); + + ObjectMapper objectMapper = new ObjectMapper(); + Map resultMap = new HashMap<>(); + for (String messageKey : provisioningResult.keySet()) { + resultMap.put(messageKey, provisioningResult.getString(messageKey)); + } + String jsonResult = objectMapper.writeValueAsString(resultMap); + FullHttpResponse res; + if (resultMap.get("status").equals("failed")) { + res = ApiHandlerHelpers.internalServerErrorResponse(outputBuffer, jsonResult); + } else { + res = ApiHandlerHelpers.successResponse(outputBuffer, jsonResult); + } + res.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + LoggingService.logDebug(MODULE_NAME, "Finished processing request in Provision Api Handler"); + return res; + } catch (Exception e) { + String errorMsg = "Log message parsing error, " + e.getMessage(); + LoggingService.logError(MODULE_NAME, errorMsg, new AgentSystemException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/QueryMessageReceiverHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/QueryMessageReceiverHandler.java new file mode 100644 index 00000000..c1092e5d --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/QueryMessageReceiverHandler.java @@ -0,0 +1,188 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.message_bus.Message; +import org.eclipse.iofog.message_bus.MessageBusUtil; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.*; +import java.io.StringReader; +import java.util.List; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Handler to deliver the messages to the receiver, if found any. Messages are + * delivered for the particular query from the receiver. + * + * @author ashita + * @since 2016 + */ +public class QueryMessageReceiverHandler implements Callable { + private static final String MODULE_NAME = "Local API : QueryMessageReceiverHandler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public QueryMessageReceiverHandler(HttpRequest req, ByteBuf outputBuffer, byte[] content) { + this.req = req; + this.outputBuffer = outputBuffer; + this.content = content; + } + + /** + * Handler method to deliver the messages to the receiver as per the query. + * Get the messages from message bus + * + * @return Object + */ + private FullHttpResponse handleQueryMessageRequest() { + LoggingService.logDebug(MODULE_NAME, "Handle query message request"); + if (!ApiHandlerHelpers.validateMethod(this.req, POST)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", + new AgentSystemException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + final String contentTypeError = ApiHandlerHelpers.validateContentType(this.req, "application/json"); + if (contentTypeError != null) { + LoggingService.logError(MODULE_NAME, contentTypeError, + new AgentSystemException(contentTypeError)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, contentTypeError); + } + + String requestBody = new String(content, UTF_8); + JsonReader reader = Json.createReader(new StringReader(requestBody)); + JsonObject jsonObject = reader.readObject(); + + try { + validateMessageQueryInput(jsonObject); + } catch (Exception e) { + String errorMsg = "Incorrect input content/data " + e.getMessage(); + LoggingService.logError(MODULE_NAME, errorMsg, + new AgentSystemException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + + String receiverId = jsonObject.getString("id"); + long timeframeStart = Long.parseLong(jsonObject.get("timeframestart").toString()); + long timeframeEnd = Long.parseLong(jsonObject.get("timeframeend").toString()); + long actualTimeframeEnd = timeframeEnd; + + JsonArray publishersArray = jsonObject.getJsonArray("publishers"); + + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder builder = factory.createObjectBuilder(); + JsonArrayBuilder messagesArray = factory.createArrayBuilder(); + + MessageBusUtil bus = new MessageBusUtil(); + int msgCount = 0; + + for (int i = 0; i < publishersArray.size(); i++) { + String publisherId = publishersArray.getString(i); + + List messageList = bus.messageQuery(publisherId, receiverId, timeframeStart, timeframeEnd); + + if (messageList != null) { + for (Message msg : messageList) { + JsonObject msgJson = msg.toJson(); + messagesArray.add(msgJson); + msgCount++; + } + + actualTimeframeEnd = messageList.get(messageList.size()-1).getTimestamp(); + } + } + + builder.add("status", "okay"); + builder.add("count", msgCount); + builder.add("timeframestart", timeframeStart); + builder.add("timeframeend", actualTimeframeEnd); + builder.add("messages", messagesArray); + + String result = builder.build().toString(); + LoggingService.logDebug(MODULE_NAME, "Finished handle query message request"); + return ApiHandlerHelpers.successResponse(outputBuffer, result); + } + + /** + * Validate the request and the query for the messages + * + * @param message + * @return String + */ + private void validateMessageQueryInput(JsonObject message) throws Exception{ + LoggingService.logDebug(MODULE_NAME, "Validate Message Query input"); + if (!message.containsKey("id")) { + AgentUserException err = new AgentUserException("Error: Missing input field id"); + LoggingService.logError(MODULE_NAME, err.getMessage(), err); + throw err; + } + + if (!(message.containsKey("timeframestart") && message.containsKey("timeframeend"))) { + AgentUserException err = new AgentUserException("Error: Missing input field timeframe start or end"); + LoggingService.logError(MODULE_NAME, err.getMessage(), err); + throw err; + } + + if (!message.containsKey("publishers")) { + AgentUserException err = new AgentUserException("Error: Missing input field publishers"); + LoggingService.logError(MODULE_NAME, err.getMessage(), err); + throw err; + } + + try { + Long.parseLong(message.get("timeframestart").toString()); + } catch (Exception e) { + AgentUserException err = new AgentUserException("Error: Invalid value of timeframestart"); + LoggingService.logError(MODULE_NAME, err.getMessage(), err); + throw err; + } + + try { + Long.parseLong(message.get("timeframeend").toString()); + } catch (Exception e) { + AgentUserException err = new AgentUserException("Error: Invalid value of timeframeend"); + LoggingService.logError(MODULE_NAME, err.getMessage(), err); + throw err; + } + + if ((message.getString("id").trim().equals(""))) { + AgentUserException err = new AgentUserException("Error: Missing input field value id"); + LoggingService.logError(MODULE_NAME, err.getMessage(), err); + throw err; + } + + } + + /** + * Overriden method of the Callable interface which call the handler method + * + * @return Object + */ + @Override + public FullHttpResponse call() { + return handleQueryMessageRequest(); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/StatusApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/StatusApiHandler.java new file mode 100644 index 00000000..ee9c28bb --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/StatusApiHandler.java @@ -0,0 +1,85 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class StatusApiHandler implements Callable { + private static final String MODULE_NAME = "Status Api Handler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public StatusApiHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Handle status Api Handler call"); + if (!ApiHandlerHelpers.validateMethod(this.req, GET)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", + new AgentSystemException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + if (!ApiHandlerHelpers.validateAccessToken(this.req)) { + String errorMsg = "Incorrect access token"; + outputBuffer.writeBytes(errorMsg.getBytes(UTF_8)); + LoggingService.logError(MODULE_NAME, errorMsg, + new AgentSystemException(errorMsg)); + return ApiHandlerHelpers.unauthorizedResponse(outputBuffer, errorMsg); + } + + try { + String[] status = StatusReporter.getStatusReport().split("\\\\n"); + + ObjectMapper objectMapper = new ObjectMapper(); + Map resultMap = new HashMap<>(); + for (String it : status) { + String[] statusItem = it.split(" : "); + resultMap.put(statusItem[0].trim().toLowerCase().replace(" ", "-"), statusItem[1].trim()); + } + + String jsonResult = objectMapper.writeValueAsString(resultMap); + FullHttpResponse res; + res = ApiHandlerHelpers.successResponse(outputBuffer, jsonResult); + res.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + LoggingService.logDebug(MODULE_NAME, "Finished status Api Handler call"); + return res; + } catch (Exception e) { + String errorMsg = "Log message parsing error"; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentSystemException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/VersionApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/VersionApiHandler.java new file mode 100644 index 00000000..c242606e --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/VersionApiHandler.java @@ -0,0 +1,84 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpRequest; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.utils.CmdProperties; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static java.nio.charset.StandardCharsets.UTF_8; + +public class VersionApiHandler implements Callable { + private static final String MODULE_NAME = "Version Api Handler"; + + private final HttpRequest req; + private final ByteBuf outputBuffer; + private final byte[] content; + + public VersionApiHandler(HttpRequest request, ByteBuf outputBuffer, byte[] content) { + this.req = request; + this.outputBuffer = outputBuffer; + this.content = content; + } + + @Override + public FullHttpResponse call() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Handle Version Api Handler call"); + if (!ApiHandlerHelpers.validateMethod(this.req, GET)) { + LoggingService.logError(MODULE_NAME, "Request method not allowed", + new AgentUserException("Request method not allowed")); + return ApiHandlerHelpers.methodNotAllowedResponse(); + } + + if (!ApiHandlerHelpers.validateAccessToken(this.req)) { + String errorMsg = "Incorrect access token"; + outputBuffer.writeBytes(errorMsg.getBytes(UTF_8)); + LoggingService.logError(MODULE_NAME, errorMsg, + new AgentUserException(errorMsg)); + return ApiHandlerHelpers.unauthorizedResponse(outputBuffer, errorMsg); + } + + try { + String[] info = Configuration.getConfigReport().split("\\\\n"); + + ObjectMapper objectMapper = new ObjectMapper(); + Map resultMap = new HashMap() {{ + put("version", CmdProperties.getVersion()); + }}; + + String jsonResult = objectMapper.writeValueAsString(resultMap); + FullHttpResponse res; + res = ApiHandlerHelpers.successResponse(outputBuffer, jsonResult); + res.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + LoggingService.logDebug(MODULE_NAME, "Finished Version Api Handler call"); + return res; + } catch (Exception e) { + String errorMsg = "Log message parsing error "; + LoggingService.logError(MODULE_NAME, errorMsg, new AgentSystemException(e.getMessage(), e)); + return ApiHandlerHelpers.badRequestResponse(outputBuffer, errorMsg); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebSocketMap.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebSocketMap.java new file mode 100644 index 00000000..3cf4f23e --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebSocketMap.java @@ -0,0 +1,57 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.iofog.utils.logging.LoggingService; + +import io.netty.channel.ChannelHandlerContext; + +/** + * Real-time message and cotrol open websockets map. + * Unacknowledged messages and control signals map. + * @author ashita + * @since 2016 + */ +public final class WebSocketMap { + static final Map controlWebsocketMap = new ConcurrentHashMap<>(); + static final Map messageWebsocketMap = new ConcurrentHashMap<>(); + + static final Map unackMessageSendingMap = new ConcurrentHashMap<>(); + static final Map unackControlSignalsMap = new ConcurrentHashMap<>(); + + + + private WebSocketMap(){ + throw new UnsupportedOperationException(WebSocketMap.class + "could not be instantiated"); + } + + public static void addWebsocket(char ws, String id, ChannelHandlerContext ctx) { + LoggingService.logDebug("WebSocketMap", "Adding web socket"); + synchronized (WebSocketMap.class) { + switch (ws) { + case 'C': + controlWebsocketMap.put(id, ctx); + break; + case 'M': + messageWebsocketMap.put(id, ctx); + } + } + } + + public static Map getMessageWebsocketMap() { + return messageWebsocketMap; + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebsocketUtil.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebsocketUtil.java new file mode 100644 index 00000000..d2a198ad --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebsocketUtil.java @@ -0,0 +1,79 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import java.util.Iterator; +import java.util.Map; + +import org.eclipse.iofog.utils.logging.LoggingService; + +import io.netty.channel.ChannelHandlerContext; + +/** + * Utility class for the real-time message and control websockets + * @author ashita + * @since 2016 + */ +public class WebsocketUtil { + private static final String MODULE_NAME = "Local API"; + + /** + * Remove inactive websocket from the open websocket map + * @param ctx + * @param socketMap + * @return void + */ + public static synchronized void removeWebsocketContextFromMap(ChannelHandlerContext ctx, Map socketMap){ + LoggingService.logDebug(MODULE_NAME, "Removing real-time websocket context for the id "); + for (Iterator> it = socketMap.entrySet().iterator(); it.hasNext();) { + Map.Entry e = it.next(); + if (ctx.equals(e.getValue())) { + it.remove(); + } + } + LoggingService.logDebug(MODULE_NAME, "Finished Removing real-time websocket context for the id "); + } + + /** + * Check if the container has open real-time websocket + * @param ctx + * @param socketMap + * @return boolean + */ + public static boolean hasContextInMap(ChannelHandlerContext ctx, Map socketMap) { + for (ChannelHandlerContext context: socketMap.values()) + if (context.equals(ctx)) { + LoggingService.logDebug(MODULE_NAME, "Container has open real-time websocket : " + true); + return true; + } + LoggingService.logDebug(MODULE_NAME, "Container has open real-time websocket : " + false); + return false; + } + + /** + * Get id for the real-time socket channel + * @param ctx + * @param socketMap + * @return String + */ + public static String getIdForWebsocket(ChannelHandlerContext ctx, Map socketMap){ + LoggingService.logDebug(MODULE_NAME, "Get id for the real-time socket channel"); + String id = ""; + for (Map.Entry e : socketMap.entrySet()) { + if (ctx.equals(e.getValue())) { + return e.getKey(); + } + } + return id; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageReceiverWebSocketClientHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageReceiverWebSocketClientHandler.java new file mode 100644 index 00000000..ef8e9845 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageReceiverWebSocketClientHandler.java @@ -0,0 +1,125 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import org.bouncycastle.util.Arrays; +import org.eclipse.iofog.message_bus.Message; +import org.eclipse.iofog.utils.BytesUtil; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import org.eclipse.iofog.utils.logging.LoggingService; + +public class MessageReceiverWebSocketClientHandler extends SimpleChannelInboundHandler{ + + private static final Byte OPCODE_ACK = 0xB; + private static final Byte OPCODE_MSG = 0xD; + + private final WebSocketClientHandshaker handshaker; + private ChannelPromise handshakeFuture; + + public MessageReceiverWebSocketClientHandler(WebSocketClientHandshaker handshaker) { + this.handshaker = handshaker; + } + + public ChannelFuture handshakeFuture() { + return handshakeFuture; + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + handshakeFuture = ctx.newPromise(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + handshaker.handshake(ctx.channel()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + System.out.println("WebSocket Client disconnected!"); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) { + System.out.println("client channelRead0 "+ctx); + Channel ch = ctx.channel(); + if (!handshaker.isHandshakeComplete()) { + handshaker.finishHandshake(ch, (FullHttpResponse) msg); + System.out.println("WebSocket Client connected!"); + handshakeFuture.setSuccess(); + } + + if(msg instanceof WebSocketFrame){ + WebSocketFrame frame = (WebSocketFrame)msg; + if(frame instanceof BinaryWebSocketFrame){ + handleWebSocketFrame(ctx, frame); + } + } + } + + private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { + System.out.println("In client handleWebSocketFrame....."); + if (frame instanceof BinaryWebSocketFrame) { + System.out.println("In websocket client..... Text WebSocket Frame...Receiving message" ); + ByteBuf input = frame.content(); + if (!input.isReadable()) { + return; + } + + byte[] byteArray = new byte[input.readableBytes()]; + int readerIndex = input.readerIndex(); + input.getBytes(readerIndex, byteArray); + + Byte opcode = byteArray[0]; + System.out.println("Opcode: " + opcode); + if(opcode.intValue() == OPCODE_MSG){ + + int totalMsgLength = BytesUtil.bytesToInteger(Arrays.copyOfRange(byteArray, 1, 5)); + Message message; + try { + message = new Message(Arrays.copyOfRange(byteArray, 5, totalMsgLength)); + System.out.println(message.toString()); + } catch (Exception e) { + LoggingService.logError("Message Socket Handler", "Wrong message format", e); + System.out.println("wrong message format " + e.getMessage()); + } + + ByteBuf buffer1 = ctx.alloc().buffer(); + + buffer1.writeByte(OPCODE_ACK); + System.out.println("Message received.. Send acknowledgment"); + ctx.channel().writeAndFlush(new BinaryWebSocketFrame(buffer1)); + } + } + + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + if (!handshakeFuture.isDone()) { + handshakeFuture.setFailure(cause); + } + ctx.close(); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSenderWebSocketClientHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSenderWebSocketClientHandler.java new file mode 100644 index 00000000..4bd7ee23 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSenderWebSocketClientHandler.java @@ -0,0 +1,213 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import io.netty.util.ReferenceCountUtil; +import org.bouncycastle.util.Arrays; +import org.eclipse.iofog.message_bus.Message; +import org.eclipse.iofog.utils.BytesUtil; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.iofog.utils.logging.LoggingService.logError; + +public class MessageSenderWebSocketClientHandler extends SimpleChannelInboundHandler{ + + private static final String MODULE_NAME = "MessageSenderWebSocketClientHandler"; + + private static int testCounter = 0; + + private static final Byte OPCODE_MSG = 0xD; + private static final Byte OPCODE_RECEIPT = 0xE; + private String publisherId = ""; + + private final WebSocketClientHandshaker handshaker; + private ChannelPromise handshakeFuture; + + public MessageSenderWebSocketClientHandler(WebSocketClientHandshaker handshaker, String id) { + this.handshaker = handshaker; + publisherId = id; + } + + public ChannelFuture handshakeFuture() { + return handshakeFuture; + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + handshakeFuture = ctx.newPromise(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + handshaker.handshake(ctx.channel()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + System.out.println("WebSocket Client disconnected!"); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) { + System.out.println("client channelRead0 "+ctx); + Channel ch = ctx.channel(); + if (!handshaker.isHandshakeComplete()) { + handshaker.finishHandshake(ch, (FullHttpResponse) msg); + System.out.println("WebSocket Client connected!"); + handshakeFuture.setSuccess(); + } + + if(msg instanceof WebSocketFrame){ + WebSocketFrame frame = (WebSocketFrame)msg; + if(frame instanceof BinaryWebSocketFrame){ + handleWebSocketFrame(frame); + } + return; + } + sendRealTimeMessageTest(ctx); + } + + private void handleWebSocketFrame(WebSocketFrame frame) { + System.out.println("In client handleWebSocketFrame....."); + + if (frame instanceof BinaryWebSocketFrame) { + System.out.println("In websocket client..... Text WebSocket Frame...Receiving Receipt" ); + ByteBuf input = frame.content(); + if (!input.isReadable()) { + return; + } + + byte[] byteArray = new byte[input.readableBytes()]; + int readerIndex = input.readerIndex(); + input.getBytes(readerIndex, byteArray); + + + Byte opcode = byteArray[0]; + System.out.println("Opcode: " + opcode); + if(opcode.intValue() == OPCODE_RECEIPT){ + int size = byteArray[1]; + int pos = 3; + if (size > 0) { + String messageId = BytesUtil.bytesToString(Arrays.copyOfRange(byteArray, pos, pos + size)); + System.out.println("Message Id: " + messageId + "\n"); + pos += size; + } + + size = byteArray[2]; + if (size > 0) { + long timeStamp = BytesUtil.bytesToLong(Arrays.copyOfRange(byteArray, pos, pos + size)); + System.out.println("Timestamp: " + timeStamp + "\n"); + } + + } + } + + } + + private void sendRealTimeMessageTest(ChannelHandlerContext ctx){ + ByteBuf buffer1 = Unpooled.buffer(1024); + try { + System.out.println("In clienttest : sendRealTimeMessageTest"); + System.out.println("Test Counter: " + testCounter); + + buffer1.writeByte(OPCODE_MSG); + + //Actual Message + //short version = 4;//version + String id = ""; //id + String tag = "Bosch Camera 8798797"; //tag + String messageGroupId = "group1"; //messageGroupId + Integer seqNum; + synchronized (this) { + testCounter++; + seqNum = testCounter; //sequence number + } + Integer seqTot = 100; //sequence total + Byte priority = 5; //priority + Long timestamp = (long) 0; //timestamp + String publisher = publisherId; //publisher + String authid = "auth"; //authid + String authGroup = "authgrp"; //auth group + Long chainPos = (long) 10; //chain position + String hash = "hashingggg"; //hash + String prevHash = "prevhashingggg"; //previous hash + String nounce = "nounceee"; //nounce + Integer diffTarget = 30;//difficultytarget + String infotype = "image/jpeg"; //infotype + String infoformat = "base64"; //infoformat + String contextData = "gghh"; + String contentData = "sdkjhwrtiy8wrtgSDFOiuhsrgowh4touwsdhsDFDSKJhsdkljasjklweklfjwhefiauhw98p328testcounter"; + + System.out.println("Publisher: " + publisherId + "," + "testcounter: " + testCounter); + + Message m = new Message(); + m.setId(id); + m.setTag(tag); + m.setMessageGroupId(messageGroupId); + m.setSequenceNumber(seqNum); + m.setSequenceTotal(seqTot); + m.setPriority(priority); + m.setTimestamp(timestamp); + m.setPublisher(publisher); + m.setAuthIdentifier(authid); + m.setAuthGroup(authGroup); + m.setChainPosition(chainPos); + m.setHash(hash); + m.setPreviousHash(prevHash); + m.setNonce(nounce); + m.setDifficultyTarget(diffTarget); + m.setInfoType(infotype); + m.setInfoFormat(infoformat); + m.setContextData(contextData.getBytes(UTF_8)); + m.setContentData(contentData.getBytes(UTF_8)); + + //Send Total Length of IOMessage - 4 bytes + try { + byte[] bmsg = m.getBytes(); + int totalMsgLength = bmsg.length; + System.out.println("Total message length: " + totalMsgLength); + buffer1.writeBytes(BytesUtil.integerToBytes(totalMsgLength)); + buffer1.writeBytes(bmsg); + + ctx.channel().writeAndFlush(new BinaryWebSocketFrame(buffer1)); + System.out.println("Send RealTime Message : done"); + } catch (Exception exp) { + logError(MODULE_NAME, exp.getMessage(), exp); + } + } finally { + ReferenceCountUtil.release(buffer1); + } + } + + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + if (!handshakeFuture.isDone()) { + handshakeFuture.setFailure(cause); + } + ctx.close(); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSocketTestMain.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSocketTestMain.java new file mode 100644 index 00000000..302b79de --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSocketTestMain.java @@ -0,0 +1,103 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +public class MessageSocketTestMain { + + public static void main(String[] args) throws Exception { + + /////////////////////////Open socket and receive message////////////////////////// + MessageWebsocketReceiverClient rec1 = new MessageWebsocketReceiverClient("LmMgpZGqJk8H4c42RW4RW4"); + Thread t11 = new Thread(rec1); + t11.start(); + + MessageWebsocketReceiverClient rec2 = new MessageWebsocketReceiverClient("pTn6CCZnLKJxZ6wFwcYCR3"); + Thread t12 = new Thread(rec2); + t12.start(); + + MessageWebsocketReceiverClient rec3 = new MessageWebsocketReceiverClient("bbRxKWjnW2t8yrYPYPwJMq"); + Thread t13 = new Thread(rec3); + t13.start(); + + MessageWebsocketReceiverClient rec4 = new MessageWebsocketReceiverClient("wtzNQQ2rrbBjHkdYG9Z8gx"); + Thread t14 = new Thread(rec4); + t14.start(); + + MessageWebsocketReceiverClient rec5 = new MessageWebsocketReceiverClient("yfmw3QdPgHhNv8Fvy6F8nk"); + Thread t15 = new Thread(rec5); + t15.start(); + + MessageWebsocketReceiverClient rec6 = new MessageWebsocketReceiverClient("tBj8KhfZMLd8ggYt6d8mZC"); + Thread t16 = new Thread(rec6); + t16.start(); + + MessageWebsocketReceiverClient rec7 = new MessageWebsocketReceiverClient("vF4ZJJ862zGjHpzVpmFBvV"); + Thread t17 = new Thread(rec7); + t17.start(); + + MessageWebsocketReceiverClient rec8 = new MessageWebsocketReceiverClient("QrmVDvgD4Jg8HYQP4QpJ84"); + Thread t18 = new Thread(rec8); + t18.start(); + + MessageWebsocketReceiverClient rec9 = new MessageWebsocketReceiverClient("7HYv3C4wxVWQNyk3bpKrxr"); + Thread t19 = new Thread(rec9); + t19.start(); + + MessageWebsocketReceiverClient rec10 = new MessageWebsocketReceiverClient("J7cyYQjzRHKYVNGbjpjdBC"); + Thread t20 = new Thread(rec10); + t20.start(); + + //Open socket and send message//////////////////////////////////// + MessageWebsocketSenderClient sender1 = new MessageWebsocketSenderClient("68jrzddRXc92jHm8kPT7CP"); + Thread t1 = new Thread(sender1); + t1.start(); + + MessageWebsocketSenderClient sender2 = new MessageWebsocketSenderClient("XdhkQpqfdpGHV8tthYWxDV"); + Thread t2 = new Thread(sender2); + t2.start(); + + MessageWebsocketSenderClient sender3 = new MessageWebsocketSenderClient("Yy2fxWL9PBFpVDRwyr2FgT"); + Thread t3 = new Thread(sender3); + t3.start(); + + MessageWebsocketSenderClient sender4 = new MessageWebsocketSenderClient("qkxXLyBZqbtyXmkBjFbNZY"); + Thread t4 = new Thread(sender4); + t4.start(); + + MessageWebsocketSenderClient sender5 = new MessageWebsocketSenderClient("Cv9MTMDYmCxmP2hz979Mv3"); + Thread t5 = new Thread(sender5); + t5.start(); + + MessageWebsocketSenderClient sender6 = new MessageWebsocketSenderClient("PgyYmGKPJ99f6TLGTbPkHM"); + Thread t6 = new Thread(sender6); + t6.start(); + + MessageWebsocketSenderClient sender7 = new MessageWebsocketSenderClient("G2mXGwLFvzV2xgGg6pqdCw"); + Thread t7 = new Thread(sender7); + t7.start(); + + MessageWebsocketSenderClient sender8 = new MessageWebsocketSenderClient("7m9YwYjcKXc6VRBFx3dq3K"); + Thread t8 = new Thread(sender8); + t8.start(); + + MessageWebsocketSenderClient sender9 = new MessageWebsocketSenderClient("GJYK6pXGR3222xYjJJgVM9"); + Thread t9 = new Thread(sender9); + t9.start(); + + MessageWebsocketSenderClient sender10 = new MessageWebsocketSenderClient("QxzkZvBVBy8QRXdhcqFQ7z"); + Thread t10 = new Thread(sender10); + t10.start(); + + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketReceiverClient.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketReceiverClient.java new file mode 100644 index 00000000..32f1e93a --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketReceiverClient.java @@ -0,0 +1,109 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import java.net.URI; +import java.net.URISyntaxException; + +import javax.net.ssl.SSLException; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; +import io.netty.handler.codec.http.websocketx.WebSocketVersion; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import org.eclipse.iofog.utils.logging.LoggingService; + +public class MessageWebsocketReceiverClient implements Runnable{ + + private final String URL; + + public MessageWebsocketReceiverClient(String id) { + URL = System.getProperty("url", "ws://127.0.0.1:54321/v2/message/socket/id/" + id); + } + + public void run(){ + try { + URI uri = new URI(URL); + String scheme = uri.getScheme() == null? "ws" : uri.getScheme(); + final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost(); + final int port; + if (uri.getPort() == -1) { + if ("ws".equalsIgnoreCase(scheme)) { + port = 80; + } else if ("wss".equalsIgnoreCase(scheme)) { + port = 443; + } else { + port = -1; + } + } else { + port = uri.getPort(); + } + + if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) { + System.err.println("Only WS(S) is supported."); + return; + } + + final boolean ssl = "wss".equalsIgnoreCase(scheme); + final SslContext sslCtx; + if (ssl) { + sslCtx = SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); + } else { + sslCtx = null; + } + + EventLoopGroup group = new NioEventLoopGroup(); + final MessageReceiverWebSocketClientHandler handler = + new MessageReceiverWebSocketClientHandler( + WebSocketClientHandshakerFactory.newHandshaker( + uri, WebSocketVersion.V13, null, false, new DefaultHttpHeaders())); + + Bootstrap b = new Bootstrap(); + b.group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline p = ch.pipeline(); + if (sslCtx != null) { + p.addLast(sslCtx.newHandler(ch.alloc(), host, port)); + } + p.addLast( + new HttpClientCodec(), + new HttpObjectAggregator(2147483647), + handler); + } + }); + + Channel ch = b.connect(uri.getHost(), port).sync().channel(); + handler.handshakeFuture().sync(); + } catch (SSLException | InterruptedException | URISyntaxException e) { + LoggingService.logError("Message Socket Receiver", e.getMessage(), e); + e.printStackTrace(); + } + + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketSenderClient.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketSenderClient.java new file mode 100644 index 00000000..8ffcd691 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketSenderClient.java @@ -0,0 +1,111 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import java.net.URI; +import java.net.URISyntaxException; + +import javax.net.ssl.SSLException; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; +import io.netty.handler.codec.http.websocketx.WebSocketVersion; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; +import org.eclipse.iofog.utils.logging.LoggingService; + +public class MessageWebsocketSenderClient implements Runnable{ + + private String id = ""; + private final String URL; + + public MessageWebsocketSenderClient(String cid) { + this.id = cid; + this.URL = System.getProperty("url", "ws://127.0.0.1:54321/v2/message/socket/id/" + id); + } + + public void run(){ + try { + URI uri = new URI(URL); + String scheme = uri.getScheme() == null? "ws" : uri.getScheme(); + final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost(); + final int port; + if (uri.getPort() == -1) { + if ("ws".equalsIgnoreCase(scheme)) { + port = 80; + } else if ("wss".equalsIgnoreCase(scheme)) { + port = 443; + } else { + port = -1; + } + } else { + port = uri.getPort(); + } + + if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) { + System.err.println("Only WS(S) is supported."); + return; + } + + final boolean ssl = "wss".equalsIgnoreCase(scheme); + final SslContext sslCtx; + if (ssl) { + sslCtx = SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); + } else { + sslCtx = null; + } + + EventLoopGroup group = new NioEventLoopGroup(); + final MessageSenderWebSocketClientHandler handler = + new MessageSenderWebSocketClientHandler( + WebSocketClientHandshakerFactory.newHandshaker( + uri, WebSocketVersion.V13, null, false, new DefaultHttpHeaders()), id); + + Bootstrap b = new Bootstrap(); + b.group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline p = ch.pipeline(); + if (sslCtx != null) { + p.addLast(sslCtx.newHandler(ch.alloc(), host, port)); + } + p.addLast( + new HttpClientCodec(), + new HttpObjectAggregator(2147483647), + handler); + } + }); + + Channel ch = b.connect(uri.getHost(), port).sync().channel(); + handler.handshakeFuture().sync(); + } catch (SSLException | InterruptedException | URISyntaxException e) { + LoggingService.logError("Message Socket Receiver", e.getMessage(), e); + e.printStackTrace(); + } + + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestApiDriver.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestApiDriver.java new file mode 100644 index 00000000..f810914a --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestApiDriver.java @@ -0,0 +1,29 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import java.util.Random; + +public class RestApiDriver { + public static void main(String[] args) throws Exception { + Random random = new Random(); + for (int i = 0; i < 20; i++) { + RestPublishTest pub = new RestPublishTest("DTCnTG4dLyrGC7XYrzzTqNhW7R78hk3V", 1500, 50); + pub.start(); + Thread.sleep(random.nextInt(250)); + RestReceiveTest rec = new RestReceiveTest("receiver_" + String.valueOf(i + 1), 50); + rec.start(); + } + System.out.println(); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestPublishTest.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestPublishTest.java new file mode 100644 index 00000000..7e1f0fa9 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestPublishTest.java @@ -0,0 +1,71 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import javax.json.Json; +import javax.json.JsonObject; + +import org.eclipse.iofog.message_bus.Message; + +public class RestPublishTest { + private int count; + private int delay; + private String publisher; + + public RestPublishTest(String publisher, int count, int delay) { + this.count = count; + this.delay = delay; + this.publisher = publisher; + } + + private final Runnable publish = () -> { + byte[] content = Base64.getEncoder().encode("HELLLLOOOOOO!".getBytes()); + Message msg = new Message(); + msg.setPublisher(publisher); + msg.setInfoType("test"); + msg.setInfoFormat("utf-8"); + msg.setContentData(content); + byte[] bytes = msg.toString().getBytes(StandardCharsets.US_ASCII); + + try { + for (int i = 0; i < count; i++) { + HttpURLConnection httpRequest = (HttpURLConnection) new URL("http://127.0.0.1:54321/v2/messages/new").openConnection(); + httpRequest.setRequestMethod("POST"); + httpRequest.setRequestProperty("Content-Type", "application/json"); + httpRequest.setRequestProperty("Content-Length", String.valueOf(bytes.length)); + httpRequest.setDoOutput(true); + httpRequest.getOutputStream().write(bytes); + BufferedReader in = new BufferedReader(new InputStreamReader(httpRequest.getInputStream(), "UTF-8")); + JsonObject result = Json.createReader(in).readObject(); + try { + Thread.sleep(delay); + } catch (Exception e) { + break; + } + } + } catch (Exception e) {} + }; + + public void start() { + Thread t = new Thread(publish, "PUBLSHER"); + t.start(); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestReceiveTest.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestReceiveTest.java new file mode 100644 index 00000000..512d7e03 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestReceiveTest.java @@ -0,0 +1,64 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.StandardCharsets; + +import javax.json.Json; +import javax.json.JsonObject; + +public class RestReceiveTest { + private int delay; + private String receiver; + private int counter; + + public RestReceiveTest(String receiver, int delay) { + this.delay = delay; + this.receiver = receiver; + counter = 0; + } + + private final Runnable receive = () -> { + byte[] bytes = String.format("{\"id\":\"%s\"}", receiver).getBytes(StandardCharsets.US_ASCII); + + try { + while (counter < 1500) { + HttpURLConnection httpRequest = (HttpURLConnection) new URL("http://127.0.0.1:54321/v2/messages/next").openConnection(); + httpRequest.setRequestMethod("POST"); + httpRequest.setRequestProperty("Content-Type", "application/json"); + httpRequest.setRequestProperty("Content-Length", String.valueOf(bytes.length)); + httpRequest.setDoOutput(true); + httpRequest.getOutputStream().write(bytes); + BufferedReader in = new BufferedReader(new InputStreamReader(httpRequest.getInputStream(), "UTF-8")); + JsonObject result = Json.createReader(in).readObject(); + System.out.println(receiver + " : " + counter); + counter++; + try { + Thread.sleep(delay); + } catch (Exception e) { + break; + } + } + } catch (Exception e) {} + }; + + public void start() { + Thread t = new Thread(receive, "RECEIVER"); + t.start(); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientControl.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientControl.java new file mode 100644 index 00000000..cfcadd37 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientControl.java @@ -0,0 +1,97 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import java.net.URI; + +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshakerFactory; +import io.netty.handler.codec.http.websocketx.WebSocketVersion; +import io.netty.handler.ssl.SslContext; +import io.netty.handler.ssl.SslContextBuilder; +import io.netty.handler.ssl.util.InsecureTrustManagerFactory; + +public class WebSocketClientControl { + + private static final String URL = System.getProperty("url", "ws://127.0.0.1:54321/v2/control/socket/id/viewer"); + + public static void main(String[] args) throws Exception { + URI uri = new URI(URL); + String scheme = uri.getScheme() == null? "ws" : uri.getScheme(); + final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost(); + final int port; + if (uri.getPort() == -1) { + if ("ws".equalsIgnoreCase(scheme)) { + port = 80; + } else if ("wss".equalsIgnoreCase(scheme)) { + port = 443; + } else { + port = -1; + } + } else { + port = uri.getPort(); + } + + if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) { + System.err.println("Only WS(S) is supported."); + return; + } + + final boolean ssl = "wss".equalsIgnoreCase(scheme); + final SslContext sslCtx; + if (ssl) { + sslCtx = SslContextBuilder.forClient() + .trustManager(InsecureTrustManagerFactory.INSTANCE).build(); + } else { + sslCtx = null; + } + + EventLoopGroup group = new NioEventLoopGroup(); + final WebSocketClientHandlerControl handler = + new WebSocketClientHandlerControl( + WebSocketClientHandshakerFactory.newHandshaker( + uri, WebSocketVersion.V13, null, false, new DefaultHttpHeaders())); + + Bootstrap b = new Bootstrap(); + b.group(group) + .channel(NioSocketChannel.class) + .handler(new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel ch) { + ChannelPipeline p = ch.pipeline(); + if (sslCtx != null) { + p.addLast(sslCtx.newHandler(ch.alloc(), host, port)); + } + p.addLast( + new HttpClientCodec(), + new HttpObjectAggregator(8192), + handler); + } + }); + + Channel ch = b.connect(uri.getHost(), port).sync().channel(); + handler.handshakeFuture().sync(); + + } +} + diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientHandlerControl.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientHandlerControl.java new file mode 100644 index 00000000..6bf5737e --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientHandlerControl.java @@ -0,0 +1,113 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api.test; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; +import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketClientHandshaker; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.util.ReferenceCountUtil; + +public class WebSocketClientHandlerControl extends SimpleChannelInboundHandler{ + + private static final Byte OPCODE_PING = 0x9; + private static final Byte OPCODE_PONG = 0xA; + private static final Byte OPCODE_ACK = 0xB; + private static final Byte OPCODE_CONTROL_SIGNAL = 0xC; + + private final WebSocketClientHandshaker handshaker; + private ChannelPromise handshakeFuture; + + public WebSocketClientHandlerControl(WebSocketClientHandshaker handshaker) { + this.handshaker = handshaker; + } + + public ChannelFuture handshakeFuture() { + return handshakeFuture; + } + + @Override + public void handlerAdded(ChannelHandlerContext ctx) { + handshakeFuture = ctx.newPromise(); + } + + @Override + public void channelActive(ChannelHandlerContext ctx) { + handshaker.handshake(ctx.channel()); + } + + @Override + public void channelInactive(ChannelHandlerContext ctx) { + System.out.println("WebSocket Client disconnected!"); + } + + @Override + public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { + System.out.println("1"+ctx); + Channel ch = ctx.channel(); + if (!handshaker.isHandshakeComplete()) { + handshaker.finishHandshake(ch, (FullHttpResponse) msg); + System.out.println("WebSocket Client connected!"); + handshakeFuture.setSuccess(); + //ping(ctx); + return; + } + + WebSocketFrame frame = (WebSocketFrame) msg; + if (frame instanceof TextWebSocketFrame) { + ByteBuf buffer = frame.content(); + Byte opcode = buffer.readByte(); + System.out.println("OPCPDE control client: " + opcode); + if(opcode == OPCODE_CONTROL_SIGNAL.intValue()){ + System.out.println("Control signal received...."); + ByteBuf buffer1 = Unpooled.buffer(126); + try { + //buffer1.writeByte(OPCODE_ACK); + System.out.println(ctx); + System.out.println("Acknowledgment send... "); + ch.writeAndFlush(new TextWebSocketFrame(buffer1)); + } finally { + ReferenceCountUtil.release(buffer1); + } + return; + } + } + + if (frame instanceof PongWebSocketFrame) { + ByteBuf buffer = frame.content(); + Byte opcode = buffer.readByte(); + if(opcode == OPCODE_PONG.intValue()){ + System.out.println("Received pong and done...."); + } + } + + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + cause.printStackTrace(); + if (!handshakeFuture.isDone()) { + handshakeFuture.setFailure(cause); + } + ctx.close(); + } +} + diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/IOMessageListener.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/IOMessageListener.java new file mode 100644 index 00000000..dafdd412 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/IOMessageListener.java @@ -0,0 +1,61 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.local_api.MessageCallback; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.jms.MessageListener; +import javax.jms.TextMessage; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; + +import java.io.StringReader; + +/** + * listener for real-time receiving + * + * @author saeid + * + */ +public class IOMessageListener implements MessageListener { + private static final String MODULE_NAME = "MessageListener"; + + private final MessageCallback callback; + + public IOMessageListener(MessageCallback callback) { + this.callback = callback; + } + + @Override + public void onMessage(javax.jms.Message msg) { + LoggingService.logDebug(MODULE_NAME, "Start acknowledging message onMessage"); + try { + TextMessage textMessage = (TextMessage) msg; + textMessage.acknowledge(); + JsonReader jsonReader = Json.createReader(new StringReader(textMessage.getText())); + JsonObject json = jsonReader.readObject(); + jsonReader.close(); + + Message message = new Message(json); + callback.sendRealtimeMessage(message); + } catch (Exception exp) { + LoggingService.logError(MODULE_NAME, "Error acknowledging message", + new AgentSystemException("Error acknowledging message", exp)); + } + + LoggingService.logDebug(MODULE_NAME, "Finish acknowledging message onMessage"); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/Message.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/Message.java new file mode 100644 index 00000000..d2140c80 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/Message.java @@ -0,0 +1,722 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; +import javax.json.Json; +import javax.json.JsonObject; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.utils.BytesUtil; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.iofog.utils.logging.LoggingService.*; + +/** + * represents IOMessage + * + * @author saeid + * + */ +public class Message { + private static final short VERSION = 4; + private static final String MODULE_NAME = "Message"; + + private String id; + private String tag; + private String messageGroupId; + private int sequenceNumber; + private int sequenceTotal; + private byte priority; + private long timestamp; + private String publisher; + private String authIdentifier; + private String authGroup; + private short version; + private long chainPosition; + private String hash; + private String previousHash; + private String nonce; + private int difficultyTarget; + private String infoType; + private String infoFormat; + private byte[] contextData; + private byte[] contentData; + + public Message() { + version = VERSION; + id = null; + tag = null; + messageGroupId = null; + sequenceNumber = 0; + sequenceTotal = 0; + priority = 0; + timestamp = 0; + publisher = null; + authIdentifier = null; + authGroup = null; + chainPosition = 0; + hash = null; + previousHash = null; + nonce = null; + difficultyTarget = 0; + infoType = null; + infoFormat = null; + contentData = null; + contextData = null; + } + + public Message(String publisher) { + super(); + this.publisher = publisher; + } + + public Message(JsonObject json) { + super(); + if (json.containsKey("id")) + setId(json.getString("id")); + if (json.containsKey("tag")) + setTag(json.getString("tag")); + if (json.containsKey("groupid")) + setMessageGroupId(json.getString("groupid")); + if (json.containsKey("sequencenumber")) + setSequenceNumber(json.getInt("sequencenumber")); + if (json.containsKey("sequencetotal")) + setSequenceTotal(json.getInt("sequencetotal")); + if (json.containsKey("priority")) + setPriority((byte) json.getInt("priority")); + if (json.containsKey("timestamp")) + setTimestamp(json.getJsonNumber("timestamp").longValue()); + if (json.containsKey("publisher")) + setPublisher(json.getString("publisher")); + if (json.containsKey("authid")) + setAuthIdentifier(json.getString("authid")); + if (json.containsKey("authgroup")) + setAuthGroup(json.getString("authgroup")); + if (json.containsKey("chainposition")) + setChainPosition(json.getJsonNumber("chainposition").longValue()); + if (json.containsKey("hash")) + setHash(json.getString("hash")); + if (json.containsKey("previoushash")) + setPreviousHash(json.getString("previoushash")); + if (json.containsKey("nonce")) + setNonce(json.getString("nonce")); + if (json.containsKey("difficultytarget")) + setDifficultyTarget(json.getInt("difficultytarget")); + if (json.containsKey("infotype")) + setInfoType(json.getString("infotype")); + if (json.containsKey("infoformat")) + setInfoFormat(json.getString("infoformat")); + if (json.containsKey("contextdata")){ + String contextData = json.getString("contextdata"); + setContextData(Base64.getDecoder().decode(contextData.getBytes(UTF_8))); + } + if (json.containsKey("contentdata")){ + String contentData = json.getString("contentdata"); + setContentData(Base64.getDecoder().decode(contentData.getBytes(UTF_8))); + } + } + + public Message(byte[] rawBytes) { + super(); + + version = BytesUtil.bytesToShort(BytesUtil.copyOfRange(rawBytes, 0, 2)); + if (version != VERSION) { + // TODO: incompatible version + return; + } + + int pos = 33; + + int size = rawBytes[2]; + if (size > 0) { + id = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(rawBytes, 3, 5)); + if (size > 0) { + tag = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[5]; + if (size > 0) { + messageGroupId = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[6]; + if (size > 0) { + sequenceNumber = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[7]; + if (size > 0) { + sequenceTotal = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[8]; + if (size > 0) { + priority = rawBytes[pos]; + pos += size; + } + + size = rawBytes[9]; + if (size > 0) { + timestamp = BytesUtil.bytesToLong(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[10]; + if (size > 0) { + publisher = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(rawBytes, 11, 13)); + if (size > 0) { + authIdentifier = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(rawBytes, 13, 15)); + if (size > 0) { + authGroup = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[15]; + if (size > 0) { + chainPosition = BytesUtil.bytesToLong(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(rawBytes, 16, 18)); + if (size > 0) { + hash = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(rawBytes, 18, 20)); + if (size > 0) { + previousHash = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(rawBytes, 20, 22)); + if (size > 0) { + nonce = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[22]; + if (size > 0) { + difficultyTarget = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[23]; + if (size > 0) { + infoType = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = rawBytes[24]; + if (size > 0) { + infoFormat = BytesUtil.bytesToString(BytesUtil.copyOfRange(rawBytes, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(rawBytes, 25, 29)); + if (size > 0) { + contextData = BytesUtil.copyOfRange(rawBytes, pos, pos + size); + pos += size; + } + + size = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(rawBytes, 29, 33)); + if (size > 0) { + contentData = BytesUtil.copyOfRange(rawBytes, pos, pos + size); + } + } + + public Message(byte[] header, byte[] data) { + super(); + + version = BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 0, 2)); + if (version != VERSION) { + // TODO: incompatible version + return; + } + + int pos = 0; + + int size = header[2]; + if (size > 0) { + id = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 3, 5)); + if (size > 0) { + tag = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[5]; + if (size > 0) { + messageGroupId = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[6]; + if (size > 0) { + sequenceNumber = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[7]; + if (size > 0) { + sequenceTotal = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[8]; + if (size > 0) { + priority = data[pos]; + pos += size; + } + + size = header[9]; + if (size > 0) { + timestamp = BytesUtil.bytesToLong(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[10]; + if (size > 0) { + publisher = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 11, 13)); + if (size > 0) { + authIdentifier = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 13, 15)); + if (size > 0) { + authGroup = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[15]; + if (size > 0) { + chainPosition = BytesUtil.bytesToLong(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 16, 18)); + if (size > 0) { + hash = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 18, 20)); + if (size > 0) { + previousHash = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 20, 22)); + if (size > 0) { + nonce = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[22]; + if (size > 0) { + difficultyTarget = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[23]; + if (size > 0) { + infoType = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = header[24]; + if (size > 0) { + infoFormat = BytesUtil.bytesToString(BytesUtil.copyOfRange(data, pos, pos + size)); + pos += size; + } + + size = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(header, 25, 29)); + if (size > 0) { + contextData = BytesUtil.copyOfRange(data, pos, pos + size); + pos += size; + } + + size = BytesUtil.bytesToInteger(BytesUtil.copyOfRange(header, 29, 33)); + if (size > 0) { + contentData = BytesUtil.copyOfRange(data, pos, pos + size); + } + } + + public String getId() { + return id; + } + public void setId(String id) { + this.id = id; + } + public String getTag() { + return tag; + } + public void setTag(String tag) { + this.tag = tag; + } + public String getMessageGroupId() { + return messageGroupId; + } + public void setMessageGroupId(String messageGroupId) { + this.messageGroupId = messageGroupId; + } + public int getSequenceNumber() { + return sequenceNumber; + } + public void setSequenceNumber(int sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + public int getSequenceTotal() { + return sequenceTotal; + } + public void setSequenceTotal(int sequenceTotal) { + this.sequenceTotal = sequenceTotal; + } + public byte getPriority() { + return priority; + } + public void setPriority(byte priority) { + this.priority = priority; + } + public long getTimestamp() { + return timestamp; + } + public void setTimestamp(long timestamp) { + this.timestamp = timestamp; + } + public String getPublisher() { + return publisher; + } + public void setPublisher(String publisher) { + this.publisher = publisher; + } + public String getAuthIdentifier() { + return authIdentifier; + } + public void setAuthIdentifier(String authIdentifier) { + this.authIdentifier = authIdentifier; + } + public String getAuthGroup() { + return authGroup; + } + public void setAuthGroup(String authGroup) { + this.authGroup = authGroup; + } + public short getVersion() { + return version; + } + public long getChainPosition() { + return chainPosition; + } + public void setChainPosition(long chainPosition) { + this.chainPosition = chainPosition; + } + public String getHash() { + return hash; + } + public void setHash(String hash) { + this.hash = hash; + } + public String getPreviousHash() { + return previousHash; + } + public void setPreviousHash(String previousHash) { + this.previousHash = previousHash; + } + public String getNonce() { + return nonce; + } + public void setNonce(String nonce) { + this.nonce = nonce; + } + public int getDifficultyTarget() { + return difficultyTarget; + } + public void setDifficultyTarget(int difficultyTarget) { + this.difficultyTarget = difficultyTarget; + } + public String getInfoType() { + return infoType; + } + public void setInfoType(String infoType) { + this.infoType = infoType; + } + public String getInfoFormat() { + return infoFormat; + } + public void setInfoFormat(String infoFormat) { + this.infoFormat = infoFormat; + } + public byte[] getContextData() { + return contextData; + } + public void setContextData(byte[] contextData) { + this.contextData = contextData; + } + public byte[] getContentData() { + return contentData; + } + public void setContentData(byte[] contentData) { + this.contentData = contentData; + } + + private int getLength(String str) { + if (str == null) + return 0; + else + return str.length(); + } + + public byte[] getBytes() { + try (ByteArrayOutputStream headerBaos = new ByteArrayOutputStream(); + ByteArrayOutputStream dataBaos = new ByteArrayOutputStream()){ + //version + headerBaos.write(BytesUtil.shortToBytes(VERSION)); + + // id + int len = getLength(getId()); + headerBaos.write((byte) (len & 0xff)); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getId())); + + // tag + len = getLength(getTag()); + headerBaos.write(BytesUtil.shortToBytes((short) (len & 0xffff))); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getTag())); + + //groupid + len = getLength(getMessageGroupId()); + headerBaos.write((byte) (len & 0xff)); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getMessageGroupId())); + + // seq no + if (getSequenceNumber() == 0) + headerBaos.write(0); + else { + dataBaos.write(BytesUtil.integerToBytes(getSequenceNumber())); + headerBaos.write(4); + } + + // seq total + if (getSequenceTotal() == 0) + headerBaos.write(0); + else { + dataBaos.write(BytesUtil.integerToBytes(getSequenceTotal())); + headerBaos.write(4); + } + + + // priority + if (getPriority() == 0) + headerBaos.write(0); + else { + headerBaos.write(1); + dataBaos.write(getPriority()); + } + + //timestamp + if (getTimestamp() == 0) + headerBaos.write(0); + else { + headerBaos.write(8); + dataBaos.write(BytesUtil.longToBytes(getTimestamp())); + } + + // publisher + len = getLength(getPublisher()); + headerBaos.write((byte) (len & 0xff)); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getPublisher())); + + // authIdentifier + len = getLength(getAuthIdentifier()); + headerBaos.write(BytesUtil.shortToBytes((short) (len & 0xffff))); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getAuthIdentifier())); + + // authGroup + len = getLength(getAuthGroup()); + headerBaos.write(BytesUtil.shortToBytes((short) (len & 0xffff))); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getAuthGroup())); + + // chainPosition + if (getChainPosition() == 0) + headerBaos.write(0); + else { + headerBaos.write(8); + dataBaos.write(BytesUtil.longToBytes(getChainPosition())); + } + + // hash + len = getLength(getHash()); + headerBaos.write(BytesUtil.shortToBytes((short) (len & 0xffff))); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getHash())); + + // previousHash + len = getLength(getPreviousHash()); + headerBaos.write(BytesUtil.shortToBytes((short) (len & 0xffff))); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getPreviousHash())); + + // nonce + len = getLength(getNonce()); + headerBaos.write(BytesUtil.shortToBytes((short) (len & 0xffff))); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getNonce())); + + // difficultyTarget + if (getDifficultyTarget() == 0) + headerBaos.write(0); + else { + headerBaos.write(4); + dataBaos.write(BytesUtil.integerToBytes(getDifficultyTarget())); + } + + // infoType + len = getLength(getInfoType()); + headerBaos.write((byte) (len & 0xff)); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getInfoType())); + + // infoFormat + len = getLength(getInfoFormat()); + headerBaos.write((byte) (len & 0xff)); + if (len > 0) + dataBaos.write(BytesUtil.stringToBytes(getInfoFormat())); + + // contextData + if (getContextData() == null) + headerBaos.write(BytesUtil.integerToBytes(0)); + else { + headerBaos.write(BytesUtil.integerToBytes(getContextData().length)); + dataBaos.write(getContextData()); + } + + // contentData + if (getContentData() == null) + headerBaos.write(BytesUtil.integerToBytes(0)); + else { + headerBaos.write(BytesUtil.integerToBytes(getContentData().length)); + dataBaos.write(getContentData()); + } + + ByteArrayOutputStream result = new ByteArrayOutputStream(); + headerBaos.writeTo(result); + dataBaos.writeTo(result); + return result.toByteArray(); + } catch (IOException exc) { + logError(MODULE_NAME, "Error in getBytes", new AgentSystemException(exc.getMessage(), exc)); + } + + return new byte[] {}; + } + + @Override + public String toString() { + return toJson().toString(); + } + + public void decodeBase64(byte[] bytes) { + Message result; + try { + result = new Message(Base64.getDecoder().decode(bytes)); + id = result.id; + tag = result.tag; + messageGroupId = result.messageGroupId; + sequenceNumber = result.sequenceNumber; + sequenceTotal = result.sequenceTotal; + priority = result.priority; + timestamp = result.timestamp; + publisher = result.publisher; + authIdentifier = result.authIdentifier; + authGroup = result.authGroup; + version = result.version; + chainPosition = result.chainPosition; + hash = result.hash; + previousHash = result.previousHash; + nonce = result.nonce; + difficultyTarget = result.difficultyTarget; + infoType = result.infoType; + infoFormat = result.infoFormat; + contextData = result.contextData; + contentData = result.contentData; + } catch (Exception exp) { + logError(MODULE_NAME, "Error in decodeBase64", new AgentSystemException("Error in decodeBase64", exp)); + } + } + + public JsonObject toJson() { + return Json.createObjectBuilder() + .add("id", id == null ? "" : id) + .add("tag", tag == null ? "" : tag) + .add("groupid", messageGroupId == null ? "" : messageGroupId) + .add("sequencenumber", sequenceNumber) + .add("sequencetotal", sequenceTotal) + .add("priority", priority) + .add("timestamp", timestamp) + .add("publisher", publisher == null ? "" : publisher) + .add("authid", authIdentifier == null ? "" : authIdentifier) + .add("authgroup", authGroup == null ? "" : authGroup) + .add("version", version) + .add("chainposition", chainPosition) + .add("hash", hash == null ? "" : hash) + .add("previoushash", previousHash == null ? "" : previousHash) + .add("nonce", nonce == null ? "" : nonce) + .add("difficultytarget", difficultyTarget) + .add("infotype", infoType == null ? "" : infoType) + .add("infoformat", infoFormat == null ? "" : infoFormat) + .add("contextdata", contextData == null ? "" : new String(Base64.getEncoder().encode(contextData))) + .add("contentdata", contentData == null ? "" : new String(Base64.getEncoder().encode(contentData))) + .build(); + } + + public byte[] encodeBase64() { + try { + return Base64.getEncoder().encode(this.getBytes()); + } catch (Exception exp) { + logError(MODULE_NAME, "Error in encodeBase64", new AgentSystemException(exp.getMessage(), exp)); + return new byte[] {}; + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageArchive.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageArchive.java new file mode 100644 index 00000000..e26f89c7 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageArchive.java @@ -0,0 +1,262 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import java.io.File; +import java.io.FilenameFilter; +import java.io.RandomAccessFile; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Stack; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.utils.BytesUtil; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +/** + * archives received {@link Message} from {@link Microservice} + * + * @author saeid + * + */ +public class MessageArchive implements AutoCloseable{ + private static final String MODULE_NAME = "MessageArchive"; + + private static final byte HEADER_SIZE = 33; + private static final short MAXIMUM_MESSAGE_PER_FILE = 1000; + private static final int MAXIMUM_ARCHIVE_SIZE_MB = 1; + + private final String name; + private String diskDirectory; + private String currentFileName; + private RandomAccessFile indexFile; + private RandomAccessFile dataFile; + + public MessageArchive(String name) { + this.name = name; + init(); + } + + /** + * sets the file name for {@link Message} to be archived + * + */ + private void init() { + currentFileName = ""; + diskDirectory = Configuration.getDiskDirectory() + "messages/archive/"; + + File lastFile = null; + long lastFileTimestamp = 0; + final File workingDirectory = new File(diskDirectory); + if (!workingDirectory.exists()) + workingDirectory.mkdirs(); + + FilenameFilter filter = (dir, fileName) -> fileName.substring(0, name.length()).equals(name) + && fileName.substring(fileName.indexOf(".")).equals(".idx"); + + for (File file : workingDirectory.listFiles(filter)) { + if (!file.isFile()) + continue; + String filename = file.getName(); + if (filename.substring(0, name.length()).equals(name)) { + String timestampStr = filename.substring(name.length() + 1, filename.indexOf(".")); + long timestamp = Long.parseLong(timestampStr); + if (timestamp > lastFileTimestamp) { + lastFileTimestamp = timestamp; + lastFile = file; + } + } + } + + if (lastFileTimestamp > 0 && lastFile.length() < ((HEADER_SIZE + Long.BYTES) * MAXIMUM_MESSAGE_PER_FILE)) + currentFileName = lastFile.getPath(); + } + + /** + * opens index and data file + * + * @param timestamp- timestamp of first {@link Message} in the file + * @throws Exception + */ + private void openFiles(long timestamp) throws Exception { + if (currentFileName.equals("")) + currentFileName = diskDirectory + name + "_" + timestamp + ".idx"; + indexFile = new RandomAccessFile(new File(currentFileName), "rw"); + dataFile = new RandomAccessFile(new File(currentFileName.substring(0, currentFileName.indexOf(".")) + ".iomsg"), "rw"); + } + + /** + * archives {@link Message} to file. If size of the data file becomes more than + * defined value, creates a new file + * + * @param message - {@link Message} to be archived + * @param timestamp - timestamp of the {@link Message} + * @throws Exception + */ + void save(byte[] message, long timestamp) throws Exception { + if (indexFile == null) + openFiles(timestamp); + + if ((message.length + dataFile.length()) >= (MAXIMUM_ARCHIVE_SIZE_MB * 1_000_000)) { + close(); + openFiles(timestamp); + } + indexFile.seek(indexFile.length()); + dataFile.seek(dataFile.length()); + long dataPos = dataFile.getFilePointer(); + try { + indexFile.write(message, 0, HEADER_SIZE); + indexFile.writeLong(dataPos); + dataFile.write(message, HEADER_SIZE, message.length - HEADER_SIZE); + } catch(Exception e) { + LoggingService.logError(MODULE_NAME, "Error saving archive", + new AgentSystemException(e.getMessage(), e)); + } + } + + /** + * closes index and data files + * + */ + public void close() { + try { + currentFileName = ""; + if (indexFile != null){ + indexFile.close(); + indexFile = null; + } + if (dataFile != null){ + dataFile.close(); + dataFile = null; + } + currentFileName = ""; + } catch (Exception exp) { + LoggingService.logError(MODULE_NAME, exp.getMessage(), exp); + } + } + + /** + * computes {@link Message} size + * + * @param header - header of the {@link Message} + * @return int + */ + private int getDataSize(byte[] header) { + int size; + size = header[2]; + size += BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 3, 5)); + size += header[5]; + size += header[6]; + size += header[7]; + size += header[8]; + size += header[9]; + size += header[10]; + size += BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 11, 13)); + size += BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 13, 15)); + size += header[15]; + size += BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 16, 18)); + size += BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 18, 20)); + size += BytesUtil.bytesToShort(BytesUtil.copyOfRange(header, 20, 22)); + size += header[22]; + size += header[23]; + size += header[24]; + size += BytesUtil.bytesToInteger(BytesUtil.copyOfRange(header, 25, 29)); + size += BytesUtil.bytesToInteger(BytesUtil.copyOfRange(header, 29, 33)); + return size; + } + + /** + * + * + * @return + */ + private long freeMemory() { + Runtime runtime = Runtime.getRuntime(); + return runtime.maxMemory() - ((runtime.totalMemory() - runtime.freeMemory())); + } + + /** + * retrieves list of {@link Message} sent by this {@link Microservice} within the time frame + * + * @param from - beginning of time frame in milliseconds + * @param to - end of time frame in milliseconds + * @return list of {@link Message} + */ + public List messageQuery(long from, long to) { + LoggingService.logDebug(MODULE_NAME, "Start message query"); + boolean outOfMemory = false; + List result = new ArrayList<>(); + + File workingDirectory = new File(diskDirectory); + FilenameFilter filter = (dir, fileName) -> fileName.substring(0, name.length()).equals(name) + && fileName.substring(fileName.indexOf(".")).equals(".idx"); + File[] listOfFiles = workingDirectory.listFiles(filter); + Stack resultSet = new Stack<>(); + if (listOfFiles != null) { + Arrays.sort(listOfFiles); + + int i = listOfFiles.length - 1; + for (; i >= 0; i--) { + File file = listOfFiles[i]; + if (!file.isFile()) + continue; + long timestamp = Long.parseLong(file.getName().substring(name.length() + 1, file.getName().indexOf("."))); + if (timestamp < from) + break; + if (timestamp >= from && timestamp <= to) + resultSet.push(file); + } + if (i >= 0) + resultSet.push(listOfFiles[i]); + } + + byte[] header = new byte[HEADER_SIZE]; + while (!resultSet.isEmpty() && !outOfMemory) { + File file = resultSet.pop(); + String fileName = file.getName(); + String dataFileName = diskDirectory + fileName.substring(0, fileName.indexOf(".")) + ".iomsg"; + try (RandomAccessFile indexFile = new RandomAccessFile(new File(diskDirectory + fileName), "r"); + RandomAccessFile dataFile = new RandomAccessFile(new File(dataFileName), "r")){ + long dataFileLength = dataFile.length(); + while (indexFile.getFilePointer() < indexFile.length()) { + if (freeMemory() < 32 * Constants.MiB) { + outOfMemory = true; + break; + } + + indexFile.read(header, 0, HEADER_SIZE); + if (((header[0] * 256) + header[1]) != 4) + throw new Exception("invalid index file format"); + long dataPos = indexFile.readLong(); + int dataSize = getDataSize(header); + if (dataPos + dataSize > dataFileLength || dataSize > dataFileLength) + throw new Exception("invalid data file format"); + byte[] data = new byte[dataSize]; + dataFile.read(data, 0, dataSize); + Message message = new Message(header, data); + if (message.getTimestamp() < from || message.getTimestamp() > to) + continue; + result.add(message); + } + } catch (Exception e) { + LoggingService.logError("Message Archive", e.getMessage(), e); + } + } + LoggingService.logDebug(MODULE_NAME, "Finish message query"); + return result; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBus.java new file mode 100644 index 00000000..ff1c0652 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBus.java @@ -0,0 +1,469 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import io.netty.channel.ChannelHandlerContext; +import org.apache.qpid.jms.JmsConnectionRemotelyClosedException; +import org.apache.qpid.jms.exceptions.JmsConnectionClosedException; +import org.apache.qpid.jms.exceptions.JmsConnectionFailedException; +import org.eclipse.iofog.IOFogModule; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.field_agent.enums.RequestType; +import org.eclipse.iofog.local_api.WebSocketMap; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.microservice.Route; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.Orchestrator; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.jms.ExceptionListener; +import javax.jms.JMSException; +import javax.jms.MessageConsumer; +import javax.json.JsonObject; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantLock; + +import static org.eclipse.iofog.utils.Constants.MESSAGE_BUS; +import static org.eclipse.iofog.utils.Constants.ModulesStatus.STOPPED; + +/** + * Message Bus module + * + * @author saeid + * + */ +public class MessageBus implements IOFogModule { + + final static String MODULE_NAME = "Message Bus"; + + private MessageBusServer messageBusServer; + private Map routes; + private Map publishers = new ConcurrentHashMap<>(); + private Map receivers = new ConcurrentHashMap<>(); + private MessageIdGenerator idGenerator = new MessageIdGenerator();; + private static MessageBus instance; + private MicroserviceManager microserviceManager; + private final Object updateLock = new Object(); + private String routerHost; + private int routerPort; + private ReentrantLock messageBusLock = new ReentrantLock(); + + private long lastSpeedTime, lastSpeedMessageCount; + + private MessageBus() {} + + @Override + public int getModuleIndex() { + return MESSAGE_BUS; + } + + @Override + public String getModuleName() { + return MODULE_NAME; + } + + public static MessageBus getInstance() { + if (instance == null) { + synchronized (MessageBus.class) { + if (instance == null) { + instance = new MessageBus(); + } + } + } + return instance; + } + + + /** + * enables real-time {@link Message} receiving of an {@link Microservice} + * + * @param receiver - ID of {@link Microservice} + */ + public synchronized void enableRealTimeReceiving(String receiver) { + logDebug("Starting enable real time receiving"); + MessageReceiver rec = receiver != null ? receivers.get(receiver) : null; + if (rec == null) + return; + rec.enableRealTimeReceiving(); + logDebug("Finishing enable real time receiving"); + } + + /** + * disables real-time {@link Message} receiving of an {@link Microservice} + * + * @param receiver - ID of {@link Microservice} + */ + public synchronized void disableRealTimeReceiving(String receiver) { + logDebug("Starting disable real time receiving"); + MessageReceiver rec = receiver != null ? receivers.get(receiver) : null; + if (rec == null) + return; + rec.disableRealTimeReceiving(); + logDebug("Finishing disable real time receiving"); + } + + /** + * initialize list of {@link Message} publishers and receivers + * + */ + private void init() throws Exception { + logInfo("Starting initialization of message bus publisher and receiver"); + lastSpeedMessageCount = 0; + lastSpeedTime = System.currentTimeMillis(); + + updatePublishersAndReceivers(); + + messageBusServer.setExceptionListener(new MessageBusExceptionListener(messageBusServer, publishers, receivers, startServer)); + + logInfo("Finished initialization of message bus publisher and receiver"); + } + + /** + * calculates the average speed of {@link Message} moving through ioFog + * + */ + private final Runnable calculateSpeed = () -> { + while (true) { + try { + Thread.sleep(Configuration.getSpeedCalculationFreqMinutes() * 60 * 1000); + + logDebug("Start calculating message processing speed"); + + long now = System.currentTimeMillis(); + long msgs = StatusReporter.getMessageBusStatus().getProcessedMessages(); + + float speed = ((float)(msgs - lastSpeedMessageCount)) / ((now - lastSpeedTime) / 1000f); + StatusReporter.setMessageBusStatus().setAverageSpeed(speed); + lastSpeedMessageCount = msgs; + lastSpeedTime = now; + } catch (Exception exp) { + logError(MODULE_NAME, + new AgentSystemException("unable to calculate message processing speed", exp)); + } + logDebug("Finished calculating message processing speed"); + } + }; + + private void updatePublishersAndReceivers() throws Exception { + Map newRoutes = microserviceManager.getRoutes(); + List newPublishers = new ArrayList<>(); + List newReceivers = new ArrayList<>(); + + for (Map.Entry entry: newRoutes.entrySet()) { + if (entry.getValue() == null || entry.getValue().getReceivers() == null) { + continue; + } + + newPublishers.add(entry.getKey()); + for (String receiver: entry.getValue().getReceivers()) { + newReceivers.add(receiver); + } + } + + Set keys = publishers.keySet(); + for (String key: keys) { + if (!newPublishers.contains(key)) { + publishers.get(key).close(); + messageBusServer.removeProducer(key); + publishers.remove(key); + } else { + MessagePublisher publisher = publishers.get(key); + Route route = newRoutes.get(key); + Route currentRoute = publisher.getRoute(); + if (!currentRoute.equals(route)) { + messageBusServer.removeProducer(key); + publisher.updateRoute(route, messageBusServer.getProducer(key, route.getReceivers())); + } + } + } + + for (String newPublisher: newPublishers) { + if (publishers.containsKey(newPublisher)) { + continue; + } + Route route = newRoutes.get(newPublisher); + MessagePublisher messagePublisher = new MessagePublisher(newPublisher, route, messageBusServer.getProducer(newPublisher, route.getReceivers())); + publishers.put(newPublisher, messagePublisher); + } + + Set recs = receivers.keySet(); + for (String rec: recs) { + if (newReceivers.contains(rec)) { + continue; + } + receivers.get(rec).close(); + messageBusServer.removeConsumer(rec); + publishers.remove(rec); + } + + for (String newReceiver: newReceivers) { + if (receivers.containsKey(newReceiver)) { + continue; + } + MessageReceiver messageReceiver = new MessageReceiver(newReceiver, messageBusServer.getConsumer(newReceiver)); + receivers.put(newReceiver, messageReceiver); + } + + routes = newRoutes; + + List latestMicroservices = microserviceManager.getLatestMicroservices(); + Map publishedMessagesPerMicroservice = StatusReporter.getMessageBusStatus().getPublishedMessagesPerMicroservice(); + publishedMessagesPerMicroservice.keySet().removeIf(key -> !microserviceManager.microserviceExists(latestMicroservices, key)); + + for (Microservice microservice: latestMicroservices) { + if (!publishedMessagesPerMicroservice.keySet().contains(microservice.getMicroserviceUuid())) { + publishedMessagesPerMicroservice.put(microservice.getMicroserviceUuid(), 0L); + } + + if (!microservice.isConsumer()) { + continue; + } + + String id = microservice.getMicroserviceUuid(); + MessageConsumer consumer = messageBusServer.getConsumer(id); + if (consumer != null) { + MessageReceiver messageReceiver = new MessageReceiver(id, consumer); + receivers.put(id, messageReceiver); + + Map messageSocketMap = WebSocketMap.getMessageWebsocketMap(); + if (messageSocketMap.containsKey(id)) { + messageReceiver.enableRealTimeReceiving(); + } + } else { + throw new Exception("Unable to create consumer " + id); + } + } + } + + /** + * updates routing, list of publishers and receivers + * Field Agent calls this method when any changes applied + * + */ + public void update() throws Exception { + logDebug("Start update routes, list of publishers and receivers"); + synchronized (updateLock) { + if (!messageBusServer.isConnected()) { + messageBusServer.setConnected(false); + new Thread(startServer).start(); + throw new JMSException("Not connected to router"); + } + + String tempRouterHost = routerHost; + int tempRouterPort = routerPort; + getRouterAddress(); + if (!tempRouterHost.equals(routerHost) || tempRouterPort != routerPort) { + try { + stop(); + } catch (Exception ex) { + logError(MODULE_NAME, new AgentSystemException("unable to update router info", ex)); + } finally { + messageBusServer.setConnected(false); + new Thread(startServer).start(); + throw new JMSException("Not connected to router"); + } + } + + updatePublishersAndReceivers(); + } + logDebug("Finished update routes, list of publishers and receivers"); + } + + /** + * sets memory usage limit of ActiveMQ + * {@link Configuration} calls this method when any changes applied + * + */ + public void instanceConfigUpdated() { + // TODO: Set router address if changed + } + + private void getRouterAddress() throws Exception { + Orchestrator orchestrator = new Orchestrator(); + JsonObject configs = orchestrator.request("config", RequestType.GET, null, null); + routerHost = configs.getString("routerHost"); + routerPort = configs.getJsonNumber("routerPort").intValue(); + } + + public void startServer() throws Exception { + logInfo("STARTING MESSAGE BUS SERVER"); + + getRouterAddress(); + + messageBusServer.startServer(routerHost, routerPort); + messageBusServer.initialize(); + + logInfo("MESSAGE BUS SERVER STARTED"); + Thread speedCalc = new Thread(calculateSpeed, Constants.MESSAGE_BUS_CALCULATE_SPEED); + speedCalc.start(); + } + + private Runnable startServer = new Runnable() { + @Override + public void run() { + messageBusLock.lock(); + + if (messageBusServer.isConnected()) { + messageBusLock.unlock(); + return; + } + + try { + startServer(); + init(); + + messageBusServer.setConnected(true); + } catch (Exception e) { + messageBusServer.setConnected(false); + try { + Thread.sleep(2000); + stop(); + } catch (Exception exp) { + } + logWarning("Error starting message bus module: " + + new AgentSystemException(e.getMessage(), e)); + StatusReporter.setSupervisorStatus().setModuleStatus(MESSAGE_BUS, STOPPED); + new Thread(startServer).start(); + } finally { + messageBusLock.unlock(); + } + } + }; + + /** + * starts Message Bus module + * + */ + public void start() { + microserviceManager = MicroserviceManager.getInstance(); + + messageBusServer = new MessageBusServer(); + + new Thread(startServer).start(); + } + + /** + * closes receivers and publishers and stops ActiveMQ server + * + */ + public void stop() { + logInfo("Start closing receivers and publishers and stops ActiveMQ server"); + + if (receivers != null) { + for (MessageReceiver receiver : receivers.values()) { + try { + receiver.close(); + } catch (Exception e) { + logError("Error closing receiver " + receiver.getName(), new AgentSystemException(e.getMessage(), e)); + } + } + receivers.clear(); + } + + if (publishers != null) { + for (MessagePublisher publisher : publishers.values()) { + try { + publisher.close(); + } catch (Exception e) { + logError("Error closing publisher " + publisher.getName(), new AgentSystemException(e.getMessage(), e)); + } + } + publishers.clear(); + } + + try { + messageBusServer.stopServer(); + } catch (Exception exp) { + logError("Error closing receivers and publishers and stops ActiveMQ server", new AgentSystemException(exp.getMessage(), exp)); + } + logInfo("Finished closing receivers and publishers and stops ActiveMQ server"); + } + + /** + * returns {@link MessagePublisher} + * + * @param publisher - ID of {@link Microservice} + * @return + */ + public MessagePublisher getPublisher(String publisher) { + return publishers.get(publisher); + } + + /** + * returns {@link MessageReceiver} + * + * @param receiver - ID of {@link Microservice} + * @return + */ + public MessageReceiver getReceiver(String receiver) { + return receivers.get(receiver); + } + + /** + * returns next generated message id + * + * @return + */ + public synchronized String getNextId() { + return idGenerator.getNextId(); + } + + /** + * returns routes + * + * @return + */ + public synchronized Map getRoutes() { + return microserviceManager.getRoutes(); + } + + public static class MessageBusExceptionListener implements ExceptionListener { + private MessageBusServer messageBusServer; + private Map publishers; + private Map receivers; + private Runnable startServer; + + public MessageBusExceptionListener(MessageBusServer messageBusServer, Map publishers, Map receivers, Runnable startServer) { + this.messageBusServer = messageBusServer; + this.publishers = publishers; + this.receivers = receivers; + this.startServer = startServer; + } + + @Override + public void onException(JMSException exception) { + if (exception instanceof JmsConnectionClosedException + || exception instanceof JmsConnectionFailedException + || exception instanceof JmsConnectionRemotelyClosedException) { + LoggingService.logError("Message Bus", "Server is not active. restarting...", exception); + + try { + messageBusServer.setConnected(false); + Thread.sleep(2000); + publishers.forEach((key, publisher) -> publisher.close()); + receivers.forEach((key, receiver) -> receiver.close()); + messageBusServer.stopServer(); + } catch (Exception e) {} + + new Thread(startServer).start(); + } + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusServer.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusServer.java new file mode 100644 index 00000000..8aa178ab --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusServer.java @@ -0,0 +1,270 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.apache.qpid.jms.JmsConnectionFactory; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.jms.*; +import javax.jms.IllegalStateException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * ActiveMQ server + * + * @author saeid + */ +public class MessageBusServer { + + public static final Object messageBusSessionLock = new Object(); + public static final Object consumerLock = new Object(); + public static final Object producerLock = new Object(); + private static final String MODULE_NAME = "Message Bus Server"; + + private Connection connection; + private static Session session; + + private Map consumers = new ConcurrentHashMap<>(); + private Map> producers = new ConcurrentHashMap<>(); + + private boolean isConnected = false; + + static TextMessage createMessage(String text) throws Exception { + return session.createTextMessage(text); + } + + /** + * Sets {@link ExceptionListener} + * + * @param exceptionListener + * @throws Exception + */ + void setExceptionListener(ExceptionListener exceptionListener) throws Exception { + if (connection != null) { + connection.setExceptionListener(exceptionListener); + } + } + + /** + * starts ActiveMQ server + * + * @throws Exception + */ + void startServer(String routerHost, int routerPort) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Starting server"); + JmsConnectionFactory connectionFactory = new JmsConnectionFactory(String.format("amqp://%s:%d", routerHost, routerPort)); + connection = connectionFactory.createConnection(); + LoggingService.logDebug(MODULE_NAME, "Finished starting server"); + } + + /** + * creates IOFog {@link javax.jms.Message} producers + * and {@link Session} + * + * @throws Exception + */ + void initialize() throws Exception { + LoggingService.logDebug(MODULE_NAME, "Starting initialization"); + synchronized (messageBusSessionLock) { + session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE); + connection.start(); + } + LoggingService.logDebug(MODULE_NAME, "Finished initialization"); + } + + /** + * creates a new {@link MessageConsumer} for receiver {@link Microservice} + * + * @param name - ID of {@link Microservice} + * @throws Exception + */ + void createConsumer(String name) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Starting create consumer"); + + synchronized (consumerLock) { + Destination messageQueue = session.createQueue(name); + MessageConsumer consumer = session.createConsumer(messageQueue); + consumers.put(name, consumer); + } + + LoggingService.logDebug(MODULE_NAME, "Finished create consumer"); + } + + /** + * returns {@link MessageConsumer} of a receiver {@link Microservice} + * + * @param receiver - ID of {@link Microservice} + * @return {@link MessageConsumer} + */ + MessageConsumer getConsumer(String receiver) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start get consumer"); + if (consumers == null || !consumers.containsKey(receiver)) + try { + createConsumer(receiver); + } catch (IllegalStateException e) { + setConnected(false); + throw e; + } + + LoggingService.logDebug(MODULE_NAME, "Finished get consumer"); + return consumers.get(receiver); + } + + /** + * removes {@link MessageConsumer} when a receiver {@link Microservice} has been removed + * + * @param name - ID of {@link Microservice} + */ + void removeConsumer(String name) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start remove consumer"); + + synchronized (consumerLock) { + if (consumers != null && consumers.containsKey(name)) { + MessageConsumer consumer = consumers.remove(name); + consumer.close(); + } + } + + LoggingService.logDebug(MODULE_NAME, "Finished remove consumer"); + } + + /** + * creates a new {@link MessageProducer} for publisher {@link Microservice} + * + * @param name - ID of {@link Microservice} + * @throws Exception + */ + void createProducer(String name, List receivers) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start create Producer"); + + synchronized (producerLock) { + if (receivers != null && receivers.size() > 0) { + List messageProducers = new ArrayList<>(); + for (String receiver: receivers) { + Destination messageQueue = session.createQueue(receiver); + MessageProducer producer = session.createProducer(messageQueue); + messageProducers.add(producer); + } + producers.put(name, messageProducers); + } + } + + LoggingService.logDebug(MODULE_NAME, "Finish create Producer"); + } + + /** + * returns {@link MessageProducer} of a publisher {@link Microservice} + * + * @param publisher - ID of {@link Microservice} + * @return {@link MessageProducer} + */ + List getProducer(String publisher, List receivers) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start get Producer"); + + if (!producers.containsKey(publisher)) { + try { + createProducer(publisher, receivers); + } catch (IllegalStateException e) { + setConnected(false); + throw e; + } + } + + LoggingService.logDebug(MODULE_NAME, "Finish get Producer"); + return producers.get(publisher); + } + + /** + * removes {@link MessageConsumer} when a receiver {@link Microservice} has been removed + * + * @param name - ID of {@link Microservice} + */ + void removeProducer(String name) { + LoggingService.logDebug(MODULE_NAME, "Start remove Producer"); + + synchronized (producerLock) { + if (producers != null && producers.containsKey(name)) { + List messageProducers = producers.remove(name); + messageProducers.forEach(producer -> { + try { + producer.close(); + } catch (Exception e) { + LoggingService.logWarning(MODULE_NAME, "Unable to close producer"); + } + }); + } + } + + LoggingService.logDebug(MODULE_NAME, "Finish remove Producer"); + } + + /** + * stops all consumers, producers and ActiveMQ server + * + * @throws Exception + */ + void stopServer() throws Exception { + LoggingService.logDebug(MODULE_NAME, "stopping server started"); + if (consumers != null) { + consumers.forEach((key, value) -> { + try { + value.close(); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error closing consumer", + new AgentSystemException(e.getMessage(), e)); + } + }); + consumers.clear(); + } + if (producers != null) { + producers.forEach((key, value) -> { + value.forEach(producer -> { + try { + producer.close(); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error closing producer", + new AgentSystemException(e.getMessage(), e)); + } + }); + }); + producers.clear(); + } + + if (session != null) { + session.close(); + } + + if (connection != null) { + connection.close(); + } + + LoggingService.logDebug(MODULE_NAME, "stopped server"); + } + + public boolean isConnected() { + synchronized (messageBusSessionLock) { + return isConnected; + } + } + + public void setConnected(boolean connected) { + synchronized (messageBusSessionLock) { + isConnected = connected; + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusStatus.java new file mode 100644 index 00000000..32e8bcf8 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusStatus.java @@ -0,0 +1,87 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import java.util.HashMap; +import java.util.Map; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; + +/** + * represents Message Bus status + * + * @author saeid + * + */ +public class MessageBusStatus { + private long processedMessages; + private final Map publishedMessagesPerMicroservice; + private float averageSpeed; + + public MessageBusStatus() { + publishedMessagesPerMicroservice = new HashMap<>(); + processedMessages = 0; + averageSpeed = 0; + } + + public long getProcessedMessages() { + return processedMessages; + } + + public Long getPublishedMessagesPerMicroservice(String microservice) { + return publishedMessagesPerMicroservice.get(microservice); + } + + public Map getPublishedMessagesPerMicroservice() { + return publishedMessagesPerMicroservice; + } + + public MessageBusStatus increasePublishedMessagesPerMicroservice(String microservice) { + this.processedMessages++; + + Long n = this.publishedMessagesPerMicroservice.get(microservice); + if (n == null) + n = 0L; + this.publishedMessagesPerMicroservice.put(microservice, n + 1); + return this; + } + + public float getAverageSpeed() { + return averageSpeed; + } + + public MessageBusStatus setAverageSpeed(float averageSpeed) { + this.averageSpeed = averageSpeed; + return this; + } + + public void removePublishedMessagesPerMicroservice(String microservice) { + if (publishedMessagesPerMicroservice.containsKey(microservice)) + publishedMessagesPerMicroservice.remove(microservice); + } + + public String getJsonPublishedMessagesPerMicroservice() { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + publishedMessagesPerMicroservice.forEach((key, value) -> { + JsonObjectBuilder objectBuilder = Json.createObjectBuilder() + .add("id", key) + .add("messagecount", value); + arrayBuilder.add(objectBuilder); + + }); + return arrayBuilder.build().toString(); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusUtil.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusUtil.java new file mode 100644 index 00000000..c1a8feda --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusUtil.java @@ -0,0 +1,101 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.microservice.Route; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.logging.LoggingService; + +public class MessageBusUtil { + + private final MessageBus messageBus; + private static final String MODULE_NAME = "Message Bus Util"; + public MessageBusUtil() { + messageBus = MessageBus.getInstance(); + } + + /** + * sets messageId and timestamp and publish the {@link Message} + * + * @param message - {@link Message} to be published + * @return published {@link Message} containing the id and timestamp + */ + public void publishMessage(Message message) { + LoggingService.logDebug(MODULE_NAME, "Start publish message"); + long timestamp = System.currentTimeMillis(); + StatusReporter.setMessageBusStatus().increasePublishedMessagesPerMicroservice(message.getPublisher()); + message.setId(messageBus.getNextId()); + message.setTimestamp(timestamp); + + MessagePublisher publisher = messageBus.getPublisher(message.getPublisher()); + if (publisher != null) { + try { + publisher.publish(message); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Unable to send message : Message Publisher (" + publisher.getName()+ ")", + new AgentSystemException(e.getMessage(), e)); + } + } + LoggingService.logDebug(MODULE_NAME, "Finishing publish message"); + } + + /** + * gets list of {@link Message} for receiver + * + * @param receiver - ID of {@link Microservice} + * @return list of {@link Message} + */ + public List getMessages(String receiver) { + LoggingService.logDebug(MODULE_NAME, "Starting get message"); + List messages = new ArrayList<>(); + MessageReceiver rec = messageBus.getReceiver(receiver); + if (rec != null) { + try { + messages = rec.getMessages(); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "unable to receive messages : Message Receiver (" + receiver + ")", + new AgentSystemException(e.getMessage(), e)); + } + } + LoggingService.logDebug(MODULE_NAME, "Finishing get message"); + return messages; + } + + /** + * gets list of {@link Message} within a time frame + * + * @param publisher - ID of {@link Microservice} + * @param receiver - ID of {@link Microservice} + * @param from - beginning of time frame + * @param to - end of time frame + * @return list of {@link Message} + */ + public List messageQuery(String publisher, String receiver, long from, long to) { + LoggingService.logDebug(MODULE_NAME, "Starting message query"); + Route route = messageBus.getRoutes().get(publisher); + if (to < from || route == null || !route.getReceivers().contains(receiver)) + return null; + + MessagePublisher messagePublisher = messageBus.getPublisher(publisher); + if (messagePublisher == null) + return null; + LoggingService.logDebug(MODULE_NAME, "Finishing message query"); + return messagePublisher.messageQuery(from, to); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageIdGenerator.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageIdGenerator.java new file mode 100644 index 00000000..56a9b881 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageIdGenerator.java @@ -0,0 +1,110 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import java.util.LinkedList; +import java.util.Queue; +import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * class to generate unique id for {@link Message} + * + * @author saeid + * + */ +public class MessageIdGenerator { + private static final char[] ALPHABETS_ARRAY = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789".toCharArray(); + + /** + * converts base 10 to base 58 + * + * @param number - number to be converted + * @return base 58 presentation of number + */ + private String toBase58(long number) { + StringBuilder result = new StringBuilder(); + while (number >= 58) { + result.append(ALPHABETS_ARRAY[(int) (number % 58)]); + number /= 58; + } + result.append(ALPHABETS_ARRAY[(int) number]); + return result.reverse().toString(); + } + + + private final int MAX_SEQUENCE = 100000000; + private volatile long lastTime = 0; + private volatile int sequence = MAX_SEQUENCE; + /** + * generates unique id based on time and sequence + * + * @param time - timestamp in milliseconds + */ + public synchronized String generate(long time) { + if (lastTime == time) { + sequence--; + } else { + lastTime = time; + sequence = MAX_SEQUENCE; + } + return toBase58(time) + toBase58(sequence); + } + + + // uuid + private static final int PRE_GENERATED_IDS_COUNT = 100_000; + private boolean isRefilling = false; + private final Queue generatedIds = new LinkedList<>(); + /** + * generates unique id based on UUID + * + */ + private final Runnable refill = () -> { + if (isRefilling) + return; + isRefilling = true; + while (generatedIds.size() < PRE_GENERATED_IDS_COUNT) + synchronized (generatedIds) { + generatedIds.offer(UUID.randomUUID().toString().replaceAll("-", "")); + } + isRefilling = false; + }; + + /** + * returns next generated id from list + * + * @return id + */ + public String getNextId() { + while (generatedIds.size() == 0); + synchronized (generatedIds) { + return generatedIds.poll(); + } + } + + public MessageIdGenerator() { + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(refill, 0, 5, TimeUnit.SECONDS); + } + +// 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 +// 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789 +// Double: uWJ7hf5NAL7ufAjUbfwAfAwuwQfLNN9y9wyUQy5syYE3W9qJsWC3bEQu7WYUWJyUYNu3EbSfbCLWwdNsGsEYuqY35h79YoSqYh7bfYWGGNmWqYyWoummsdwodoqLyjGSwyfWhu3hb1Q1J9wWhdUbJufo9AACYJyuYG3E5mmTre6jpcs +// Float: 319jQQdmU33UhsMFqAEuBx +// Long: XZvFwUyHzQM +// Integer: DRvaEH +// Elements: rv8H3m2fdzKJrKNVGftmWFYDbvgb3tpv +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessagePublisher.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessagePublisher.java new file mode 100644 index 00000000..4b2447f3 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessagePublisher.java @@ -0,0 +1,121 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.microservice.Route; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.jms.*; +import java.util.List; + +import static org.eclipse.iofog.message_bus.MessageBus.MODULE_NAME; +import static org.eclipse.iofog.utils.logging.LoggingService.logError; + +/** + * publisher {@link Microservice} + * + * @author saeid + * + */ +public class MessagePublisher implements AutoCloseable{ + private final MessageArchive archive; + private final String name; + private List producers; + private Route route; + + public MessagePublisher(String name, Route route, List producers) { + this.archive = new MessageArchive(name); + this.route = route; + this.name = name; + this.producers = producers; + } + + public String getName() { + return name; + } + + /** + * publishes a {@link Message} + * + * @param message - {@link Message} to be published + * @throws Exception + */ + synchronized void publish(Message message) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start publish message :" + this.name ); + byte[] bytes = message.getBytes(); + + try { + archive.save(bytes, message.getTimestamp()); + } catch (Exception e) { + logError(MODULE_NAME, "Message Publisher (" + this.name + ")unable to archive message", + new AgentSystemException(e.getMessage(), e)); + } + + for (MessageProducer producer: producers) { + try { + TextMessage msg = MessageBusServer.createMessage(message.toJson().toString()); + producer.send(msg, DeliveryMode.NON_PERSISTENT, javax.jms.Message.DEFAULT_PRIORITY, javax.jms.Message.DEFAULT_TIME_TO_LIVE); + } catch (Exception e) { + logError(MODULE_NAME, "Message Publisher (" + this.name + ") unable to send message", + new AgentSystemException(e.getMessage(), e)); + } + } + LoggingService.logDebug(MODULE_NAME, "Finished publish message : " + this.name); + } + + synchronized void updateRoute(Route route, List producers) { + LoggingService.logDebug(MODULE_NAME, "Updating route"); + this.route = route; + this.producers = producers; + } + + public synchronized void close() { + LoggingService.logDebug(MODULE_NAME, "Start closing publish"); + try { + archive.close(); + } catch (Exception exp) { + logError(MODULE_NAME, "Error closing message archive", new AgentSystemException(exp.getMessage(), exp)); + } + + if (producers != null && producers.size() > 0) { + for (MessageProducer producer: producers) { + try { + producer.close(); + } catch (Exception exp) { + logError(MODULE_NAME, "Error closing message publisher", new AgentSystemException(exp.getMessage(), exp)); + } + } + producers.clear(); + } + + LoggingService.logDebug(MODULE_NAME, "Finished closing publish"); + } + + /** + * retrieves list of {@link Message} published by this {@link Microservice} + * within a time frame + * + * @param from - beginning of time frame + * @param to - end of time frame + * @return list of {@link Message} + */ + public synchronized List messageQuery(long from, long to) { + return archive.messageQuery(from, to); + } + + public Route getRoute() { + return route; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageReceiver.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageReceiver.java new file mode 100644 index 00000000..83e2ab32 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageReceiver.java @@ -0,0 +1,150 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.local_api.MessageCallback; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.jms.MessageConsumer; +import javax.jms.TextMessage; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.StringReader; +import java.util.ArrayList; +import java.util.List; + +import static org.eclipse.iofog.utils.logging.LoggingService.logError; + +/** + * receiver {@link Microservice} + * + * @author saeid + * + */ +public class MessageReceiver implements AutoCloseable{ + private static final String MODULE_NAME = "MessageReceiver"; + + private final String name; + + private IOMessageListener listener; + private final MessageConsumer consumer; + + public MessageReceiver(String name, MessageConsumer consumer) { + this.name = name; + this.consumer = consumer; + this.listener = null; + } + + /** + * receivers list of {@link Message} sent to this {@link Microservice} + * + * @return list of {@link Message} + * @throws Exception + */ + synchronized List getMessages() throws Exception { + LoggingService.logDebug(MODULE_NAME, String.format("Start getting message \"%s\"", name)); + List result = new ArrayList<>(); + + if (consumer != null || listener == null) { + Message message = getMessage(); + while (message != null) { + result.add(message); + message = getMessage(); + } + } + LoggingService.logDebug(MODULE_NAME, String.format("Finished getting message \"%s\"", name)); + return result; + } + + /** + * receives only one {@link Message} + * + * @return {@link Message} + * @throws Exception + */ + private Message getMessage() throws Exception { + if (consumer == null || listener != null) + return null; + + Message result = null; + TextMessage msg; + msg = (TextMessage) consumer.receiveNoWait(); + if (msg != null) { + msg.acknowledge(); + JsonReader jsonReader = Json.createReader(new StringReader(msg.getText())); + JsonObject json = jsonReader.readObject(); + jsonReader.close(); + + result = new Message(json); + } + return result; + } + + protected String getName() { + return name; + } + + /** + * enables real-time receiving for this {@link Microservice} + * + */ + void enableRealTimeReceiving() { + LoggingService.logDebug(MODULE_NAME, "Start enable real time receiving"); + + listener = new IOMessageListener(new MessageCallback(name)); + try { + consumer.setMessageListener(listener); + } catch (Exception e) { + listener = null; + LoggingService.logError(MODULE_NAME, "Error in enabling real time listener", + new AgentSystemException(e.getMessage(), e)); + } + LoggingService.logDebug(MODULE_NAME, "Finished enable real time receiving"); + } + + /** + * disables real-time receiving for this {@link Microservice} + * + */ + void disableRealTimeReceiving() { + LoggingService.logDebug(MODULE_NAME, "Start disable real time receiving"); + try { + if (consumer == null || listener == null || consumer.getMessageListener() == null) + return; + listener = null; + consumer.setMessageListener(null); + } catch (Exception exp) { + logError(MODULE_NAME, "Error in disabling real time listener", + new AgentSystemException(exp.getMessage() + , exp)); + } + LoggingService.logDebug(MODULE_NAME, "Finished disable real time receiving"); + } + + public void close() { + LoggingService.logDebug(MODULE_NAME, "Start closing receiver"); + if (consumer == null) + return; + disableRealTimeReceiving(); + try { + consumer.close(); + } catch (Exception exp) { + logError(MODULE_NAME, "Error in closing receiver", + new AgentSystemException(exp.getMessage(), exp)); + } + LoggingService.logDebug(MODULE_NAME, "Finished closing receiver"); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/EnvVar.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/EnvVar.java new file mode 100644 index 00000000..20bb8bad --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/EnvVar.java @@ -0,0 +1,51 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.microservice; + +/** + * represents Microservices port mappings + * + * @author saeid + * + */ +public class EnvVar { + private final String key; + private final String value; + + public EnvVar(String key, String value) { + this.key = key; + this.value = value; + } + + public String getKey() { + return key; + } + + public String getValue() { + return value; + } + + @Override + public String toString() { + return "{" + "key='" + key + '\'' + ", value='" + value + '\'' + '}'; + } + + @Override + public boolean equals(Object other) { + if (other == null || getClass() != other.getClass()) return false; + + EnvVar o = (EnvVar) other; + return this.key.equals(o.key) && this.value.equals(o.value); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Microservice.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Microservice.java new file mode 100644 index 00000000..54ef5aaa --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Microservice.java @@ -0,0 +1,209 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.microservice; + +import java.util.List; + +/** + * represents Microservices + * + * @author saeid + */ +public class Microservice { + + public static final Object deleteLock = new Object(); + private final String microserviceUuid; //container name + private final String imageName; + private List portMappings; + private String config; + private List routes; + private String containerId; + private int registryId; + private String containerIpAddress; + private boolean rebuild; + private boolean rootHostAccess; + private long logSize; + private List volumeMappings; + private boolean isUpdating; + private List envVars; + private List args; + private List extraHosts; + private boolean isConsumer; + + private boolean delete; + private boolean deleteWithCleanup; + private boolean isStuckInRestart; + + public Microservice(String microserviceUuid, String imageName) { + this.microserviceUuid = microserviceUuid; + this.imageName = imageName; + containerId = ""; + } + + public boolean isRebuild() { + return rebuild; + } + + public void setRebuild(boolean rebuild) { + this.rebuild = rebuild; + } + + public String getContainerIpAddress() { + return containerIpAddress; + } + + public void setContainerIpAddress(String containerIpAddress) { + this.containerIpAddress = containerIpAddress; + } + + public int getRegistryId() { + return registryId; + } + + public void setRegistryId(int registryId) { + this.registryId = registryId; + } + + public String getContainerId() { + return containerId; + } + + public void setContainerId(String containerId) { + this.containerId = containerId; + } + + public List getPortMappings() { + return portMappings; + } + + public void setPortMappings(List portMappings) { + this.portMappings = portMappings; + } + + public String getConfig() { + return config; + } + + public void setConfig(String config) { + this.config = config; + } + + public String getMicroserviceUuid() { + return microserviceUuid; + } + + public String getImageName() { + return imageName; + } + + public boolean isRootHostAccess() { + return rootHostAccess; + } + + public void setRootHostAccess(boolean rootHostAccess) { + this.rootHostAccess = rootHostAccess; + } + + public long getLogSize() { + return logSize; + } + + public void setLogSize(long logSize) { + this.logSize = logSize; + } + + public List getVolumeMappings() { + return volumeMappings; + } + + public void setVolumeMappings(List volumeMappings) { + this.volumeMappings = volumeMappings; + } + + public synchronized boolean isUpdating() { + return isUpdating; + } + + public synchronized void setUpdating(boolean updating) { + isUpdating = updating; + } + + public boolean isDelete() { + return delete; + } + + public void setDelete(boolean delete) { + this.delete = delete; + } + + public boolean isDeleteWithCleanup() { + return deleteWithCleanup; + } + + public void setDeleteWithCleanup(boolean deleteWithCleanUp) { + this.deleteWithCleanup = deleteWithCleanUp; + } + + public List getEnvVars() { return envVars; } + + public void setEnvVars(List envVars) { this.envVars = envVars; } + + public List getArgs() { return args; } + + public void setArgs(List args) { this.args = args; } + + @Override + public boolean equals(Object e) { + if (this == e) return true; + if (e == null || getClass() != e.getClass()) return false; + Microservice microservice = (Microservice) e; + return this.microserviceUuid.equals(microservice.getMicroserviceUuid()); + } + + @Override + public int hashCode() { + return microserviceUuid.hashCode(); + } + + public List getRoutes() { + return routes; + } + + public void setRoutes(List routes) { + this.routes = routes; + } + + public boolean isConsumer() { + return isConsumer; + } + + public void setConsumer(boolean consumer) { + isConsumer = consumer; + } + + public List getExtraHosts() { + return extraHosts; + } + + public void setExtraHosts(List extraHosts) { + this.extraHosts = extraHosts; + } + + public boolean isStuckInRestart() { + return isStuckInRestart; + } + + public void setStuckInRestart(boolean stuckInRestart) { + isStuckInRestart = stuckInRestart; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceManager.java new file mode 100644 index 00000000..f1319406 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceManager.java @@ -0,0 +1,162 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.microservice; + +import java.util.*; + +import org.eclipse.iofog.utils.logging.LoggingService; + +/** + * microservice common repository + * thread-safe except Microservice, collections are unmodifiable + * + * @author saeid + */ +public class MicroserviceManager { + + private List latestMicroservices = new ArrayList<>(); + private List currentMicroservices = new ArrayList<>(); + private Map routes = new HashMap<>(); + private Map configs = new HashMap<>(); + private List registries = new ArrayList<>(); + private static final String MODULE_NAME = "MicroserviceManager"; + + private MicroserviceManager() { + } + + public static class SingletonHolder { + public static final MicroserviceManager em = new MicroserviceManager(); + } + + public static MicroserviceManager getInstance() { + return SingletonHolder.em; + } + + public List getLatestMicroservices() { + LoggingService.logDebug(MODULE_NAME ,"get list of latest microservices "); + synchronized (MicroserviceManager.class) { + return Collections.unmodifiableList(latestMicroservices); + } + } + + public List getCurrentMicroservices() { + LoggingService.logDebug(MODULE_NAME ,"get list of current microservices "); + synchronized (MicroserviceManager.class) { + return Collections.unmodifiableList(currentMicroservices); + } + } + + public Map getRoutes() { + LoggingService.logDebug(MODULE_NAME ,"get map of routes "); + synchronized (MicroserviceManager.class) { + return Collections.unmodifiableMap(routes); + } + } + + public Map getConfigs() { + LoggingService.logDebug(MODULE_NAME ,"get map of configs "); + synchronized (MicroserviceManager.class) { + return Collections.unmodifiableMap(configs); + } + } + + public List getRegistries() { + LoggingService.logDebug(MODULE_NAME ,"get list of registry "); + synchronized (MicroserviceManager.class) { + return Collections.unmodifiableList(registries); + } + } + + public Registry getRegistry(int id) { + LoggingService.logDebug(MODULE_NAME ,"get registry "); + synchronized (MicroserviceManager.class) { + for (Registry registry : registries) { + if (registry.getId() == id) + return registry; + } + return null; + } + } + + public void setLatestMicroservices(List latestMicroservices) { + LoggingService.logDebug(MODULE_NAME ,"set latest Microservices "); + synchronized (MicroserviceManager.class) { + this.latestMicroservices = new ArrayList<>(latestMicroservices); + } + } + + public void setCurrentMicroservices(List currentMicroservices) { + LoggingService.logDebug(MODULE_NAME ,"set Current Microservices "); + synchronized (MicroserviceManager.class) { + this.currentMicroservices = new ArrayList<>(currentMicroservices); + } + } + + public void setConfigs(Map configs) { + LoggingService.logDebug(MODULE_NAME ,"set Configs "); + synchronized (MicroserviceManager.class) { + this.configs = new HashMap<>(configs); + } + } + + public void setRoutes(Map routes) { + LoggingService.logDebug(MODULE_NAME ,"set Routes "); + synchronized (MicroserviceManager.class) { + this.routes = new HashMap<>(routes); + } + } + + public void setRegistries(List registries) { + LoggingService.logDebug(MODULE_NAME ,"set Registries "); + synchronized (MicroserviceManager.class) { + this.registries = new ArrayList<>(registries); + } + } + + /*** + * not thread safe for Microservice obj properties + */ + public Optional findLatestMicroserviceByUuid(String microserviceUuid) { + LoggingService.logDebug(MODULE_NAME ,"find Latest Microservice By Uuid "); + synchronized (MicroserviceManager.class) { + return findMicroserviceByUuid(latestMicroservices, microserviceUuid); + } + } + + public boolean microserviceExists(List microservices, String microserviceUuid) { + LoggingService.logDebug(MODULE_NAME ,"find microservice Exists"); + return findMicroserviceByUuid(microservices, microserviceUuid).isPresent(); + } + + /*** + * not thread safe for Microservice obj properties + */ + private Optional findMicroserviceByUuid(List microservices, String microserviceUuid) { + LoggingService.logDebug(MODULE_NAME ,"find Microservice By Uuid : " + microserviceUuid); + return microservices.stream() + .filter(microservice -> microservice.getMicroserviceUuid().equals(microserviceUuid)) + .findAny(); + } + + public void clear() { + LoggingService.logDebug(MODULE_NAME ,"Start microservice clear"); + synchronized (MicroserviceManager.class) { + latestMicroservices.clear(); + currentMicroservices.clear(); + routes.clear(); + configs.clear(); + registries.clear(); + } + LoggingService.logDebug(MODULE_NAME ,"Finished microservice clear"); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceState.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceState.java new file mode 100644 index 00000000..87233996 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceState.java @@ -0,0 +1,31 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.microservice; + +/** + * Created by Stolbunov D on 15.02.2018-2022. + */ +public enum MicroserviceState { + + QUEUED, PULLING, CREATING, CREATED, + STARTING, RUNNING, UPDATING, + RESTARTING, EXITING, STUCK_IN_RESTART, FAILED, + MARKED_FOR_DELETION, DELETING, DELETED, + STOPPING, STOPPED, + UNKNOWN; + + public static MicroserviceState fromText(String value){ + return valueOf(value.toUpperCase()); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceStatus.java new file mode 100644 index 00000000..c9296171 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceStatus.java @@ -0,0 +1,198 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.microservice; + + +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.CpuStatsConfig; +import com.github.dockerjava.api.model.MemoryStatsConfig; +import com.github.dockerjava.api.model.Statistics; +import org.eclipse.iofog.process_manager.DockerUtil; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.Optional; + +/** + * represents microservice status + * + * @author saeid + * + */ +public class MicroserviceStatus { + + private MicroserviceState status; + private long startTime; + private float cpuUsage; + private long memoryUsage; + private String containerId; + private float percentage; + private String errorMessage; + + public String getErrorMessage() { + return errorMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public static String getModuleName() { + return MODULE_NAME; + } + + public MicroserviceStatus() { + this.status = MicroserviceState.UNKNOWN; + } + + public MicroserviceStatus(MicroserviceState status) { + this.status = status; + this.containerId = ""; + } + + private static final String MODULE_NAME = MicroserviceStatus.class.getSimpleName(); + + public float getCpuUsage() { + return cpuUsage; + } + + private void setCpuUsage(float cpuUsage) { + this.cpuUsage = cpuUsage; + } + + public long getMemoryUsage() { + return memoryUsage; + } + + private void setMemoryUsage(long memoryUsage) { + this.memoryUsage = memoryUsage; + } + + public MicroserviceState getStatus() { + return status; + } + + public void setStatus(MicroserviceState status) { + this.status = status; + } + + public long getStartTime() { + return startTime; + } + + public void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getOperatingDuration() { + return System.currentTimeMillis() - startTime; + } + + public String getContainerId() { + return containerId; + } + + public void setContainerId(String containerId) { + this.containerId = containerId; + } + + public float getPercentage() { + return percentage; + } + + public void setPercentage(float percentage) { + this.percentage = percentage; + } + + /** + * set in {@link MicroserviceStatus} cpu usage and memory usage of given {@link Container} + * + * @param containerId - id of {@link Container} + */ + public void setUsage(String containerId) { + DockerUtil docker = DockerUtil.getInstance(); + if (docker.isContainerRunning(containerId)) { + Optional statisticsBefore = docker.getContainerStats(containerId); + + try { + Thread.sleep(200); + } catch (InterruptedException exp) { + LoggingService.logError(MODULE_NAME, exp.getMessage(), exp); + } + + Optional statisticsAfter = docker.getContainerStats(containerId); + + if (statisticsBefore.isPresent() && statisticsAfter.isPresent()) { + Statistics before = statisticsBefore.get(); + CpuStatsConfig usageBefore = before.getCpuStats(); + if (usageBefore != null) { + float totalUsageBefore = extractTotalUsage(usageBefore); + float systemCpuUsageBefore = extractSystemCpuUsage(usageBefore); + + Statistics after = statisticsAfter.get(); + CpuStatsConfig usageAfter = after.getCpuStats(); + if (usageAfter != null) { + float totalUsageAfter = extractTotalUsage(usageAfter); + float systemCpuUsageAfter = extractSystemCpuUsage(usageAfter); + setCpuUsage(Math.abs(1000f * ((totalUsageAfter - totalUsageBefore) / (systemCpuUsageAfter - systemCpuUsageBefore)))); + + MemoryStatsConfig memoryUsage = after.getMemoryStats(); + if (memoryUsage != null) { + setMemoryUsage(extractMemoryUsage(memoryUsage)); + } + } + } + } + } + } + + private long extractMemoryUsage(MemoryStatsConfig memoryUsage) { + Long usage = memoryUsage.getUsage(); + if (usage == null) { + return 0; + } + + return usage; + } + + @SuppressWarnings("unchecked") + private float extractTotalUsage(CpuStatsConfig cpuStatsConfig) { + float totalUsage = 0; + if (cpuStatsConfig.getCpuUsage() != null){ + totalUsage = cpuStatsConfig.getCpuUsage().getTotalUsage(); + } + return totalUsage; + } + + private float extractSystemCpuUsage(CpuStatsConfig cpuStatsConfig) { + Long usage = cpuStatsConfig.getSystemCpuUsage(); + if (usage == null) { + return 0; + } + + return usage; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MicroserviceStatus that = (MicroserviceStatus) o; + return status == that.status; + } + + @Override + public int hashCode() { + return status != null ? status.hashCode() : 0; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/PortMapping.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/PortMapping.java new file mode 100644 index 00000000..b7d9a79f --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/PortMapping.java @@ -0,0 +1,67 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.microservice; + +/** + * represents Microservices port mappings + * + * @author saeid + * + */ +public class PortMapping implements Comparable { + private final int outside; + private final int inside; + private boolean udp; + + public PortMapping(int outside, int inside, boolean udp) { + this.outside = outside; + this.inside = inside; + this.udp = udp; + } + + public int getOutside() { + return outside; + } + + public int getInside() { + return inside; + } + + public boolean isUdp() { + return udp; + } + + + @Override + public String toString() { + return "{" + "outside='" + outside + '\'' + ", inside='" + inside + '\'' + ", isUdp='" + udp + '\'' + '}'; + } + + @Override + public boolean equals(Object other) { + if (this == other) return true; + if (other == null || getClass() != other.getClass()) return false; + + PortMapping o = (PortMapping) other; + return this.outside == o.outside && this.inside == o.inside; + } + + @Override + public int compareTo(PortMapping o) { + if (this.inside == o.inside) { + return this.outside - o.outside; + } + + return this.inside - o.inside; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Registry.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Registry.java new file mode 100644 index 00000000..b2911138 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Registry.java @@ -0,0 +1,159 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.microservice; + +import java.util.Objects; + +/** + * represents registries + * + * @author saeid + * + */ +public class Registry { + private final int id; + private final String url; + private final boolean isPublic; + private final boolean secure; + private final String certificate; + private final boolean requiresCertificate; + private final String userName; + private final String password; + private final String userEmail; + + private Registry(final int id, final String url, final boolean isPublic, final boolean secure, final String certificate, final boolean requiresCertificate, + final String userName, final String password, final String userEmail) { + this.id = id; + this.url = url; + this.isPublic = isPublic; + this.secure = secure; + this.certificate = certificate; + this.requiresCertificate = requiresCertificate; + this.userName = userName; + this.password = password; + this.userEmail = userEmail; + } + + public int getId() { + return id; + } + + public String getUrl() { + return url; + } + + public boolean getIsPublic() { + return isPublic; + } + + public boolean isSecure() { + return secure; + } + + public String getCertificate() { + return certificate; + } + + public boolean isRequiresCertificate() { + return requiresCertificate; + } + + public String getUserName() { + return userName; + } + + public String getPassword() { + return password; + } + + public String getUserEmail() { + return userEmail; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Registry registry = (Registry) o; + + if (isPublic != registry.isPublic) return false; + return id == registry.id; + } + + @Override + public int hashCode() { + return Objects.hash(url, isPublic); + } + + public static class RegistryBuilder { + private int id; + private String url; + private boolean isPublic; + private boolean secure; + private String certificate; + private boolean requiresCertificate; + private String userName; + private String password; + private String userEmail; + + public RegistryBuilder setId(int id) { + this.id = id; + return this; + } + + public RegistryBuilder setUrl(String url) { + this.url = url; + return this; + } + + public RegistryBuilder setIsPublic(boolean isPublic) { + this.isPublic = isPublic; + return this; + } + + public RegistryBuilder setSecure(boolean secure) { + this.secure = secure; + return this; + } + + public RegistryBuilder setCertificate(String certificate) { + this.certificate = certificate; + return this; + } + + public RegistryBuilder setRequiresCertificate(boolean requiresCertificate) { + this.requiresCertificate = requiresCertificate; + return this; + } + + public RegistryBuilder setUserName(String userName) { + this.userName = userName; + return this; + } + + public RegistryBuilder setPassword(String password) { + this.password = password; + return this; + } + + public RegistryBuilder setUserEmail(String userEmail) { + this.userEmail = userEmail; + return this; + } + + public Registry build() { + return new Registry(id, url, isPublic, secure, certificate, requiresCertificate, userName, password, userEmail); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Route.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Route.java new file mode 100644 index 00000000..a453c049 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Route.java @@ -0,0 +1,63 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.microservice; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * represents microservice routings + * + * @author saeid + * + */ +public class Route { + private List receivers; + + public Route() { + receivers = new ArrayList<>(); + } + + public List getReceivers() { + return receivers; + } + + public void setReceivers(List receivers) { + this.receivers = receivers; + this.receivers.sort(String::compareTo); + } + + @Override + public String toString() { + StringBuilder in = new StringBuilder("\"receivers\" : ["); + if (receivers != null) + for (String e : receivers) + in.append("\"").append(e).append("\","); + in.append("]"); + return "{" + in + "}"; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Route route = (Route) o; + return receivers.equals(route.receivers); + } + + @Override + public int hashCode() { + return receivers.hashCode(); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMapping.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMapping.java new file mode 100644 index 00000000..6da7b55d --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMapping.java @@ -0,0 +1,77 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.microservice; + +import java.util.Objects; + +/** + * represents microservice volume mappings for Docker run options + * + * @author ilaryionava + */ +public class VolumeMapping { + + private final String hostDestination; + private final String containerDestination; + private final String accessMode; + private final VolumeMappingType type; + + public VolumeMapping(String hostDestination, String containerDestination, String accessMode, VolumeMappingType type) { + this.hostDestination = hostDestination; + this.containerDestination = containerDestination; + this.accessMode = accessMode; + this.type = type; + } + + public String getHostDestination() { + return hostDestination; + } + + public String getContainerDestination() { + return containerDestination; + } + + public String getAccessMode() { + return accessMode; + } + + public VolumeMappingType getType() { + return type; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + VolumeMapping that = (VolumeMapping) o; + return Objects.equals(hostDestination, that.hostDestination) && + Objects.equals(containerDestination, that.containerDestination) && + Objects.equals(accessMode, that.accessMode) && + Objects.equals(type, that.type); + } + + @Override + public int hashCode() { + return Objects.hash(hostDestination, containerDestination, accessMode, type); + } + + @Override + public String toString() { + return "VolumeMapping{" + + "hostDestination='" + hostDestination + '\'' + + ", containerDestination='" + containerDestination + '\'' + + ", accessMode='" + accessMode + '\'' + + ", type='" + type + '\'' + + '}'; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMappingType.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMappingType.java new file mode 100644 index 00000000..8a3d45a3 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMappingType.java @@ -0,0 +1,6 @@ +package org.eclipse.iofog.microservice; + +public enum VolumeMappingType { + VOLUME, + BIND +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterface.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterface.java new file mode 100644 index 00000000..eefa2d2e --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterface.java @@ -0,0 +1,193 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.network; + +import org.apache.commons.lang.exception.ExceptionUtils; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.process_manager.DockerUtil; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.functional.Pair; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.net.*; +import java.util.*; +import java.util.concurrent.*; + +import static org.eclipse.iofog.command_line.CommandLineConfigParam.NETWORK_INTERFACE; + +/** + * Created by ekrylovich + * on 2/8/18. + */ +public class IOFogNetworkInterface { + private static final String MODULE_NAME = "IOFogNetworkInterface"; + + private static final String UNABLE_TO_GET_IP_ADDRESS = "Unable to get ip address. Please check network connection"; + private static String dockerBridgeInterfaceName = "bridge"; + + /** + * returns IPv4 host address of IOFog network interface + * + * @return {@link Inet4Address} + * @throws Exception + */ + public static String getCurrentIpAddress() { + Optional inetAddress = getLocalIp(); + return inetAddress.map(InetAddress::getHostAddress).orElse(""); + } + + private static Optional getLocalIp() { + Optional inetAddress = Optional.empty(); + try { + inetAddress = Optional.of(getInetAddress()); + } catch (SocketException | MalformedURLException exp) { + LoggingService.logError(MODULE_NAME, "Unable to find the local IP address", new AgentSystemException(exp.getMessage(), exp)); + } catch (Exception e){ + LoggingService.logError(MODULE_NAME, "Unable to find the IP address of the machine running ioFog exception occurred", new AgentSystemException(e.getMessage(), e)); + } + return inetAddress; + } + + /** + * returns IPv4 address of IOFog network interface + * + * @return {@link Inet4Address} + * @throws Exception + */ + public static InetAddress getInetAddress() throws SocketException, MalformedURLException { + try { + final Pair connectedAddress = getNetworkInterface(); + if (connectedAddress != null) { + return connectedAddress._2(); + } + } catch (SocketException | MalformedURLException e){ + throw e; + } catch (Exception e){ + throw e; + } + return null; + } + + public static Pair getNetworkInterface() throws MalformedURLException, SocketException { + final String configNetworkInterface = Configuration.getNetworkInterface(); + if (NETWORK_INTERFACE.getDefaultValue().equals(configNetworkInterface)) { + return getOSNetworkInterface(); + } + + try { + URL controllerUrl = new URL(Configuration.getControllerUrl()); + NetworkInterface networkInterface = NetworkInterface.getByName(configNetworkInterface); + return getConnectedAddress(controllerUrl, networkInterface); + } catch (Exception e) { + LoggingService.logWarning(MODULE_NAME, "Unable to get Network Interface : " + ExceptionUtils.getFullStackTrace(e)); + throw e; + } + } + + private static void setDockerBridgeInterfaceName() throws Exception { + // Docker-Java#listNetworksCmd() stucks forever on ARM when docker url is set to unix domain + // Adding a timeout + Runnable getDockerBridgeInterfaceName = new Thread(() -> { + dockerBridgeInterfaceName = DockerUtil.getInstance().getDockerBridgeName(); + }); + + final ExecutorService executor = Executors.newSingleThreadExecutor(); + final Future future = executor.submit(getDockerBridgeInterfaceName); + executor.shutdown(); + + try { + future.get(1, TimeUnit.SECONDS); + } catch (Exception e) { + LoggingService.logWarning(MODULE_NAME, "Unable to set Docker Bridge Interface Name : " + ExceptionUtils.getFullStackTrace(e)); + dockerBridgeInterfaceName = null; + } + } + + private static Pair getOSNetworkInterface() { + try { + setDockerBridgeInterfaceName(); + + URL controllerUrl = new URL(Configuration.getControllerUrl()); + NetworkInterface dockerBridgeNetworkInterface = null; + + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface networkInterface: Collections.list(networkInterfaces)) { + if (dockerBridgeInterfaceName != null && networkInterface.getName().equals(dockerBridgeInterfaceName)) { + dockerBridgeNetworkInterface = networkInterface; + continue; + } + + if (networkInterface.isLoopback() || networkInterface.isVirtual() || !networkInterface.isUp()) { + continue; + } + + Pair connectedAddress = getConnectedAddress(controllerUrl, networkInterface); + if (connectedAddress != null) { + return connectedAddress; + } + } + + if (dockerBridgeNetworkInterface != null) { + return getConnectedAddress(controllerUrl, dockerBridgeNetworkInterface, false); + } + + return null; + } catch (Exception e) { + LoggingService.logWarning(MODULE_NAME, "Unable to Get OS Network Interface : " + ExceptionUtils.getFullStackTrace(e)); + return null; + } + } + + private static Pair getConnectedAddress(URL controllerUrl, NetworkInterface networkInterface) { + return getConnectedAddress(controllerUrl, networkInterface, true); + } + + private static Pair getConnectedAddress(URL controllerUrl, NetworkInterface networkInterface, boolean checkConnection) { + int controllerPort = controllerUrl.getPort(); + String controllerHost = controllerUrl.getHost(); + Enumeration nifAddresses = networkInterface.getInetAddresses(); + for (InetAddress nifAddress: Collections.list(nifAddresses)) { + LoggingService.logInfo(MODULE_NAME, "Detected network interface: " + networkInterface.toString() + " And network address - hostAddress : "+ nifAddress.getHostAddress() + " type of : " + nifAddress.getClass().getName()); + if (!(nifAddress instanceof Inet4Address)) { + continue; + } + + if (!checkConnection) { + return Pair.of(networkInterface, nifAddress); + } + + try { + Socket soc = new java.net.Socket(); + soc.bind(new InetSocketAddress(nifAddress, 0)); + soc.connect(new InetSocketAddress(controllerHost, controllerPort), 1000); + soc.close(); + return Pair.of(networkInterface, nifAddress); + } catch (Exception e) { + LoggingService.logWarning(MODULE_NAME, "Unable to Get Connected Address : " + ExceptionUtils.getFullStackTrace(e)); + } + } + + return null; + } + public static String getHostName() { + String hostname = ""; + try { + InetAddress ip = InetAddress.getLocalHost(); + hostname = ip.getHostName(); + } catch (UnknownHostException e) { + LoggingService.logWarning(MODULE_NAME, "Unable to get hostname : " + ExceptionUtils.getFullStackTrace(e)); + } + return hostname; + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterfaceManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterfaceManager.java new file mode 100644 index 00000000..8f679e67 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterfaceManager.java @@ -0,0 +1,126 @@ +package org.eclipse.iofog.network; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.utils.functional.Pair; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.lang.management.ManagementFactory; +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public class IOFogNetworkInterfaceManager { + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private ScheduledFuture futureTask; + + private static final String MODULE_NAME = "IOFogNetworkInterfaceManager"; + private static IOFogNetworkInterfaceManager instance; + private String currentIpAddress; + private Pair networkInterface; + private String hostName; + private long pid; + + public String getCurrentIpAddress() { + return currentIpAddress; + } + + public void setCurrentIpAddress(String currentIpAddress) { + this.currentIpAddress = currentIpAddress; + } + + public InetAddress getInetAddress() { + return inetAddress; + } + + public void setInetAddress(InetAddress inetAddress) { + this.inetAddress = inetAddress; + } + + public Pair getNetworkInterface() { + return networkInterface; + } + + public void setNetworkInterface(Pair networkInterface) { + this.networkInterface = networkInterface; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public void setPid(long pid) { + this.pid = pid; + } + + public long getPid() { + return pid; + } + + private InetAddress inetAddress; + private IOFogNetworkInterfaceManager(){} + public static IOFogNetworkInterfaceManager getInstance() { + if(instance == null){ + synchronized (IOFogNetworkInterfaceManager.class){ + if(instance == null) { + instance = new IOFogNetworkInterfaceManager(); + } + } + } + return instance; + } + + public void updateIOFogNetworkInterface() throws SocketException, MalformedURLException { + LoggingService.logDebug(MODULE_NAME, "Updating IoFog NetworkInterface"); + try { + setCurrentIpAddress(IOFogNetworkInterface.getCurrentIpAddress()); + setNetworkInterface(IOFogNetworkInterface.getNetworkInterface()); + setInetAddress(IOFogNetworkInterface.getInetAddress()); + setHostName(IOFogNetworkInterface.getHostName()); + setPid(getFogPid()); + } catch (SocketException | MalformedURLException exp) { + LoggingService.logError(MODULE_NAME, "Unable to set IP address of the machine running ioFog", new AgentSystemException(exp.getMessage(), exp)); + throw exp; + } + } + + private final Runnable getIoFogNetworkInterface = () -> { + LoggingService.logDebug(MODULE_NAME, "Start getIoFogNetworkInterface"); + try { + updateIOFogNetworkInterface(); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME,"Error in updateIOFogNetworkInterface", new AgentSystemException(e.getMessage(), e)); + if (futureTask != null) + { + futureTask.cancel(true); + } + start(); + } + LoggingService.logDebug(MODULE_NAME, "Finished getIoFogNetworkInterface"); + }; + public void start() { + LoggingService.logInfo(MODULE_NAME, "Start IoFog NetworkInterface"); + try { + updateIOFogNetworkInterface(); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME,"Error in updating IOFogNetworkInterface", new AgentSystemException(e.getMessage(), e)); + start(); + } + futureTask = scheduler.scheduleAtFixedRate(getIoFogNetworkInterface, 0, 30, TimeUnit.MINUTES); + LoggingService.logInfo(MODULE_NAME, "Started IoFog NetworkInterface"); + } + + public long getFogPid(){ + String processName = ManagementFactory.getRuntimeMXBean().getName(); + return Long.parseLong(processName.split("@")[0]); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerManager.java new file mode 100644 index 00000000..a1039bb7 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerManager.java @@ -0,0 +1,271 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.exception.ConflictException; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Image; +import org.eclipse.iofog.microservice.*; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.logging.LoggingService; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import static org.eclipse.iofog.microservice.Microservice.deleteLock; + +/** + * provides methods to manage Docker containers + * + * @author saeid + */ +public class ContainerManager { + + private DockerUtil docker; + private final MicroserviceManager microserviceManager; + + private static final String MODULE_NAME = "Container Manager"; + + public ContainerManager() { + microserviceManager = MicroserviceManager.getInstance(); + } + + /** + * pulls {@link Image} from {@link Registry} and creates a new {@link Container} + * + * @throws Exception exception + */ + private void addContainer(Microservice microservice) throws Exception { + LoggingService.logInfo(MODULE_NAME, "Add container for microservice : " + microservice.getImageName()); + Optional containerOptional = docker.getContainer(microservice.getMicroserviceUuid()); + if (!containerOptional.isPresent()) { + createContainer(microservice); + } + } + + private Registry getRegistry(Microservice microservice) throws AgentSystemException { + LoggingService.logInfo(MODULE_NAME, "Get registry for microservice : " + microservice.getImageName()); + Registry registry; + registry = microserviceManager.getRegistry(microservice.getRegistryId()); + if (registry == null) { + throw new AgentSystemException(String.format("registry is not valid \"%d\"", microservice.getRegistryId()), null); + } + return registry; + } + + /** + * removes an existing {@link Container} and creates a new one + * + * @param withCleanUp if true then removes old image and volumes + * @throws Exception exception + */ + private void updateContainer(Microservice microservice, boolean withCleanUp) throws Exception { + LoggingService.logInfo(MODULE_NAME, "Start update container for microservice : " + microservice.getImageName()); + microservice.setUpdating(true); + removeContainerByMicroserviceUuid(microservice.getMicroserviceUuid(), withCleanUp); + createContainer(microservice); + microservice.setUpdating(false); + LoggingService.logDebug(MODULE_NAME, "Finished update container for microservice : " + microservice.getImageName()); + } + + private void createContainer(Microservice microservice) throws Exception { + createContainer(microservice, true); + } + + private void createContainer(Microservice microservice, boolean pullImage) throws Exception { + setMicroserviceStatus(microservice.getMicroserviceUuid(), MicroserviceState.PULLING); + Registry registry = getRegistry(microservice); + if (!registry.getUrl().equals("from_cache") && pullImage){ + try { + docker.pullImage(microservice.getImageName(), microservice.getMicroserviceUuid(), registry); + StatusReporter.setProcessManagerStatus().setMicroservicesStatePercentage(microservice.getMicroserviceUuid(), + Constants.PERCENTAGE_COMPLETION); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "unable to pull \"" + microservice.getImageName() + "\" from registry. trying local cache", + new AgentSystemException(e.getMessage(), e)); + createContainer(microservice, false); + LoggingService.logInfo(MODULE_NAME, "created \"" + microservice.getImageName() + "\" from local cache"); + return; + } + } + if (!pullImage && !docker.findLocalImage(microservice.getImageName())) { + throw new NotFoundException("Image not found in local cache"); + } + LoggingService.logInfo(MODULE_NAME, "Creating container \"" + microservice.getImageName() + "\""); + setMicroserviceStatus(microservice.getMicroserviceUuid(), MicroserviceState.STARTING); + String hostName = IOFogNetworkInterfaceManager.getInstance().getCurrentIpAddress(); + if(hostName.isEmpty()){ + hostName = retryHostName(hostName); + LoggingService.logInfo(MODULE_NAME, "hostname updated to \"" + hostName + "\""); + } + String id = docker.createContainer(microservice, hostName); + microservice.setContainerId(id); + microservice.setContainerIpAddress(docker.getContainerIpAddress(id)); + LoggingService.logInfo(MODULE_NAME, "container is created \"" + microservice.getImageName() + "\""); + startContainer(microservice); + microservice.setRebuild(false); + setMicroserviceStatus(microservice.getMicroserviceUuid(), MicroserviceState.RUNNING); + } + + private String retryHostName(String hostName) { + LoggingService.logDebug(MODULE_NAME, "Retrying to get hostname"); + int tries = 0; + while (hostName.equals("") && tries < 5){ + try { + TimeUnit.SECONDS.sleep(10); + IOFogNetworkInterfaceManager.getInstance().updateIOFogNetworkInterface(); + hostName = IOFogNetworkInterfaceManager.getInstance().getCurrentIpAddress(); + tries += 1; + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Hostname not found", new AgentSystemException(e.getMessage(), e)); + } + } + return hostName; + } + + /** + * starts a {@link Container} and sets appropriate status + */ + private void startContainer(Microservice microservice) { + LoggingService.logInfo(MODULE_NAME, String.format("Starting container \"%s\"", microservice.getImageName())); + try { + if (!docker.isContainerRunning(microservice.getContainerId())) { + docker.startContainer(microservice); + } + Optional statusOptional = docker.getContainerStatus(microservice.getContainerId()); + String status = statusOptional.orElse("unknown"); + LoggingService.logInfo(MODULE_NAME, String.format("starting %s, status: %s", microservice.getImageName(), status)); + microservice.setContainerIpAddress(docker.getContainerIpAddress(microservice.getContainerId())); + StatusReporter.setProcessManagerStatus().setMicroservicesStatusErrorMessage(microservice.getMicroserviceUuid(), ""); + } catch (Exception ex) { + LoggingService.logError(MODULE_NAME, + String.format("Container \"%s\" not found", microservice.getImageName()), + new AgentSystemException(ex.getMessage(), ex)); + } + LoggingService.logInfo(MODULE_NAME, String.format("Container started \"%s\"", microservice.getImageName())); + } + + /** + * stops a {@link Container} + * + * @param microserviceUuid id of the {@link Microservice} + */ + private void stopContainer(String microserviceUuid) { + LoggingService.logInfo(MODULE_NAME, "Stop container with microserviceuuid : " + microserviceUuid); + Optional containerOptional = docker.getContainer(microserviceUuid); + containerOptional.ifPresent(container -> { + setMicroserviceStatus(microserviceUuid, MicroserviceState.STOPPING); + LoggingService.logInfo(MODULE_NAME, String.format("Stopping container \"%s\"", container.getId())); + try { + docker.stopContainer(container.getId()); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, String.format("Error stopping container \"%s\"", container.getId()), + new AgentSystemException(e.getMessage(), e)); + } + }); + setMicroserviceStatus(microserviceUuid, MicroserviceState.STOPPED); + LoggingService.logInfo(MODULE_NAME, "Stopped container with microserviceuuid : " + microserviceUuid); + + } + + /** + * removes a {@link Container} by Microservice uuid + */ + private void removeContainerByMicroserviceUuid(String microserviceUuid, boolean withCleanUp) throws AgentSystemException { + LoggingService.logInfo(MODULE_NAME, "Start remove container with microserviceuuid : " + microserviceUuid); + synchronized (deleteLock) { + Optional containerOptional = docker.getContainer(microserviceUuid); + if (containerOptional.isPresent()) { + stopContainer(microserviceUuid); + Container container = containerOptional.get(); + setMicroserviceStatus(microserviceUuid, MicroserviceState.DELETING); + removeContainer(container.getId(), container.getImageId(), withCleanUp); + setMicroserviceStatus(microserviceUuid, MicroserviceState.DELETED); + } + } + LoggingService.logInfo(MODULE_NAME, "Finished remove container with microserviceuuid : " + microserviceUuid); + } + + private void removeContainer(String containerId, String imageId, boolean withCleanUp) throws AgentSystemException{ + LoggingService.logInfo(MODULE_NAME, String.format("Removing container \"%s\"", containerId)); + try { + docker.removeContainer(containerId, withCleanUp); + if (withCleanUp) { + try { + docker.removeImageById(imageId); + } catch (ConflictException ex) { + LoggingService.logError(MODULE_NAME, String.format("Image for container \"%s\" cannot be removed", containerId), + new AgentSystemException(ex.getMessage(), ex)); + } catch (Exception ex) { + LoggingService.logError(MODULE_NAME, String.format("Image for container \"%s\" cannot be removed", containerId), + new AgentSystemException(ex.getMessage(), ex)); + } + } + + LoggingService.logInfo(MODULE_NAME, String.format("Container \"%s\" removed", containerId)); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, String.format("Error removing container \"%s\"", containerId), + new AgentSystemException(e.getMessage(), e)); + throw new AgentSystemException(e.getMessage(), e); + } + } + + /** + * executes assigned task + * + * @param task - tasks to be executed + */ + public void execute(ContainerTask task) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start executes assigned task"); + docker = DockerUtil.getInstance(); + if (task != null) { + Optional microserviceOptional = microserviceManager.findLatestMicroserviceByUuid(task.getMicroserviceUuid()); + switch (task.getAction()) { + case ADD: + if (microserviceOptional.isPresent()) { + addContainer(microserviceOptional.get()); + } + break; + case UPDATE: + if (microserviceOptional.isPresent()) { + Microservice microservice = microserviceOptional.get(); + updateContainer(microserviceOptional.get(), false); + } + break; + case REMOVE: + removeContainerByMicroserviceUuid(task.getMicroserviceUuid(), false); + break; + case REMOVE_WITH_CLEAN_UP: + removeContainerByMicroserviceUuid(task.getMicroserviceUuid(), true); + break; + case STOP: + stopContainerByMicroserviceUuid(task.getMicroserviceUuid()); + break; + } + } else { + LoggingService.logError(MODULE_NAME, "Container Task cannot be null", + new AgentSystemException("Container Task container be null")); + } + LoggingService.logDebug(MODULE_NAME, "Finished executes assigned task"); + } + + private void stopContainerByMicroserviceUuid(String microserviceUuid) { + stopContainer(microserviceUuid); + } + + private void setMicroserviceStatus(String uuid, MicroserviceState state) { + StatusReporter.setProcessManagerStatus().setMicroservicesState(uuid, state); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerTask.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerTask.java new file mode 100644 index 00000000..6381b8a4 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerTask.java @@ -0,0 +1,80 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.model.Container; + +import static org.apache.commons.lang.StringUtils.EMPTY; + +/** + * represents tasks applied on a {@link Container} + * + * @author saeid + */ +public class ContainerTask { + + public enum Tasks { + ADD, + UPDATE, + REMOVE, + REMOVE_WITH_CLEAN_UP, + STOP + } + + private Tasks action; + private String microserviceUuid; + private int retries; + + public ContainerTask(Tasks action, String microserviceUuid) { + this.action = action; + this.microserviceUuid = microserviceUuid != null ? microserviceUuid : EMPTY; + this.retries = 0; + } + + public Tasks getAction() { + return action; + } + + public int getRetries() { + return retries; + } + + public String getMicroserviceUuid() { + return microserviceUuid; + } + + public void incrementRetries() { + this.retries++; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ContainerTask that = (ContainerTask) o; + + if (retries != that.retries) return false; + if (action != that.action) return false; + return microserviceUuid.equals(that.microserviceUuid); + } + + @Override + public int hashCode() { + int result = action.hashCode(); + result = 31 * result + microserviceUuid.hashCode(); + result = 31 * result + retries; + return result; + } +} + diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/DockerUtil.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/DockerUtil.java new file mode 100755 index 00000000..5817d6a5 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/DockerUtil.java @@ -0,0 +1,879 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.*; +import com.github.dockerjava.api.command.InspectContainerResponse.ContainerState; +import com.github.dockerjava.api.exception.ConflictException; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.exception.NotModifiedException; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.api.model.Ports.Binding; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientBuilder; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.command.EventsResultCallback; +import com.github.dockerjava.api.command.PullImageResultCallback; +import org.apache.commons.lang.SystemUtils; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.microservice.*; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.Json; +import javax.json.JsonObject; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.format.DateTimeFormatter; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.eclipse.iofog.microservice.MicroserviceState.fromText; +import static org.eclipse.iofog.utils.logging.LoggingService.logError; + +/** + * provides methods for Docker commands + * + * @author saeid + */ +public class DockerUtil { + private final static String MODULE_NAME = "Docker Util"; + + private static DockerUtil instance; + private DockerClient dockerClient; + + private DockerUtil() { + initDockerClient(); + } + + public static DockerUtil getInstance() { + if (instance == null) { + synchronized (DockerUtil.class) { + if (instance == null) + instance = new DockerUtil(); + } + } + return instance; + } + + /** + * initializes docker client + */ + private void initDockerClient() { + LoggingService.logInfo(MODULE_NAME , "Start Docker Client initialization"); + try { + DefaultDockerClientConfig.Builder configBuilder = DefaultDockerClientConfig.createDefaultConfigBuilder() + .withDockerHost(Configuration.getDockerUrl()); + if (!Configuration.getDockerApiVersion().isEmpty()) { + configBuilder = configBuilder.withApiVersion(Configuration.getDockerApiVersion()); + } + DockerClientConfig config = configBuilder.build(); + dockerClient = DockerClientBuilder.getInstance(config).build(); + } catch (Exception e) { + logError(MODULE_NAME,"Docker client initialization failed", new AgentUserException(e.getMessage(), e)); + throw e; + } + addDockerEventHandler(); + LoggingService.logInfo(MODULE_NAME , "Finished Docker Client initialization"); + } + + /** + * reinitialization of docker client + */ + public void reInitDockerClient() { + LoggingService.logInfo(MODULE_NAME , "Start Docker Client re-initialization"); + try { + if (null != dockerClient) { + dockerClient.close(); + } + } catch (IOException e) { + logError(MODULE_NAME, "Docker client closing failed", new AgentSystemException(e.getMessage(), e)); + } + initDockerClient(); + LoggingService.logInfo(MODULE_NAME , "Finished Docker Client re-initialization"); + + } + + + /** + * starts docker events handler + */ + private void addDockerEventHandler() { + LoggingService.logDebug(MODULE_NAME , "Starting docker events handler"); + dockerClient.eventsCmd().exec(new EventsResultCallback() { + @Override + public void onNext(Event item) { + switch (item.getType()) { + case CONTAINER: + case IMAGE: + StatusReporter.setProcessManagerStatus().getMicroserviceStatus(item.getId()).setStatus( + fromText(item.getStatus())); + } + } + }); + LoggingService.logDebug(MODULE_NAME, "docker events handler is started"); + } + + /** + * generates Docker authConfig + * based on Docker Remote API document + * + * @param registry - {@link Registry} + * @return base64 encoded string + */ + private String getAuth(Registry registry) { + LoggingService.logInfo(MODULE_NAME , "get auth"); + JsonObject auth = Json.createObjectBuilder() + .add("username", registry.getUserName()) + .add("password", registry.getPassword()) + .add("email", registry.getUserEmail()) + .add("auth", EMPTY) + .build(); + return Base64.getEncoder().encodeToString(auth.toString().getBytes(StandardCharsets.US_ASCII)); + } + + /** + * returns docker's default bridge name + * + * @return String default bridge name + */ + public String getDockerBridgeName() { + LoggingService.logDebug(MODULE_NAME , "get docker bridge name"); + List networks = dockerClient.listNetworksCmd().exec(); + + Network dockerBridge = networks + .stream() + .filter(network -> network.getOptions().getOrDefault("com.docker.network.bridge.default_bridge", "false").equals("true")) + .findFirst() + .orElse(null); + + if (dockerBridge == null) { + return null; + } + LoggingService.logDebug(MODULE_NAME , "Finished get docker bridge name"); + return dockerBridge.getOptions().get("com.docker.network.bridge.name"); + } + + /** + * starts a {@link Container} + * + * @param microservice {@link Microservice} + */ + public void startContainer(Microservice microservice) throws NotFoundException, NotModifiedException { +// long totalMemory = ((OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean()).getTotalPhysicalMemorySize(); +// long jvmMemory = Runtime.getRuntime().maxMemory(); +// long requiredMemory = (long) Math.min(totalMemory * 0.25, 256 * Constants.MiB); +// +// if (totalMemory - jvmMemory < requiredMemory) +// throw new Exception("Not enough memory to start the container"); + try { + LoggingService.logInfo(MODULE_NAME , "Start Container " + microservice.getImageName()); + dockerClient.startContainerCmd(microservice.getContainerId()).exec(); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, String.format("Exception occurred while starting container\"%s\" ", microservice.getImageName()), + new AgentSystemException(e.getMessage(), e)); + StatusReporter.setProcessManagerStatus().setMicroservicesStatusErrorMessage(microservice.getMicroserviceUuid(), e.getMessage()); + throw e; + } + } + + /** + * stops a {@link Container} + * + * @param id - id of {@link Container} + */ + public void stopContainer(String id) throws NotFoundException, NotModifiedException { + LoggingService.logInfo(MODULE_NAME , "Stop Container : " + id); + if (isContainerRunning(id)) { + dockerClient.stopContainerCmd(id).exec(); + } + } + + /** + * removes a {@link Container} + * + * @param id - id of {@link Container} + * @param withRemoveVolumes - true or false, Remove the volumes associated to the container + */ + public void removeContainer(String id, Boolean withRemoveVolumes) throws NotFoundException, NotModifiedException { + LoggingService.logInfo(MODULE_NAME , "Remove Container : " + id); + dockerClient.removeContainerCmd(id).withForce(true).withRemoveVolumes(withRemoveVolumes).exec(); + } + + /** + * gets IPv4 address of a {@link Container} + * + * @param id - id of {@link Container} + * @return ip address + */ + @SuppressWarnings("deprecation") + public String getContainerIpAddress(String id) throws AgentSystemException { + LoggingService.logDebug(MODULE_NAME , "Get Container IpAddress for container id : " + id); + try { + InspectContainerResponse inspect = dockerClient.inspectContainerCmd(id).exec(); + return inspect.getNetworkSettings().getIpAddress(); + } catch (NotModifiedException exp) { + logError(MODULE_NAME, "Error getting container ipAddress", + new AgentSystemException(exp.getMessage(), exp)); + throw new AgentSystemException(exp.getMessage(), exp); + }catch (NotFoundException exp) { + logError(MODULE_NAME, "Error getting container ipAddress", + new AgentSystemException(exp.getMessage(), exp)); + throw new AgentSystemException(exp.getMessage(), exp); + }catch (Exception exp) { + logError(MODULE_NAME, "Error getting container ipAddress", + new AgentSystemException(exp.getMessage(), exp)); + throw new AgentSystemException(exp.getMessage(), exp); + } + } + + public String getContainerName(Container container) { + return container.getNames()[0].substring(1); + } + + /** + * gets microsreviceUuid (basically just gets substring of container name) + * + * @param container Container object + * @return microsreviceUuid + */ + public String getContainerMicroserviceUuid(Container container) { + String containerName = getContainerName(container); + return containerName.startsWith(Constants.IOFOG_DOCKER_CONTAINER_NAME_PREFIX) + ? getContainerName(container).substring(Constants.IOFOG_DOCKER_CONTAINER_NAME_PREFIX.length()) + : containerName; + } + + /** + * gets container name by microserviceUuid + * + * @param microserviceUuid + * @return container name + */ + public static String getIoFogContainerName(String microserviceUuid) { + return Constants.IOFOG_DOCKER_CONTAINER_NAME_PREFIX + microserviceUuid; + } + + /** + * returns a {@link Container} if exists + * + * @param microserviceUuid - name of {@link Container} (id of {@link Microservice}) + * @return Optional + */ + public Optional getContainer(String microserviceUuid) { + List containers = getContainers(); + return containers.stream() + .filter(c -> getContainerMicroserviceUuid(c).equals(microserviceUuid)) + .findAny(); + } + + /** + * computes started time in milliseconds + * + * @param startedTime - string representing {@link Container} started time + * @return started time in milliseconds + */ + private long getStartedTime(String startedTime) { + LoggingService.logDebug(MODULE_NAME , "Get started time of container"); + int milli = startedTime.length() > 22 ? Integer.parseInt(startedTime.substring(20, 23)) : 0; + startedTime = startedTime.substring(0, 10) + " " + startedTime.substring(11, 19); + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + dateFormat.setTimeZone(TimeZone.getTimeZone("GMT")); + try { + Date local = dateFormat.parse(dateFormat.format(dateFormat.parse(startedTime))); + LoggingService.logDebug(MODULE_NAME , "Finished get started time of container"); + return local.getTime() + milli; + } catch (Exception e) { + logError(MODULE_NAME, "Error getting started time of container", + new AgentSystemException(e.getMessage(), e)); + return 0; + } + } + + /** + * gets {@link Container} status + * + * @param containerId - id of {@link Container} + * @return {@link MicroserviceStatus} + */ + public MicroserviceStatus getMicroserviceStatus(String containerId, String microserviceUuid) { + LoggingService.logDebug(MODULE_NAME , "Get microservice status for microservice uuid : "+ microserviceUuid); + InspectContainerResponse inspectInfo; + MicroserviceStatus result = new MicroserviceStatus(); + try { + inspectInfo = dockerClient.inspectContainerCmd(containerId).exec(); + ContainerState containerState = inspectInfo.getState(); + if (containerState != null) { + if (containerState.getStartedAt() != null) { + result.setStartTime(getStartedTime(containerState.getStartedAt())); + } + + MicroserviceState microserviceState = containerToMicroserviceState(containerState); + result.setStatus(isMicroserviceStuckInExitOrCreation(microserviceState, microserviceUuid) + ? MicroserviceState.STUCK_IN_RESTART + : microserviceState); + result.setContainerId(containerId); + result.setUsage(containerId); + MicroserviceStatus existingStatus = StatusReporter.setProcessManagerStatus().getMicroserviceStatus(microserviceUuid); + result.setPercentage(existingStatus.getPercentage()); + result.setErrorMessage(existingStatus.getErrorMessage()); + } + } catch (Exception e) { + LoggingService.logWarning(MODULE_NAME, "Error occurred while getting container status of microservice uuid" + microserviceUuid + + " error : " + ExceptionUtils.getFullStackTrace(e)); + } + LoggingService.logDebug(MODULE_NAME , "Finished get microservice status for microservice uuid : "+ microserviceUuid); + return result; + } + + private boolean isMicroserviceStuckInExitOrCreation(MicroserviceState microserviceState, String microServiceUuid){ + if (MicroserviceState.EXITING.equals(microserviceState)){ + return RestartStuckChecker.isStuck(microServiceUuid); + } else if (MicroserviceState.CREATED.equals(microserviceState)){ + return RestartStuckChecker.isStuckInContainerCreation(microServiceUuid); + } + return false; + } + + private MicroserviceState containerToMicroserviceState(ContainerState containerState) { + if (containerState == null) { + return MicroserviceState.UNKNOWN; + } + + switch (containerState.getStatus().toLowerCase()) { + case "running": + return MicroserviceState.RUNNING; + case "create": + return MicroserviceState.CREATING; + case "attach": + case "start": + return MicroserviceState.STARTING; + case "restart": + return MicroserviceState.RESTARTING; + case "kill": + case "die": + case "stop": + return MicroserviceState.STOPPING; + case "destroy": + return MicroserviceState.DELETING; + case "exited": + return MicroserviceState.EXITING; + case "created": + return MicroserviceState.CREATED; + } + + return MicroserviceState.UNKNOWN; + } + + public List getRunningContainers() { + LoggingService.logDebug(MODULE_NAME ,"Get Running list of Containers"); + return getContainers().stream() + .filter(container -> { + InspectContainerResponse inspectInfo = dockerClient.inspectContainerCmd(container.getId()).exec(); + ContainerState containerState = inspectInfo.getState(); + return containerToMicroserviceState(containerState) == MicroserviceState.RUNNING; + }) + .collect(Collectors.toList()); + } + + public List getRunningIofogContainers() { + LoggingService.logDebug(MODULE_NAME ,"Get Running list of ioFog Containers"); + return getRunningContainers().stream() + .filter(container -> getContainerName(container).startsWith(Constants.IOFOG_DOCKER_CONTAINER_NAME_PREFIX)) + .collect(Collectors.toList()); + } + + public Optional getContainerStats(String containerId) { + StatsCmd statsCmd = dockerClient.statsCmd(containerId); + CountDownLatch countDownLatch = new CountDownLatch(1); + StatsCallback stats = new StatsCallback(countDownLatch); + try (StatsCallback statscallback = statsCmd.exec(stats)) { + countDownLatch.await(1, TimeUnit.SECONDS); + } catch (InterruptedException | IOException e) { + LoggingService.logError(MODULE_NAME, "Error while getting Container Stats for container id: " + containerId, new AgentUserException(e.getMessage(), e)); + } + LoggingService.logDebug(MODULE_NAME ,"Finished get Container Stats for container id : " + containerId); + return Optional.ofNullable(stats.getStats()); + } + + /** + * return container last start epoch time + * + * @param id container id + * @return long epoch time + */ + public long getContainerStartedAt(String id) { + LoggingService.logDebug(MODULE_NAME ,"Get Container Started At for containerID : " + id); + InspectContainerResponse inspectInfo = dockerClient.inspectContainerCmd(id).exec(); + String startedAt = inspectInfo.getState().getStartedAt(); + return startedAt != null ? DateTimeFormatter.ISO_INSTANT.parse(startedAt, Instant::from).toEpochMilli() : Instant.now().toEpochMilli(); + } + + /** + * compares if microservice's and container's settings are equal + * + * @param containerId container id + * @param microservice microservice + * @return boolean + */ + public boolean areMicroserviceAndContainerEqual(String containerId, Microservice microservice) { + LoggingService.logDebug(MODULE_NAME ,"Are Microservice And Container Equal microservice : " + microservice.getImageName() + "container id : " + containerId); + InspectContainerResponse inspectInfo = dockerClient.inspectContainerCmd(containerId).exec(); + return isPortMappingEqual(inspectInfo, microservice) && isNetworkModeEqual(inspectInfo, microservice); + } + + /** + * compares if microservice has root host access, then container will have the NetworkMode 'host', + * otherwise container has to have ExtraHosts + * + * @param inspectInfo result of docker inspect command + * @param microservice microservice + * @return boolean + */ + private boolean isNetworkModeEqual(InspectContainerResponse inspectInfo, Microservice microservice) { + LoggingService.logDebug(MODULE_NAME ,"is NetworkMode Equal for microservice : " + microservice.getImageName()); + boolean isRootHostAccess = microservice.isRootHostAccess(); + HostConfig hostConfig = inspectInfo.getHostConfig(); + return (isRootHostAccess && "host".equals(hostConfig.getNetworkMode())) + || !isRootHostAccess && (hostConfig != null && hostConfig.getExtraHosts() != null && hostConfig.getExtraHosts().length > 0); + } + + /** + * compares if microservice port mapping is equal to container port mapping + * + * @param inspectInfo result of docker inspect command + * @param microservice microservice + * @return boolean true if port mappings are the same + */ + private boolean isPortMappingEqual(InspectContainerResponse inspectInfo, Microservice microservice) { + List microservicePorts = getMicroservicePorts(microservice); + Collections.sort(microservicePorts); + + List containerPorts = getContainerPorts(inspectInfo); + Collections.sort(containerPorts); + + boolean areEqual = microservicePorts.equals(containerPorts); + LoggingService.logDebug(MODULE_NAME ,"is PortMapping Equal for microservice " + microservice.getImageName() + " : " + areEqual); + + return areEqual; + } + + private List getMicroservicePorts(Microservice microservice) { + LoggingService.logDebug(MODULE_NAME ,"get list of Microservice Ports for microservice : " + microservice.getImageName()); + return microservice.getPortMappings() != null ? microservice.getPortMappings() : new ArrayList<>(); + } + + private List getContainerPorts(InspectContainerResponse inspectInfo) { + LoggingService.logDebug(MODULE_NAME ,"Get list of Container Ports"); + HostConfig hostConfig = inspectInfo.getHostConfig(); + Ports ports = hostConfig != null ? hostConfig.getPortBindings() : null; + return ports != null ? ports.getBindings().entrySet().stream() + .flatMap(entity -> { + ExposedPort exposedPort = entity.getKey(); + boolean isUdpProtocol = exposedPort.getProtocol().equals(InternetProtocol.UDP); + return Arrays.stream(entity.getValue()) + .map(Binding::getHostPortSpec) + .map(hostPort -> new PortMapping(Integer.valueOf(hostPort), exposedPort.getPort(), isUdpProtocol)); + }) + .collect(Collectors.toList()) : + new ArrayList<>(); + } + + public Optional getContainerStatus(String containerId) { + Optional result = Optional.empty(); + try { + LoggingService.logDebug(MODULE_NAME ,"Start get Container status for container id : " + containerId); + InspectContainerResponse inspectInfo = dockerClient.inspectContainerCmd(containerId).exec(); + ContainerState status = inspectInfo.getState(); + result = Optional.ofNullable(status.getStatus()); + } catch (Exception exp) { + logError(MODULE_NAME, "Error getting container status", new AgentSystemException(exp.getMessage(), exp)); + } + LoggingService.logDebug(MODULE_NAME ,"Finished get Container status for container id : " + containerId); + return result; + } + + public boolean isContainerRunning(String containerId) { + Optional status = getContainerStatus(containerId); + return status.isPresent() && status.get().equalsIgnoreCase(MicroserviceState.RUNNING.toString()); + } + + /** + * returns list of {@link Container} installed on Docker daemon + * + * @return list of {@link Container} + */ + public List getContainers() { + LoggingService.logDebug(MODULE_NAME ,"Get list of container running"); + return dockerClient.listContainersCmd().withShowAll(true).exec(); + } + + public void removeImageById(String imageId) throws NotFoundException, NotModifiedException { + LoggingService.logInfo(MODULE_NAME ,"Removing image by id imageID : " + imageId); + dockerClient.removeImageCmd(imageId).withForce(true).exec(); + LoggingService.logInfo(MODULE_NAME, String.format("image \"%s\" removed", imageId)); + } + + /** + * pulls {@link Image} from {@link Registry} + * + * @param imageName - imageName of {@link Microservice} + * @param registry - {@link Registry} where image is placed + */ + @SuppressWarnings("resource") + public void pullImage(String imageName, String microserviceUuid, Registry registry) throws AgentSystemException { + LoggingService.logInfo(MODULE_NAME, String.format("pull image name \"%s\" ", imageName)); + Map statuses = new HashMap(); + String tag = null, image; + String[] sp = imageName.split(":"); + image = sp[0]; + + if (sp.length > 1) { + tag = sp[1]; + } else { + tag = "latest"; + } + + try { + PullImageCmd req = + registry.getIsPublic() ? + dockerClient.pullImageCmd(image).withRegistry(registry.getUrl()) : + dockerClient.pullImageCmd(image).withAuthConfig( + new AuthConfig() + .withRegistryAddress(registry.getUrl()) + .withEmail(registry.getUserEmail()) + .withUsername(registry.getUserName()) + .withPassword(registry.getPassword()) + ); + req.withTag(tag); + PullImageResultCallback resultCallback = new PullImageResultCallback() { + @Override + public void onNext(PullResponseItem item) { + update(item, statuses); + double average = calculatePullPercentage(statuses); + StatusReporter.setProcessManagerStatus().setMicroservicesStatePercentage(microserviceUuid, (float)average); + super.onNext(item); + } + }; + resultCallback = req.exec(resultCallback); + resultCallback.awaitCompletion(); + + } catch (InterruptedException e) { + StatusReporter.setProcessManagerStatus().setMicroservicesStatusErrorMessage(microserviceUuid, e.getMessage()); + throw new AgentSystemException("Interrupted while pulling image : " + imageName, new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + StatusReporter.setProcessManagerStatus().setMicroservicesStatusErrorMessage(microserviceUuid, e.getMessage()); + LoggingService.logError(MODULE_NAME, "Image not found : " + imageName, new AgentSystemException(e.getMessage(), e)); + throw new AgentSystemException(e.getMessage(), e); + } + StatusReporter.setProcessManagerStatus().setMicroservicesStatusErrorMessage(microserviceUuid, ""); + LoggingService.logInfo(MODULE_NAME, String.format("Finished pull image \"%s\" ", imageName)); + } + + /** + * search for {@link Image} locally + * + * @param imageName - imageName of {@link Microservice} + */ + public boolean findLocalImage(String imageName) { + InspectImageCmd cmd = dockerClient.inspectImageCmd(imageName); + try { + InspectImageResponse res = cmd.exec(); + return true; + } catch (NotFoundException e) { + return false; + } + } + + /** + * creates {@link Container} + * + * @param microservice - {@link Microservice} + * @param host - host ip address + * @return id of created {@link Container} + */ + public String createContainer(Microservice microservice, String host) throws NotFoundException, NotModifiedException { + LoggingService.logInfo(MODULE_NAME ,String.format("Creating container \"%s\" ", microservice.getImageName())); + RestartPolicy restartPolicy = RestartPolicy.noRestart(); + + Ports portBindings = new Ports(); + List exposedPorts = new ArrayList<>(); + if (microservice.getPortMappings() != null && microservice.getPortMappings().size() != 0) + microservice.getPortMappings().forEach(mapping -> { + + ExposedPort internal = mapping.isUdp() ? + ExposedPort.udp(mapping.getInside()) : + ExposedPort.tcp(mapping.getInside()); + Binding external = Binding.bindPort(mapping.getOutside()); + portBindings.bind(internal, external); + exposedPorts.add(internal); + }); + List volumes = new ArrayList<>(); + List volumeMounts = new ArrayList<>(); + if (microservice.getVolumeMappings() != null && microservice.getVolumeMappings().size() != 0) { + microservice.getVolumeMappings().forEach(volumeMapping -> { + if (volumeMapping.getType() == VolumeMappingType.VOLUME) { + Volume volume = new Volume(volumeMapping.getContainerDestination()); + volumes.add(volume); + } + + boolean isReadOnly; + try { + isReadOnly = volumeMapping.getAccessMode().toLowerCase() == "ro"; + } catch (Exception e) { + isReadOnly = false; + LoggingService.logInfo(MODULE_NAME , String.format("volume access mode set to RW for image \"%s\" ", microservice.getImageName())); + } + + Mount mount = (new Mount()) + .withSource(volumeMapping.getHostDestination()) + .withType(volumeMapping.getType() == VolumeMappingType.BIND ? MountType.BIND : MountType.VOLUME) + .withTarget(volumeMapping.getContainerDestination()) + .withReadOnly(isReadOnly); + volumeMounts.add(mount); + }); + } + String[] hosts; + boolean hasIoFogExtraHost = false; + List extraHosts = microservice.getExtraHosts(); + if (extraHosts != null && extraHosts.size() > 0) { + if (extraHosts.stream().filter(str -> str.trim().contains("iofog")).count() != 0) { + hasIoFogExtraHost = true; + hosts = new String[extraHosts.size()]; + } else { + hosts = new String[extraHosts.size() + 1]; + } + hosts = extraHosts.toArray(hosts); + } else { + hosts = new String[1]; + } + if (!host.isEmpty() && !hasIoFogExtraHost) { + hosts[hosts.length - 1] = "iofog:" + host; + } + + Map containerLogConfig = new HashMap<>(); + int logFiles = 1; + if (microservice.getLogSize() > 2) + logFiles = (int) (microservice.getLogSize() / 2); + + containerLogConfig.put("max-file", String.valueOf(logFiles)); + containerLogConfig.put("max-size", "2m"); + LogConfig containerLog = new LogConfig(LogConfig.LoggingType.DEFAULT, containerLogConfig); + + List envVars = new ArrayList<>(Arrays.asList("SELFNAME=" + microservice.getMicroserviceUuid())); + if (microservice.getEnvVars() != null) { + envVars.addAll(microservice + .getEnvVars() + .stream() + .map(env -> env.getKey() + "=" + env.getValue()) + .collect(Collectors.toList())); + } + if (envVars.stream().filter(str -> str.trim().contains("TZ")).count() == 0){ + envVars.add("TZ=" + Configuration.getTimeZone()); + } + Map labels = new HashMap<>(); + labels.put("iofog-uuid", Configuration.getIofogUuid()); + HostConfig hostConfig = HostConfig.newHostConfig(); + hostConfig.withPortBindings(portBindings); + hostConfig.withLogConfig(containerLog); + hostConfig.withCpusetCpus("0"); + hostConfig.withRestartPolicy(restartPolicy); + + CreateContainerCmd cmd = dockerClient.createContainerCmd(microservice.getImageName()) + .withExposedPorts(exposedPorts.toArray(new ExposedPort[0])) + .withEnv(envVars) + .withName(Constants.IOFOG_DOCKER_CONTAINER_NAME_PREFIX + microservice.getMicroserviceUuid()) + .withLabels(labels); + + if (volumes.size() > 0) { + cmd = cmd.withVolumes(volumes); + } + + if (volumeMounts.size() > 0) { + hostConfig.withMounts(volumeMounts); + } + + if (SystemUtils.IS_OS_WINDOWS) { + if(microservice.isRootHostAccess()){ + hostConfig.withNetworkMode("host").withExtraHosts(hosts).withPrivileged(true); + } else if(hosts[hosts.length - 1] != null) { + hostConfig.withExtraHosts(hosts).withPrivileged(true); + } + } else if (SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC) { + if(microservice.isRootHostAccess()){ + hostConfig.withNetworkMode("host").withPrivileged(true); + } else if(hosts[hosts.length - 1] != null) { + hostConfig.withExtraHosts(hosts).withPrivileged(true); + } + } + + if (microservice.getArgs() != null && microservice.getArgs().size() > 0) { + cmd = cmd.withCmd(microservice.getArgs()); + } + cmd = cmd.withHostConfig(hostConfig); + CreateContainerResponse resp; + try { + resp = cmd.exec(); + LoggingService.logInfo(MODULE_NAME ,String.format("Container created \"%s\" ", microservice.getImageName())); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, String.format("Exception occurred while creating container\"%s\" ", + microservice.getImageName()), new AgentSystemException(e.getMessage(), e)); + StatusReporter.setProcessManagerStatus().setMicroservicesStatusErrorMessage(microservice.getMicroserviceUuid(), e.getMessage()); + throw e; + } + StatusReporter.setProcessManagerStatus().setMicroservicesStatusErrorMessage(microservice.getMicroserviceUuid(), ""); + return resp.getId(); + } + + /** + * docker prune a {@link Image} + */ + public PruneResponse dockerPrune() throws NotModifiedException { + LoggingService.logInfo(MODULE_NAME , "docker image prune"); + return dockerClient.pruneCmd(PruneType.IMAGES).withDangling(false).exec(); + } + /** + * Updates the item status of docker pull Layer + * + * @param item + * @param statuses + */ + public void update(PullResponseItem item, Map statuses) { + if (item != null && item.getId() != null) { + statuses.put(item.getId(), createStatusItem(item, statuses.get(item.getId()))); + } + } + + /** + * Creates the status of each docker pull layer + * + * @param item + * @param previousStatus + * @return + */ + private ItemStatus createStatusItem(PullResponseItem item, ItemStatus previousStatus) { + ResponseItem.ProgressDetail progressDetail = item.getProgressDetail(); + if (previousStatus != null) { + if (progressDetail != null && progressDetail.getTotal() != null && progressDetail.getTotal() > 0) { + int currentPct = computePercentage(previousStatus, progressDetail); + previousStatus.setPercentage(currentPct); + previousStatus.setPullStatus(statusNotNull(item.getStatus()) ? item.getStatus() : ""); + return previousStatus; + } + previousStatus.setPullStatus(statusNotNull(item.getStatus()) ? item.getStatus() : ""); + return previousStatus; + } + ItemStatus status = new ItemStatus(); + status.setId(item.getId()); + status.setPullStatus(statusNotNull(item.getStatus()) ? item.getStatus() : ""); + return status; + + } + + /** + * Compute percentage at each layer of docker pull + * + * @param previousStatus + * @param progressDetail + * @return + */ + private int computePercentage(ItemStatus previousStatus, ResponseItem.ProgressDetail progressDetail) { + int currentPct = (int) ( + ((float) progressDetail.getCurrent()) / + ((float) progressDetail.getTotal()) + * 100); + currentPct = (previousStatus != null && previousStatus.getPercentage() > currentPct) ? + previousStatus.getPercentage() + : + currentPct; + return currentPct; + } + + private boolean statusNotNull(String status) { + return status != null && !status.trim().equals("null"); + } + + /** + * Calculate the average percentage pull completion + * + * @param statuses + * @return + */ + private double calculatePullPercentage(Map statuses) { + List stateList = new ArrayList(statuses.values()); + double total = 0; + for (ItemStatus status : stateList) { + total = total + status.getPercentage(); + } + return (total / (stateList.size() > 1 ? (stateList.size() - 1) : stateList.size())); + + } + + /** + * returns list of {@link Image} installed on Docker daemon + * + * @return list of {@link Image} + */ + public List getImages() { + LoggingService.logDebug(MODULE_NAME ,"Get list of images already pulled"); + return dockerClient.listImagesCmd().withShowAll(true).exec(); + } + + public List getRunningNonIofogContainers() { + LoggingService.logDebug(MODULE_NAME ,"Get Running list of non ioFog Containers"); + return getRunningContainers().stream() + .filter(container -> !getContainerName(container).startsWith(Constants.IOFOG_DOCKER_CONTAINER_NAME_PREFIX)) + .collect(Collectors.toList()); + } + + class ItemStatus { + private String id; + private int percentage; + private String pullStatus; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public int getPercentage() { + return percentage; + } + + public void setPercentage(int percentage) { + this.percentage = percentage; + } + + public String getPullStatus() { + return pullStatus; + } + + public void setPullStatus(String pullStatus) { + this.pullStatus = pullStatus; + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManager.java new file mode 100644 index 00000000..fd944d60 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManager.java @@ -0,0 +1,475 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.exception.ConflictException; +import com.github.dockerjava.api.model.Container; +import org.eclipse.iofog.IOFogModule; +import org.eclipse.iofog.diagnostics.strace.StraceDiagnosticManager; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.microservice.MicroserviceStatus; +import org.eclipse.iofog.microservice.MicroserviceState; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.Constants.ModulesStatus; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.lang.String.format; +import static org.eclipse.iofog.microservice.Microservice.deleteLock; +import static org.eclipse.iofog.process_manager.ContainerTask.Tasks.*; +import static org.eclipse.iofog.utils.Constants.ControllerStatus.OK; +import static org.eclipse.iofog.utils.Constants.PROCESS_MANAGER; + +/** + * Process Manager module + * + * @author saeid + */ +public class ProcessManager implements IOFogModule { + + private static final String MODULE_NAME = "Process Manager"; + private MicroserviceManager microserviceManager; + private final Queue tasks = new LinkedList<>(); + + private DockerUtil docker; + private ContainerManager containerManager; + private static ProcessManager instance; + + private ProcessManager() { + } + + @Override + public int getModuleIndex() { + return PROCESS_MANAGER; + } + + @Override + public String getModuleName() { + return MODULE_NAME; + } + + public static ProcessManager getInstance() { + if (instance == null) { + synchronized (ProcessManager.class) { + if (instance == null) + instance = new ProcessManager(); + } + } + return instance; + } + + /** + * updates registries list according to the last changes + */ + private void updateRegistriesStatus() { + logDebug("updates registries list according to the last changes"); + StatusReporter.getProcessManagerStatus().getRegistriesStatus().entrySet() + .removeIf(entry -> (microserviceManager.getRegistry(entry.getKey()) == null)); + } + + /** + * updates {@link ProcessManager} to the last changes + * Field Agent call this method when any changes applied + */ + public void update() { + updateRegistriesStatus(); + } + + /** + * monitor containers + * removes {@link Container} if does not exists in list of {@link Microservice} + * restarts {@link Container} if it has been stopped + * updates {@link Container} if restarting failed! + */ + private final Runnable containersMonitor = () -> { + while (true) { + try { + Thread.sleep(Configuration.getMonitorContainersStatusFreqSeconds() * 1000); + } catch (InterruptedException e) { + logError("Error while sleeping thread", + new AgentSystemException(e.getMessage(), e)); + } + logDebug("Start Monitoring containers"); + + try { + + handleLatestMicroservices(); + deleteRemainingMicroservices(); + updateRunningMicroservicesCount(); + } catch (Exception ex) { + logError("Error monitoring container", new AgentSystemException(ex.getMessage(), ex)); + } + updateCurrentMicroservices(); + logDebug("Finished Monitoring containers"); + } + }; + + public void updateMicroserviceStatus() { + microserviceManager.getCurrentMicroservices().stream() + .filter(microservice -> !microservice.isStuckInRestart()) + .forEach(microservice -> { + Optional containerOptional = docker.getContainer(microservice.getMicroserviceUuid()); + if (containerOptional.isPresent()) { + String containerId = containerOptional.get().getId(); + MicroserviceStatus status = docker.getMicroserviceStatus(containerId, microservice.getMicroserviceUuid()); + if (!status.getStatus().equals(StatusReporter.getProcessManagerStatus().getMicroserviceStatus(microservice.getMicroserviceUuid()).getStatus())) { + StatusReporter.setProcessManagerStatus().setMicroservicesState(microservice.getMicroserviceUuid(), status.getStatus()); + if (status.getStatus().equals(MicroserviceState.STUCK_IN_RESTART)) { + microservice.setStuckInRestart(true); + } + logInfo(String.format("Updated microservice \"%s\" with status \"%s\" : ", microservice.getImageName(), status.getStatus().name())); + } + } else if (!containerOptional.isPresent() && MicroserviceState.RUNNING.equals(StatusReporter.getProcessManagerStatus().getMicroserviceStatus(microservice.getMicroserviceUuid()).getStatus())) { + MicroserviceStatus status = docker.getMicroserviceStatus(microservice.getContainerId(), microservice.getMicroserviceUuid()); + StatusReporter.setProcessManagerStatus().setMicroservicesStatus(microservice.getMicroserviceUuid(), status); + logInfo(String.format("Updated microservice \"%s\" with status \"%s\" : ", microservice.getImageName(), status.getStatus().name())); + } + }); + } + + private void addMicroservice(Microservice microservice) { + StatusReporter.setProcessManagerStatus().setMicroservicesState(microservice.getMicroserviceUuid(), MicroserviceState.QUEUED); + addTask(new ContainerTask(ADD, microservice.getMicroserviceUuid())); + } + + /** + * Deletes microservices which have field "delete" set to true + * @param microservice Microservice object + */ + private void deleteMicroservice(Microservice microservice) { + logDebug("Start Delete of microservices"); + disableMicroserviceFeaturesBeforeRemoval(microservice.getMicroserviceUuid()); + if (microservice.isDeleteWithCleanup()) { + addTask(new ContainerTask(REMOVE_WITH_CLEAN_UP, microservice.getMicroserviceUuid())); + } else { + addTask(new ContainerTask(REMOVE, microservice.getMicroserviceUuid())); + } + logDebug("Finished Delete of microservices"); + } + + private void disableMicroserviceFeaturesBeforeRemoval(String microserviceUuid) { + StraceDiagnosticManager.getInstance().disableMicroserviceStraceDiagnostics(microserviceUuid); + } + + private void updateMicroservice(Container container, Microservice microservice) { + logDebug("Start update microservices"); + microservice.setContainerId(container.getId()); + try { + microservice.setContainerIpAddress(docker.getContainerIpAddress(container.getId())); + } catch (Exception e) { + microservice.setContainerIpAddress("0.0.0.0"); + logError("Can't get IP address for microservice with i=" + microservice.getMicroserviceUuid() + " " + e.getMessage(), + new AgentSystemException(e.getMessage(), e)); + } + if (shouldContainerBeUpdated(microservice, container, docker.getMicroserviceStatus(container.getId(), microservice.getMicroserviceUuid()))) { + StatusReporter.setProcessManagerStatus().setMicroservicesState(microservice.getMicroserviceUuid(), MicroserviceState.UPDATING); + addTask(new ContainerTask(UPDATE, microservice.getMicroserviceUuid())); + } + logDebug("Finished update microservices"); + } + + private void handleLatestMicroservices() { + logDebug("Start handle latest microservices"); + microserviceManager.getLatestMicroservices().stream() + .filter(microservice -> !microservice.isUpdating() && !microservice.isStuckInRestart()) + .forEach(microservice -> { + Optional containerOptional = docker.getContainer(microservice.getMicroserviceUuid()); + + if (!containerOptional.isPresent() && !microservice.isDelete()) { + MicroserviceStatus status = StatusReporter.getProcessManagerStatus().getMicroserviceStatus(microservice.getMicroserviceUuid()); + if (status != null && MicroserviceState.UNKNOWN.equals(status.getStatus())) { + StatusReporter.setProcessManagerStatus().setMicroservicesState(microservice.getMicroserviceUuid(), MicroserviceState.QUEUED); + addMicroservice(microservice); + } + } else if (containerOptional.isPresent() && microservice.isDelete()) { + StatusReporter.setProcessManagerStatus().setMicroservicesState(microservice.getMicroserviceUuid(), MicroserviceState.MARKED_FOR_DELETION); + deleteMicroservice(microservice); + } else if (containerOptional.isPresent() && !microservice.isDelete()) { + String containerId = containerOptional.get().getId(); + MicroserviceStatus status = docker.getMicroserviceStatus(containerId, microservice.getMicroserviceUuid()); + StatusReporter.setProcessManagerStatus().setMicroservicesStatus(microservice.getMicroserviceUuid(), status); + updateMicroservice(containerOptional.get(), microservice); + } + }); + logDebug("Finished handle latest microservices"); + } + + public void deleteRemainingMicroservices() { + LoggingService.logDebug(MODULE_NAME ,"Start delete Remaining Microservices"); + Set latestMicroserviceUuids = microserviceManager.getLatestMicroservices().stream() + .map(Microservice::getMicroserviceUuid) + .collect(Collectors.toSet()); + Set currentMicroserviceUuids = microserviceManager.getCurrentMicroservices().stream() + .map(Microservice::getMicroserviceUuid) + .collect(Collectors.toSet()); + List runningContainers; + synchronized (deleteLock) { + runningContainers = docker.getRunningContainers(); + } + + Set runningMicroserviceUuids = runningContainers + .stream() + .map(docker::getContainerMicroserviceUuid) + .collect(Collectors.toSet()); + + Map> runningContainersLabels = runningContainers + .stream() + .collect(Collectors.toMap(docker::getContainerName, c -> c.getLabels())); + + Set allMicroserviceUuids = Stream.concat( + Stream.concat( + latestMicroserviceUuids.stream(), + currentMicroserviceUuids.stream() + ), + runningMicroserviceUuids.stream() + ) + .collect(Collectors.toSet()); + + Set oldAgentMicroserviceUuids = new HashSet<>(); + Set unknownMicroserviceUuids = new HashSet<>(); + + for (String uuid : allMicroserviceUuids) { + boolean isCurrentMicroserviceUuid = currentMicroserviceUuids.contains(uuid); + boolean isLatestMicroserviceUuid = latestMicroserviceUuids.contains(uuid); + + if (isCurrentMicroserviceUuid && !isLatestMicroserviceUuid) { + oldAgentMicroserviceUuids.add(uuid); + } else if (!isCurrentMicroserviceUuid && !isLatestMicroserviceUuid) { + String containerName = DockerUtil.getIoFogContainerName(uuid); + Map labels = runningContainersLabels.get(containerName); + if ((labels != null && labels.get("iofog-uuid") != "") || Configuration.isWatchdogEnabled()) { + unknownMicroserviceUuids.add(uuid); + } + } + } + + deleteOldAgentContainers(oldAgentMicroserviceUuids); + deleteUnknownContainers(unknownMicroserviceUuids); + LoggingService.logDebug(MODULE_NAME ,"Finished delete Remaining Microservices"); + } + + /** + * Stop running microservices when agent deprovision. + * Stop and delete microservices when agents stops + * @param withCleanUp + */ + public void stopRunningMicroservices(boolean withCleanUp, String iofogUuid) { + LoggingService.logInfo(MODULE_NAME ,"Stop running Microservices"); + if (withCleanUp) { + List allContainers; + synchronized (deleteLock) { + allContainers = docker.getContainers(); + } + Set allMicroserviceUuids = allContainers + .stream() + .map(docker::getContainerMicroserviceUuid) + .collect(Collectors.toSet()); + Map> allContainersLabels = allContainers + .stream() + .collect(Collectors.toMap(docker::getContainerName, c -> c.getLabels())); + + allMicroserviceUuids.forEach(uuid -> { + String containerName = DockerUtil.getIoFogContainerName(uuid); + Map labels = allContainersLabels.get(containerName); + if ((labels != null && iofogUuid.equals(labels.get("iofog-uuid"))) || Configuration.isWatchdogEnabled()) { + disableMicroserviceFeaturesBeforeRemoval(uuid); + removeContainerByMicroserviceUuid(uuid, withCleanUp); + } + }); + } else { + List runningContainers; + synchronized (deleteLock) { + runningContainers = docker.getRunningContainers(); + } + Set allRunningMicroserviceUuids = runningContainers + .stream() + .map(docker::getContainerMicroserviceUuid) + .collect(Collectors.toSet()); + Map> runningContainersLabels = runningContainers + .stream() + .collect(Collectors.toMap(docker::getContainerName, c -> c.getLabels())); + + Set runningMicroserviceUuids = new HashSet<>(); + + allRunningMicroserviceUuids.forEach(uuid -> { + String containerName = DockerUtil.getIoFogContainerName(uuid); + Map labels = runningContainersLabels.get(containerName); + if ((labels != null && iofogUuid.equals(labels.get("iofog-uuid"))) || Configuration.isWatchdogEnabled()) { + runningMicroserviceUuids.add(uuid); + } + }); + stopRunningAgentContainers(runningMicroserviceUuids); + } + LoggingService.logInfo(MODULE_NAME ,"Stopped running Microservices"); + } + + /** + * removes a {@link Container} by Microservice uuid + */ + private void removeContainerByMicroserviceUuid(String microserviceUuid, boolean withCleanUp) { + LoggingService.logDebug(MODULE_NAME, "Start remove container by microserviceuuid : " + microserviceUuid); + synchronized (deleteLock) { + Optional containerOptional = docker.getContainer(microserviceUuid); + if (containerOptional.isPresent()) { + Container container = containerOptional.get(); + try { + docker.stopContainer(container.getId()); + docker.removeContainer(container.getId(), withCleanUp); + } catch (Exception ex) { + LoggingService.logError(MODULE_NAME, String.format("Image for container \"%s\" cannot be removed", container.getId()), + new AgentSystemException(String.format("Image for container \"%s\" cannot be removed", container.getId()), ex)); + } + } + } + LoggingService.logInfo(MODULE_NAME, "Finished remove container by microserviceuuid : " + microserviceUuid); + } + + private void stopRunningAgentContainers(Set runningMicroserviceUuids) { + logDebug("Stop running containers : " + runningMicroserviceUuids.size()); + runningMicroserviceUuids.forEach(name -> { + disableMicroserviceFeaturesBeforeRemoval(name); + addTask(new ContainerTask(STOP, name)); + }); + } + + private void deleteOldAgentContainers(Set runningMicroserviceUuids) { + logDebug("Delete running containers : " + runningMicroserviceUuids.size()); + runningMicroserviceUuids.forEach(name -> { + disableMicroserviceFeaturesBeforeRemoval(name); + addTask(new ContainerTask(REMOVE, name)); + }); + } + + private void deleteUnknownContainers(Set unknownContainerNames) { + logDebug("Delete unknown containers : " + unknownContainerNames.size()); + unknownContainerNames.forEach(name -> addTask(new ContainerTask(REMOVE, name))); + } + + private void updateRunningMicroservicesCount() { + logDebug("Update running microservice count : "); + synchronized (deleteLock) { + StatusReporter.setProcessManagerStatus().setRunningMicroservicesCount(docker.getRunningIofogContainers().size()); + } + } + + private void updateCurrentMicroservices() { + logDebug("Start update current Microservices"); + List currentMicroservices = microserviceManager.getLatestMicroservices().stream() + .filter(microservice -> !microservice.isDelete()) + .collect(Collectors.toList()); + microserviceManager.setCurrentMicroservices(currentMicroservices); + logDebug("Finished update current Microservices"); + } + private boolean shouldContainerBeUpdated(Microservice microservice, Container container, MicroserviceStatus status) { + logDebug("Start should Container Be Updated"); + boolean isNotRunning = !MicroserviceState.RUNNING.equals(status.getStatus()); + boolean areNotEqual = !docker.areMicroserviceAndContainerEqual(container.getId(), microservice); + boolean isRebuild = microservice.isRebuild(); + boolean isUpdated = isNotRunning || areNotEqual || isRebuild; + logDebug("Finished should Container Be Updated : " + isUpdated); + return isUpdated; + } + + /** + * add a new {@link ContainerTask} + * + * @param task - {@link ContainerTask} to be added + */ + private void addTask(ContainerTask task) { + synchronized (tasks) { + if (!tasks.contains(task)) { + logInfo("NEW TASK ADDED"); + tasks.offer(task); + tasks.notifyAll(); + } + } + } + + /** + * checks and runs new {@link ContainerTask} + */ + private final Runnable checkTasks = () -> { + while (true) { + logDebug("Start check tasks"); + ContainerTask newTask; + + synchronized (tasks) { + newTask = tasks.poll(); + if (newTask == null) { + logInfo("WAITING FOR NEW TASK"); + try { + tasks.wait(); + } catch (InterruptedException e) { + logError(e.getMessage(), e); + } + logInfo("NEW TASK RECEIVED"); + newTask = tasks.poll(); + } + } + try { + containerManager.execute(newTask); + logInfo(newTask.getAction() + " action completed for container " + newTask.getMicroserviceUuid()); + } catch (Exception e) { + logError(newTask.getAction() + " was not successful. container name : " + newTask.getMicroserviceUuid(), + new AgentSystemException(e.getMessage(), e)); + + retryTask(newTask); + } + logDebug("Finished check tasks"); + } + }; + + private void retryTask(ContainerTask task) { + logDebug("Start retry tasks"); + if (StatusReporter.getFieldAgentStatus().getControllerStatus().equals(OK) || task.getAction().equals(REMOVE)) { + if (task.getRetries() < 5) { + task.incrementRetries(); + addTask(task); + } else { + StatusReporter.setProcessManagerStatus().setMicroservicesState(task.getMicroserviceUuid(), MicroserviceState.FAILED); + Exception err = new Exception(format("Container %s %s operation failed after 5 attempts", task.getMicroserviceUuid(), task.getAction().toString())); + logError(err.getMessage(), err); + } + } + logDebug("Finished retry tasks"); + } + + /** + * {@link Configuration} calls this method when any changes applied + * reconnects to Docker daemon using new docker_url + */ + public void instanceConfigUpdated() { + docker.reInitDockerClient(); + } + + /** + * starts Process Manager module + */ + public void start() { + docker = DockerUtil.getInstance(); + microserviceManager = MicroserviceManager.getInstance(); + containerManager = new ContainerManager(); + + new Thread(containersMonitor, Constants.PROCESS_MANAGER_CONTAINERS_MONITOR).start(); + new Thread(checkTasks, Constants.PROCESS_MANAGER_CHECK_TASKS).start(); + + StatusReporter.setSupervisorStatus().setModuleStatus(PROCESS_MANAGER, ModulesStatus.RUNNING); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManagerStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManagerStatus.java new file mode 100644 index 00000000..f9ce7b83 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManagerStatus.java @@ -0,0 +1,160 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import org.eclipse.iofog.microservice.*; +import org.eclipse.iofog.utils.Constants.LinkStatus; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObjectBuilder; +import java.text.NumberFormat; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * represents Process Manager status + * + * @author saeid + */ +public class ProcessManagerStatus { + private int runningMicroservicesCount; + private final Map microservicesStatus; + private final Map registriesStatus; + + public ProcessManagerStatus() { + microservicesStatus = new HashMap<>(); + registriesStatus = new HashMap<>(); + runningMicroservicesCount = 0; + } + + /** + * returns {@link Microservice} status in json format + * + * @return string in json format + */ + public String getJsonMicroservicesStatus() { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + + NumberFormat nf = NumberFormat.getInstance(Locale.US); + nf.setMaximumFractionDigits(2); + + microservicesStatus.forEach((key, status) -> { + JsonObjectBuilder objectBuilder = Json.createObjectBuilder() + .add("id", key != null ? key : "UNKNOWN") + .add("status", status != null ? + (status.getStatus() != null ? status.getStatus().toString() : "UNKNOWN") : + "UNKNOWN") + .add("percentage", status != null ? status.getPercentage() : 0); + if (status != null && status.getContainerId() != null) { + objectBuilder + .add("containerId", status.getContainerId() != null ? + status.getContainerId() : + "UNKNOWN") + .add("startTime", status.getStartTime()) + .add("operatingDuration", status.getOperatingDuration()) + .add("cpuUsage", nf.format(status.getCpuUsage())) + .add("memoryUsage", String.format("%d", status.getMemoryUsage())); + } + if (status != null && status.getErrorMessage() != null) { + objectBuilder.add("errorMessage", status.getErrorMessage()); + } + arrayBuilder.add(objectBuilder); + }); + return arrayBuilder.build().toString(); + } + + /** + * returns {@link Registry} status in json format + * + * @return string in json format + */ + public String getJsonRegistriesStatus() { + JsonArrayBuilder arrayBuilder = Json.createArrayBuilder(); + registriesStatus.forEach((key, value) -> { + JsonObjectBuilder objectBuilder = Json.createObjectBuilder() + .add("id", key) + .add("linkStatus", value.toString()); + arrayBuilder.add(objectBuilder); + + }); + return arrayBuilder.build().toString(); + } + + public int getRunningMicroservicesCount() { + return runningMicroservicesCount; + } + + public ProcessManagerStatus setRunningMicroservicesCount(int count) { + this.runningMicroservicesCount = count; + return this; + } + + public ProcessManagerStatus setMicroservicesStatus(String microserviceUuid, MicroserviceStatus status) { + synchronized (microservicesStatus) { + this.microservicesStatus.put(microserviceUuid, status); + } + return this; + } + + public ProcessManagerStatus setMicroservicesState(String microserviceUuid, MicroserviceState state) { + synchronized (microservicesStatus) { + MicroserviceStatus status = microservicesStatus.getOrDefault(microserviceUuid, new MicroserviceStatus()); + status.setStatus(state); + this.microservicesStatus.put(microserviceUuid, status); + } + return this; + } + + public MicroserviceStatus getMicroserviceStatus(String microserviceUuid) { + synchronized (microservicesStatus) { + if (!this.microservicesStatus.containsKey(microserviceUuid)) + this.microservicesStatus.put(microserviceUuid, new MicroserviceStatus()); + } + return microservicesStatus.get(microserviceUuid); + } + + public void removeNotRunningMicroserviceStatus() { + synchronized (microservicesStatus) { + microservicesStatus.entrySet().removeIf(entry -> entry.getValue().getStatus() == MicroserviceState.UNKNOWN || + entry.getValue().getStatus() == MicroserviceState.DELETED); + } + } + + public int getRegistriesCount() { + return MicroserviceManager.getInstance().getRegistries().size(); + } + + public Map getRegistriesStatus() { + return registriesStatus; + } + + public ProcessManagerStatus setMicroservicesStatePercentage(String microserviceUuid, float percentage) { + synchronized (microservicesStatus) { + MicroserviceStatus status = microservicesStatus.getOrDefault(microserviceUuid, new MicroserviceStatus()); + status.setPercentage(percentage); + this.microservicesStatus.put(microserviceUuid, status); + } + return this; + } + + public ProcessManagerStatus setMicroservicesStatusErrorMessage(String microserviceUuid, String message) { + synchronized (microservicesStatus) { + MicroserviceStatus status = microservicesStatus.getOrDefault(microserviceUuid, new MicroserviceStatus()); + status.setErrorMessage(message); + this.microservicesStatus.put(microserviceUuid, status); + } + return this; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/RestartStuckChecker.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/RestartStuckChecker.java new file mode 100644 index 00000000..b3853364 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/RestartStuckChecker.java @@ -0,0 +1,51 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.process_manager; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author elukashick + */ +public class RestartStuckChecker { + + private static final Map> restarts = new HashMap<>(); + private static final Map> containerCreation = new HashMap<>(); + private static final long INTERVAL_IN_MINUTES = 10; + private static final int ABNORMAL_NUMBER_OF_RESTARTS = 10; + + public static boolean isStuck(String microserviceUuid) { + List datesOfRestart = restarts.computeIfAbsent(microserviceUuid, k -> new ArrayList<>()); + LocalDateTime now = LocalDateTime.now(); + + datesOfRestart.removeIf(dateOfRestart -> dateOfRestart.isBefore(now.minusMinutes(INTERVAL_IN_MINUTES))); + datesOfRestart.add(now); + + return datesOfRestart.size() >= ABNORMAL_NUMBER_OF_RESTARTS; + } + + public static boolean isStuckInContainerCreation(String microserviceUuid) { + List datesOfCreation = containerCreation.computeIfAbsent(microserviceUuid, k -> new ArrayList<>()); + LocalDateTime now = LocalDateTime.now(); + + datesOfCreation.removeIf(dateOfRestart -> dateOfRestart.isBefore(now.minusMinutes(INTERVAL_IN_MINUTES))); + datesOfCreation.add(now); + + return datesOfCreation.size() >= ABNORMAL_NUMBER_OF_RESTARTS; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/StatsCallback.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/StatsCallback.java new file mode 100644 index 00000000..01e70614 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/StatsCallback.java @@ -0,0 +1,55 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.model.Statistics; +import com.github.dockerjava.core.async.ResultCallbackTemplate; + +import java.util.concurrent.CountDownLatch; + +/** + * Docker command result callback + * + * @author saeid + * + */ +public class StatsCallback extends ResultCallbackTemplate { + private Statistics stats = null; + private CountDownLatch countDownLatch; + + public StatsCallback(CountDownLatch countDownLatch) { + this.countDownLatch = countDownLatch; + } + + public Statistics getStats() { + return stats; + } + + @Override + public void onNext(Statistics stats) { + if (stats != null) { + this.stats = stats; + this.onComplete(); + } + this.countDownLatch.countDown(); + } + + public boolean gotStats() { + return stats != null; + } + + public void reset() { + stats = null; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskComparator.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskComparator.java new file mode 100644 index 00000000..7a89f652 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskComparator.java @@ -0,0 +1,31 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import org.eclipse.iofog.process_manager.ContainerTask.Tasks; + +import java.util.Comparator; + +public class TaskComparator implements Comparator { + + @Override + public int compare(ContainerTask o1, ContainerTask o2) { + if (o1.getAction() == Tasks.REMOVE) + return -1; + else if (o2.getAction() == Tasks.REMOVE) + return 1; + else + return 0; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskManager.java new file mode 100644 index 00000000..4d56e1cd --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskManager.java @@ -0,0 +1,54 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import java.util.LinkedList; +import java.util.Queue; + +public class TaskManager { + private final Queue tasks; + private static TaskManager instance; + + private TaskManager() { + tasks = new LinkedList<>(); + } + + public static TaskManager getInstance() { + if (instance == null) { + synchronized (TaskManager.class) { + if (instance == null) + instance = new TaskManager(); + } + } + return instance; + } + + public void addTask(ContainerTask task) { + synchronized (TaskManager.class) { + if (!tasks.contains(task)) + tasks.add(task); + } + } + + public ContainerTask getTask() { + synchronized (TaskManager.class) { + return tasks.peek(); + } + } + + public void removeTask(ContainerTask task) { + synchronized (TaskManager.class) { + tasks.remove(task); + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnection.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnection.java new file mode 100644 index 00000000..2f4b0ed9 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnection.java @@ -0,0 +1,121 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.proxy; + +import com.jcraft.jsch.JSch; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; + +import java.io.ByteArrayInputStream; +import java.util.function.Supplier; + +import static java.nio.charset.StandardCharsets.UTF_8; + +public class SshConnection { + + private static final String LOCAL_HOST = "localhost"; + private static final int TIMEOUT = 60000; + private final JSch jschSSHChannel; + private String username; + private String password; + private String host; + private String rsaKey; + private int remotePort; + private int localPort = 22; + private boolean closeFlag; + private Session session; + + public SshConnection() { + this.jschSSHChannel = new JSch(); + } + + /** + * opens reverse proxy on specified host + */ + public synchronized Supplier openSshTunnel() { + return () -> { + try { + session = jschSSHChannel.getSession(username, host, localPort); + session.setPassword(password); + session.connect(TIMEOUT); + session.setPortForwardingR(host, remotePort, LOCAL_HOST, localPort); + } catch (JSchException jschX) { + session.disconnect(); + throw new RuntimeException(jschX.getMessage()); + } + return null; + }; + + } + + /** + * sets connection info + * @param username username + * @param password user password + * @param host host name to start up proxy server + * @param rport remote port + * @param lport local port + * @param rsaKey server rsa key + * @param closeFlag flag indicating if connection should be closed or opened + */ + public synchronized void setProxyInfo(String username, String password, String host, int rport, int lport, String rsaKey, boolean closeFlag) { + this.username = username; + this.password = password; + this.host = host; + this.remotePort = rport; + this.localPort = lport; + this.rsaKey = rsaKey; + this.closeFlag = closeFlag; + } + + /** + * adds server rsa key to known hosts + */ + public void setKnownHost() throws JSchException { + jschSSHChannel.setKnownHosts(new ByteArrayInputStream(this.rsaKey.getBytes(UTF_8))); + } + + /** + * checks if tunnel is open + * + * @return boolean + */ + public synchronized boolean isConnected() { + return this.session != null && session.isConnected(); + } + + public synchronized boolean isCloseFlag() { + return closeFlag; + } + + public Session getSession() { + return session; + } + + public String getUsername() { + return username; + } + + public String getHost() { + return host; + } + + public int getRemotePort() { + return remotePort; + } + + public int getLocalPort() { + return localPort; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnectionStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnectionStatus.java new file mode 100644 index 00000000..02140291 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnectionStatus.java @@ -0,0 +1,26 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.proxy; + +/** + * Enum that indicates if ssh tunnel is open, closed or failed to start. + * + * @author epankov + * + */ +public enum SshConnectionStatus { + OPEN, + FAILED, + CLOSED +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManager.java new file mode 100644 index 00000000..a68cc962 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManager.java @@ -0,0 +1,193 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.proxy; + +import com.jcraft.jsch.JSchException; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.functional.Unit; +import org.eclipse.iofog.utils.logging.LoggingService; + +import javax.json.JsonObject; +import java.util.concurrent.CompletableFuture; + +import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.eclipse.iofog.proxy.SshConnectionStatus.*; +import static org.eclipse.iofog.utils.functional.Unit.UNIT; +import static org.eclipse.iofog.utils.logging.LoggingService.logError; +import static org.eclipse.iofog.utils.logging.LoggingService.logInfo; + +/** + * SSH Proxy Manager Module + * + * @author epankov + */ +public class SshProxyManager { + private static final String MODULE_NAME = "SSH Proxy Manager"; + private static final int DEFAULT_LOCAL_PORT = 22; + private static final int DEFAULT_REMOTE_PORT = 9999; + private final SshConnection connection; + + public SshProxyManager(SshConnection connection) { + this.connection = connection; + } + + /** + * starts or stops ssh tunnel according to current config + * @param config json object with proxy configs + * @return completable future of type void + */ + public CompletableFuture update(JsonObject config) { + CompletableFuture completableFuture = CompletableFuture.completedFuture(UNIT); + if (config != null && !config.isEmpty()) { + setSshConnection(config); + completableFuture = processValidConfig(); + } else { + handleUnexpectedTunnelState("Received invalid proxy config", FAILED); + } + + return completableFuture; + } + + private CompletableFuture processValidConfig() { + CompletableFuture completableFuture = CompletableFuture.completedFuture(UNIT); + if (connection.isConnected()) { + if (connection.isCloseFlag()) { + close(); + } else { + handleUnexpectedTunnelState("The tunnel is already opened. Please close it first.", OPEN); + } + } else { + if (connection.isCloseFlag()) { + handleUnexpectedTunnelState("The tunnel is already closed", CLOSED); + } else { + completableFuture = open(); + } + } + return completableFuture; + } + + /** + * handles unexpected situations like tunnel is already opened or closed + * @param errMsg error message + * @param status connection status + */ + private void handleUnexpectedTunnelState(String errMsg, SshConnectionStatus status) { + LoggingService.logWarning(MODULE_NAME, errMsg); + if (connection != null && connection.getUsername() != null) { + setSshProxyManagerStatus(status, errMsg); + } + } + + /** + * opens ssh tunnel + * @return completable future of type void + */ + private CompletableFuture open() { + setKnownHost(); + return openSshTunnel(); + } + + /** + * adds server rsa key to known hosts + */ + private void setKnownHost() { + try { + connection.setKnownHost(); + } catch (JSchException jschX) { + String errMsg = String.format("There was an issue with server RSA key setup:%n %s", jschX.getMessage()); + LoggingService.logError(MODULE_NAME, errMsg, jschX); + } + } + + /** + * opens ssh tunnel asynchronously + * @return completable future of type void + */ + private CompletableFuture openSshTunnel() { + return CompletableFuture.supplyAsync(connection.openSshTunnel()) + .handle((val, ex) -> { + if (ex != null) { + onError(ex); + } else { + onSuccess(); + } + return null; + }); + } + + private void onSuccess() { + setSshProxyManagerStatus(OPEN, EMPTY); + logInfo(MODULE_NAME, "opened ssh tunnel"); + CompletableFuture.runAsync(monitorSshTunnel()); + } + + private void onError(Throwable ex) { + String errMsg = String.format("Unable to connect to the server:%n %s", ex.getMessage()); + LoggingService.logError(MODULE_NAME, errMsg, ex); + setSshProxyManagerStatus(FAILED, errMsg); + } + + private Runnable monitorSshTunnel() { + return () -> { + while(connection.isConnected()) { + logInfo(MODULE_NAME, "ssh tunnel heartbeat message"); + try { + Thread.sleep(Configuration.getMonitorSshTunnelStatusFreqSeconds() * 1000); + } catch (InterruptedException e) { + logError(MODULE_NAME, "Error while sleeping thread", e); + } + } + if (!connection.isCloseFlag()) { + setSshProxyManagerStatus(CLOSED, EMPTY); + } + }; + } + + /** + * closes ssh tunnel + */ + private void close() { + connection.getSession().disconnect(); + setSshProxyManagerStatus(CLOSED, EMPTY); + logInfo(MODULE_NAME, "closed ssh tunnel"); + } + + /** + * sets current ssh proxy manager status + * + * @param status SshConnection status of the tunnel + */ + private void setSshProxyManagerStatus(SshConnectionStatus status, String errMsg) { + StatusReporter.setSshProxyManagerStatus() + .setProxyConfig(connection.getUsername(), connection.getHost(), connection.getRemotePort(), connection.getLocalPort()) + .setConnectionStatus(status) + .setErrorMessage(errMsg); + } + + /** + * sets proxy connection info + * @param configs JsonObject configs for ssh proxy + */ + private void setSshConnection(JsonObject configs) { + String username = configs.getString("username"); + String password = configs.getString("password"); + String host = configs.getString("host"); + String rsaKey = configs.getString("rsakey"); + int rport = configs.getInt("rport", DEFAULT_REMOTE_PORT); + int lport = configs.getInt("lport", DEFAULT_LOCAL_PORT); + boolean closeFlag = (configs.getBoolean("closed")); + connection.setProxyInfo(username, password, host, rport, lport, rsaKey, closeFlag); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManagerStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManagerStatus.java new file mode 100644 index 00000000..8d99c67a --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManagerStatus.java @@ -0,0 +1,65 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.proxy; + +import javax.json.Json; +import javax.json.JsonObjectBuilder; + +/** + * SSH Proxy Manager Status + * + * @author epankov + * + */ +public class SshProxyManagerStatus { + + private static final String EMPTY = "-"; + private String username = EMPTY; + private String host = EMPTY; + private int rport = 0; + private int lport = 0; + private SshConnectionStatus status = SshConnectionStatus.CLOSED; + private String errorMessage = EMPTY; + + public SshProxyManagerStatus() {} + + synchronized SshProxyManagerStatus setProxyConfig(String username, String host, int rport, int lport) { + this.username = username; + this.host = host; + this.rport = rport; + this.lport = lport; + return this; + } + + synchronized SshProxyManagerStatus setConnectionStatus(SshConnectionStatus status) { + this.status = status; + return this; + } + + synchronized SshProxyManagerStatus setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + return this; + } + + public synchronized String getJsonProxyStatus() { + JsonObjectBuilder objectBuilder = Json.createObjectBuilder() + .add("username", this.username) + .add("host", this.host) + .add("rport", this.rport) + .add("lport", this.lport) + .add("status", this.status.toString()) + .add("errormessage", this.errorMessage); + return objectBuilder.build().toString(); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/pruning/DockerPruningManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/pruning/DockerPruningManager.java new file mode 100644 index 00000000..2923b122 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/pruning/DockerPruningManager.java @@ -0,0 +1,159 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.pruning; + +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Image; +import com.github.dockerjava.api.model.PruneResponse; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.process_manager.DockerUtil; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @author nehanaithani + */ +public class DockerPruningManager { + private final static String MODULE_NAME = "Docker Manager"; + + private ScheduledExecutorService scheduler = null; + private DockerUtil docker = DockerUtil.getInstance(); + + private static DockerPruningManager instance; + + public static DockerPruningManager getInstance() { + if (instance == null) { + synchronized (DockerPruningManager.class) { + if (instance == null) + instance = new DockerPruningManager(); + } + } + return instance; + } + private DockerPruningManager() {} + private boolean isPruning; + private MicroserviceManager microserviceManager = MicroserviceManager.getInstance();; + /** + * Start docker pruning manager + */ + public void start() throws Exception { + LoggingService.logInfo(MODULE_NAME, "Start docker pruning manager"); + scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(triggerPruneOnThresholdBreach, 0, 30, TimeUnit.MINUTES); + LoggingService.logInfo(MODULE_NAME, "Docker pruning manager started"); + } + + /** + * Trigger prune on available disk is equal to or less than threshold + */ + private final Runnable triggerPruneOnThresholdBreach = () -> { + if (!isPruning) { + long availableDiskPercentage = StatusReporter.getResourceConsumptionManagerStatus().getAvailableDisk() * 100 / + StatusReporter.getResourceConsumptionManagerStatus().getTotalDiskSpace(); + if (availableDiskPercentage < Configuration.getAvailableDiskThreshold()) { + try { + LoggingService.logInfo(MODULE_NAME, "Pruning of unwanted images as current system available disk percentage : " + availableDiskPercentage + + " which is less than disk threshold for pruning : " + Configuration.getAvailableDiskThreshold()); + isPruning = true; + Set unwantedImages = getUnwantedImagesList(); + if (unwantedImages.size() > 0) { + removeImagesById(unwantedImages); + } + } catch (Exception e){ + LoggingService.logError(MODULE_NAME,"Error in docker Pruning when available threshold breach", new AgentSystemException(e.getMessage(), e)); + } finally { + isPruning = false; + LoggingService.logInfo(MODULE_NAME, "Pruning of unwanted images as current system available disk percentage finished"); + } + } + } + }; + + /** + * Gets list of unwanted docker images to be removed + * @return list + */ + public Set getUnwantedImagesList() { + List images = docker.getImages(); + LoggingService.logDebug(MODULE_NAME, "Total number of images already downloaded in the machine : " + images.size()); + List nonIoFogContainers = docker.getRunningNonIofogContainers(); + LoggingService.logDebug(MODULE_NAME, "Total number of running non iofog containers : " + nonIoFogContainers.size()); + + // Removes the non-ioFog running container from the images to be prune list + List ioFogImages = images.stream().filter(im -> nonIoFogContainers.stream() + .noneMatch(c -> c.getImageId().equals(im.getId()))) + .collect(Collectors.toList()); + + LoggingService.logInfo(MODULE_NAME, "Total number of ioFog images : " + ioFogImages.size()); + List microservices = microserviceManager.getLatestMicroservices(); + LoggingService.logInfo(MODULE_NAME, "Total number of running microservices : " + microservices.size()); + + // Removes the ioFog running containers from the images to be prune list + Set imageIDsToBePruned = ioFogImages.stream().filter(im -> im.getRepoTags() != null) + .filter(im -> microservices.stream() + .noneMatch(ms -> ms.getImageName().equals(im.getRepoTags()[0]))) + .map(Image::getId) + .collect(Collectors.toSet()); + Set imagesWithNoTags = ioFogImages.stream() + .filter(im -> im.getRepoTags() == null) + .map(Image::getId) + .collect(Collectors.toSet()); + imageIDsToBePruned.addAll(imagesWithNoTags); + LoggingService.logInfo(MODULE_NAME, "Total number of unwanted images to be pruned : " + imageIDsToBePruned.size()); + return imageIDsToBePruned; + } + + /** + * Remove unwanted docker image + * @param imageIDsToBePruned + */ + private void removeImagesById(Set imageIDsToBePruned){ + LoggingService.logInfo(MODULE_NAME, "Start removing image by ID size : " + imageIDsToBePruned.size()); + for (String id: imageIDsToBePruned) { + LoggingService.logInfo(MODULE_NAME, "Removing unwanted image id : " + id); + try { + docker.removeImageById(id); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME,"Error removing unwanted docker image id : " + id, + new AgentSystemException(e.getMessage(), e)); + } + } + LoggingService.logInfo(MODULE_NAME, "Finished removing image by ID"); + + } + + /** + * pruneAgent through commandLine + * @return + */ + public String pruneAgent(){ + LoggingService.logInfo(MODULE_NAME, "Initiate prune agent on demand."); + try { + PruneResponse response = docker.dockerPrune(); + LoggingService.logInfo(MODULE_NAME, "Pruned dangling docker images, total reclaimed space: " + response.getSpaceReclaimed()); + return "\nSuccess - pruned dangling docker images, total reclaimed space: " + response.getSpaceReclaimed(); + } catch (Exception e){ + LoggingService.logError(MODULE_NAME,"Error in docker Pruning", new AgentSystemException(e.getMessage(), e)); + return "\nFailure - not pruned."; + } + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManager.java new file mode 100644 index 00000000..44c8b807 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManager.java @@ -0,0 +1,350 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.resource_consumption_manager; + +import org.apache.commons.lang.SystemUtils; +import org.eclipse.iofog.IOFogModule; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.functional.Pair; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.io.*; +import java.lang.management.ManagementFactory; +import java.util.Arrays; +import java.util.List; + +import static org.apache.commons.lang.StringUtils.EMPTY; +import static org.eclipse.iofog.command_line.util.CommandShellExecutor.executeCommand; +import static org.eclipse.iofog.utils.Constants.RESOURCE_CONSUMPTION_MANAGER; + +/** + * Resource Consumption Manager module + * + * @author saeid + * + */ +public class ResourceConsumptionManager implements IOFogModule { + + private static final String MODULE_NAME = "Resource Consumption Manager"; + private float diskLimit, cpuLimit, memoryLimit; + private static ResourceConsumptionManager instance; + + private static final String POWERSHELL_GET_CPU_USAGE = "get-wmiobject Win32_PerfFormattedData_PerfProc_Process | ? { $_.IDProcess -eq %s } | select -ExpandProperty PercentProcessorTime"; + + + private ResourceConsumptionManager() {} + + @Override + public int getModuleIndex() { + return RESOURCE_CONSUMPTION_MANAGER; + } + + @Override + public String getModuleName() { + return MODULE_NAME; + } + + public static ResourceConsumptionManager getInstance() { + if (instance == null) { + synchronized (ResourceConsumptionManager.class) { + if (instance == null) + instance = new ResourceConsumptionManager(); + } + } + return instance; + } + + /** + * computes IOFog resource usage data + * and sets the {@link ResourceConsumptionManagerStatus} + * removes old archives if disk usage goes more than limit + * + */ + private Runnable getUsageData = () -> { + while (true) { + try { + logDebug("Get usage data"); + Thread.sleep(Configuration.getGetUsageDataFreqSeconds() * 1000); + + float memoryUsage = getMemoryUsage(); + float cpuUsage = getCpuUsage(); + float diskUsage = directorySize(Configuration.getDiskDirectory() + "messages/archive/"); + + long availableMemory = getSystemAvailableMemory(); + float totalCpu = getTotalCpu(); + long availableDisk = getAvailableDisk(); + long totalDiskSpace = getTotalDiskSpace(); + + StatusReporter.setResourceConsumptionManagerStatus() + .setMemoryUsage(memoryUsage / 1_000_000) + .setCpuUsage(cpuUsage) + .setDiskUsage(diskUsage / 1_000_000_000) + .setMemoryViolation(memoryUsage > memoryLimit) + .setDiskViolation(diskUsage > diskLimit) + .setCpuViolation(cpuUsage > cpuLimit) + .setAvailableMemory(availableMemory) + .setAvailableDisk(availableDisk) + .setTotalCpu(totalCpu) + .setTotalDiskSpace(totalDiskSpace); + + + if (diskUsage > diskLimit) { + float amount = diskUsage - (diskLimit * 0.75f); + removeArchives(amount); + } + }catch (InterruptedException e) { + logError("Error getting usage data Thread interrupted", new AgentSystemException(e.getMessage(), e)); + } catch (Exception e) { + logError("Error getting usage data", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finished Get usage data"); + } + }; + + /** + * remove old archives + * + * @param amount - disk space to be freed in bytes + */ + private void removeArchives(float amount) { + logDebug("Start remove archives : " + amount); + String archivesDirectory = Configuration.getDiskDirectory() + "messages/archive/"; + + final File workingDirectory = new File(archivesDirectory); + File[] filesList = workingDirectory.listFiles((dir, fileName) -> + fileName.substring(fileName.indexOf(".")).equals(".idx")); + + if (filesList != null) { + Arrays.sort(filesList, (o1, o2) -> { + String t1 = o1.getName().substring(o1.getName().indexOf('_') + 1, o1.getName().indexOf(".")); + String t2 = o2.getName().substring(o2.getName().indexOf('_') + 1, o2.getName().indexOf(".")); + return t1.compareTo(t2); + }); + + for (File indexFile : filesList) { + File dataFile = new File(archivesDirectory + indexFile.getName().substring(0, indexFile.getName().indexOf('.')) + ".iomsg"); + amount -= indexFile.length(); + indexFile.delete(); + amount -= dataFile.length(); + dataFile.delete(); + if (amount < 0) + break; + } + } + logDebug("Finished remove archives : "); + } + + /** + * gets memory usage of IOFog instance + * + * @return memory usage in bytes + */ + private float getMemoryUsage() { + logDebug("Start get memory usage"); + Runtime runtime = Runtime.getRuntime(); + long allocatedMemory = runtime.totalMemory(); + long freeMemory = runtime.freeMemory(); + logDebug("Finished get memory usage : "+ (float)(allocatedMemory - freeMemory)); + return (allocatedMemory - freeMemory); + } + + /** + * computes cpu usage of IOFog instance + * + * @return float number between 0-100 + */ + private float getCpuUsage() { + logDebug("Start get cpu usage"); + String processName = ManagementFactory.getRuntimeMXBean().getName(); + String processId = processName.split("@")[0]; + + if (SystemUtils.IS_OS_LINUX) { + + Pair before = parseStat(processId); + waitForSecond(); + Pair after = parseStat(processId); + logDebug("Finished get cpu usage : " + 100f * (after._1() - before._1()) / (after._2() - before._2())); + return 100f * (after._1() - before._1()) / (after._2() - before._2()); + } else if (SystemUtils.IS_OS_WINDOWS) { + String response = getWinCPUUsage(processId); + logInfo("Finished get cpu usage : " + response); + return Float.parseFloat(response); + } else { + logDebug("Finished get cpu usage : " + 0f); + return 0f; + } + } + + private long getSystemAvailableMemory() { + logDebug("Start get system available memory"); + if (SystemUtils.IS_OS_WINDOWS) { + logDebug("Finished get system available memory : " + 0); + return 0; + } + final String MEM_AVAILABLE = "grep 'MemAvailable' /proc/meminfo | awk '{print $2}'"; + CommandShellResultSet, List> resultSet = executeCommand(MEM_AVAILABLE); + long memInKB = 0L; + if(resultSet != null && !parseOneLineResult(resultSet).isEmpty()){ + memInKB = Long.parseLong(parseOneLineResult(resultSet)); + } + logDebug("Finished get system available memory : " + memInKB * 1024); + return memInKB * 1024; + } + + private float getTotalCpu() { + logDebug("Start get total cpu"); + if (SystemUtils.IS_OS_WINDOWS) { + return 0; + } + // @see https://github.com/Leo-G/DevopsWiki/wiki/How-Linux-CPU-Usage-Time-and-Percentage-is-calculated + final String CPU_USAGE = "LC_NUMERIC=en_US.UTF-8 && grep 'cpu' /proc/stat | awk '{usage=($2+$3+$4)*100/($2+$3+$4+$5+$6+$7+$8+$9)} END {printf (\"%d\", usage)}'"; + CommandShellResultSet, List> resultSet = executeCommand(CPU_USAGE); + float totalCpu = 0f; + if(resultSet != null && !parseOneLineResult(resultSet).isEmpty()){ + totalCpu = Float.parseFloat(parseOneLineResult(resultSet)); + } + logDebug("Finished get total cpu : " + totalCpu); + return totalCpu; + } + + private static String parseOneLineResult(CommandShellResultSet, List> resultSet) { + return resultSet.getError().size() == 0 && resultSet.getValue().size() > 0 ? resultSet.getValue().get(0) : EMPTY; + } + + private long getAvailableDisk() { + logDebug("Start get available disk"); + File[] roots = File.listRoots(); + long freeSpace = 0; + for (File f : roots) { + freeSpace += f.getUsableSpace(); + } + logDebug("Finished get available disk : " + freeSpace); + return freeSpace; + } + + private void waitForSecond() { + try { + Thread.sleep(1000); + } catch (InterruptedException exp) { + logError("Thread was interrupted", new AgentSystemException("Thread was interrupted", exp) ); + } + + } + + private Pair parseStat(String processId){ + logDebug("Inside parse Stat"); + long time = 0, total = 0; + + try { + String line; + try (BufferedReader br = new BufferedReader(new FileReader("/proc/" + processId + "/stat"))) { + line = br.readLine(); + time = Long.parseLong(line.split(" ")[13]); + } + + total = 0; + + try (BufferedReader br = new BufferedReader(new FileReader("/proc/stat"))) { + line = br.readLine(); + while (line != null) { + String[] items = line.split(" "); + if (items[0].equals("cpu")) { + for (int i = 1; i < items.length; i++) + if (!items[i].trim().equals("") && items[i].matches("[0-9]*")) + total += Long.parseLong(items[i]); + break; + } + } + } + } catch (IOException exp) { + logError("Error getting CPU usage : " + exp.getMessage(), new AgentSystemException(exp.getMessage(), exp)); + }catch (Exception exp) { + logError("Error getting CPU usage : " + exp.getMessage(), new AgentSystemException(exp.getMessage(), exp)); + } + return Pair.of(time, total); + } + + private static String getWinCPUUsage(final String pid) { + String cmd = String.format(POWERSHELL_GET_CPU_USAGE, pid); + final CommandShellResultSet, List> response = executeCommand(cmd); + return response != null ? + !response.getError().isEmpty() || response.getValue().isEmpty() ? + "0" : + response.getValue().get(0) : + "0"; + } + + /** + * computes a directory size + * + * @param name - name of the directory + * @return size in bytes + */ + private long directorySize(String name) { + logDebug("Inside get directory size"); + File directory = new File(name); + if (!directory.exists()) + return 0; + if (directory.isFile()) + return directory.length(); + long length = 0; + for (File file : directory.listFiles()) { + if (file.isFile()) + length += file.length(); + else if (file.isDirectory()) + length += directorySize(file.getPath()); + } + logDebug("Finished directory size : " + length); + return length; + } + + /** + * updates limits when changes applied to {@link Configuration} + * + */ + public void instanceConfigUpdated() { + logInfo("Start Configuration instance updated"); + diskLimit = Configuration.getDiskLimit() * 1_000_000_000; + cpuLimit = Configuration.getCpuLimit(); + memoryLimit = Configuration.getMemoryLimit() * 1_000_000; + logInfo("Finished Config updated"); + } + + /** + * starts Resource Consumption Manager module + * + */ + public void start() { + logDebug("Starting"); + instanceConfigUpdated(); + + new Thread(getUsageData, Constants.RESOURCE_CONSUMPTION_MANAGER_GET_USAGE_DATA).start(); + + logDebug("started"); + } + + private long getTotalDiskSpace() { + logDebug("Start get available disk"); + File[] roots = File.listRoots(); + long totalDiskSpace = 0; + for (File f : roots) { + totalDiskSpace += f.getTotalSpace(); + } + logDebug("Finished get available disk : " + totalDiskSpace); + return totalDiskSpace; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatus.java new file mode 100644 index 00000000..6a3f9989 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatus.java @@ -0,0 +1,126 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.resource_consumption_manager; + +/** + * represents Resource Consumption Manager status + * + * @author saeid + * + */ +public class ResourceConsumptionManagerStatus { + private float memoryUsage; + private float diskUsage; + private float cpuUsage; + private boolean memoryViolation; + private boolean diskViolation; + private boolean cpuViolation; + private long availableMemory; + private float totalCpu; + private long availableDisk; + + private long totalDiskSpace; + + public ResourceConsumptionManagerStatus() { + } + + public float getMemoryUsage() { + return memoryUsage; + } + + public ResourceConsumptionManagerStatus setMemoryUsage(float memoryUsage) { + this.memoryUsage = memoryUsage; + return this; + } + + public float getDiskUsage() { + return diskUsage; + } + + public ResourceConsumptionManagerStatus setDiskUsage(float diskUsage) { + this.diskUsage = diskUsage; + return this; + } + + public float getCpuUsage() { + return cpuUsage; + } + + public ResourceConsumptionManagerStatus setCpuUsage(float cpuUsage) { + this.cpuUsage = cpuUsage; + return this; + } + + public boolean isDiskViolation() { + return diskViolation; + } + + public ResourceConsumptionManagerStatus setDiskViolation(boolean diskViolation) { + this.diskViolation = diskViolation; + return this; + } + + public boolean isCpuViolation() { + return cpuViolation; + } + + public ResourceConsumptionManagerStatus setCpuViolation(boolean cpViolation) { + this.cpuViolation = cpViolation; + return this; + } + + public boolean isMemoryViolation() { + return memoryViolation; + } + + public ResourceConsumptionManagerStatus setMemoryViolation(boolean memoryViolation) { + this.memoryViolation = memoryViolation; + return this; + } + + public long getAvailableMemory() { + return availableMemory; + } + + public ResourceConsumptionManagerStatus setAvailableMemory(long availableMemory) { + this.availableMemory = availableMemory; + return this; + } + + public float getTotalCpu() { + return totalCpu; + } + + public ResourceConsumptionManagerStatus setTotalCpu(float totalCpu) { + this.totalCpu = totalCpu; + return this; + } + + public long getAvailableDisk() { + return availableDisk; + } + + public ResourceConsumptionManagerStatus setAvailableDisk(long availableDisk) { + this.availableDisk = availableDisk; + return this; + } + + public long getTotalDiskSpace() { + return totalDiskSpace; + } + + public void setTotalDiskSpace(long totalDiskSpace) { + this.totalDiskSpace = totalDiskSpace; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManager.java new file mode 100644 index 00000000..f1105854 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManager.java @@ -0,0 +1,66 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.resource_manager; + +import org.eclipse.iofog.IOFogModule; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +/** + * @author elukashick + */ +public class ResourceManager implements IOFogModule { + + private static final String MODULE_NAME = ResourceManager.class.getSimpleName(); + public static final String HW_INFO_URL = "http://localhost:54331/hal/hwc/lshw"; + public static final String USB_INFO_URL = "http://localhost:54331/hal/hwc/lsusb"; + public static final String COMMAND_HW_INFO = "hal/hw"; + public static final String COMMAND_USB_INFO = "hal/usb"; + + @Override + public int getModuleIndex() { + return Constants.RESOURCE_MANAGER; + } + + @Override + public String getModuleName() { + return MODULE_NAME; + } + + public void start() { + new Thread(getUsageData, Constants.RESOURCE_MANAGER_GET_USAGE_DATA).start(); + LoggingService.logDebug("ResourceManager", "started"); + + } + + private Runnable getUsageData = () -> { + + while (true) { + LoggingService.logDebug(MODULE_NAME, "Start getting usage data"); + FieldAgent.getInstance().sendUSBInfoFromHalToController(); + FieldAgent.getInstance().sendHWInfoFromHalToController(); + try { + Thread.sleep(Configuration.getDeviceScanFrequency() * 1000); + } catch (InterruptedException e) { + LoggingService.logError(MODULE_NAME, "Error getting usage data", + new AgentSystemException(e.getMessage(), e)); + } + LoggingService.logDebug(MODULE_NAME, "Finished getting usage data"); + } + }; + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManagerStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManagerStatus.java new file mode 100644 index 00000000..040620fd --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManagerStatus.java @@ -0,0 +1,40 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.resource_manager; + +/** + * @author elukashick + */ +public class ResourceManagerStatus { + + private String hwInfo = ""; + private String usbConnectionsInfo = ""; + + + public String getHwInfo() { + return hwInfo; + } + + public void setHwInfo(String hwInfo) { + this.hwInfo = hwInfo; + } + + public String getUsbConnectionsInfo() { + return usbConnectionsInfo; + } + + public void setUsbConnectionsInfo(String usbConnectionsInfo) { + this.usbConnectionsInfo = usbConnectionsInfo; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporter.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporter.java new file mode 100644 index 00000000..e488f667 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporter.java @@ -0,0 +1,232 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.status_reporter; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.field_agent.FieldAgentStatus; +import org.eclipse.iofog.local_api.LocalApiStatus; +import org.eclipse.iofog.message_bus.MessageBusStatus; +import org.eclipse.iofog.process_manager.ProcessManagerStatus; +import org.eclipse.iofog.proxy.SshProxyManagerStatus; +import org.eclipse.iofog.resource_consumption_manager.ResourceConsumptionManagerStatus; +import org.eclipse.iofog.resource_manager.ResourceManagerStatus; +import org.eclipse.iofog.supervisor.SupervisorStatus; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.io.File; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Locale; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Status Reporter module + * + * @author saeid + */ +public final class StatusReporter { + + private static final SupervisorStatus supervisorStatus = new SupervisorStatus(); + private static final ResourceConsumptionManagerStatus resourceConsumptionManagerStatus = new ResourceConsumptionManagerStatus(); + private static final ResourceManagerStatus resourceManagerStatus = new ResourceManagerStatus(); + private static final FieldAgentStatus fieldAgentStatus = new FieldAgentStatus(); + private static final StatusReporterStatus statusReporterStatus = new StatusReporterStatus(); + private static final ProcessManagerStatus processManagerStatus = new ProcessManagerStatus(); + private static final LocalApiStatus localApiStatus = new LocalApiStatus(); + private static final MessageBusStatus messageBusStatus = new MessageBusStatus(); + private static final SshProxyManagerStatus sshManagerStatus = new SshProxyManagerStatus(); + + private final static String MODULE_NAME = "Status Reporter"; + + /** + * sets system time property + */ + private static final Runnable setStatusReporterSystemTime = () -> { + LoggingService.logDebug(MODULE_NAME, "Inside setStatusReporterSystemTime"); + try { + Thread.currentThread().setName(Constants.STATUS_REPORTER_SET_STATUS_REPORTER_SYSTEM_TIME); + setStatusReporterStatus().setSystemTime(System.currentTimeMillis()); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, e.getMessage(), new AgentSystemException(e.getMessage(), e)); + } + LoggingService.logDebug(MODULE_NAME, "Finished setStatusReporterSystemTime"); + }; + + private StatusReporter() { + } + + /** + * returns report for "status" command-line parameter + * + * @return status report + */ + public static String getStatusReport() { + LoggingService.logInfo(MODULE_NAME, "Getting Status Report"); + StringBuilder result = new StringBuilder(); + + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(statusReporterStatus.getSystemTime()); + SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy hh:mm a"); + + float diskUsage = resourceConsumptionManagerStatus.getDiskUsage(); + + double availableDisk = resourceConsumptionManagerStatus.getAvailableDisk() / 1024. / 1024.; + double availableMemory = resourceConsumptionManagerStatus.getAvailableMemory() / 1024. / 1024.; + float totalCpu = resourceConsumptionManagerStatus.getTotalCpu(); + + String connectionStatus = ""; + + switch (fieldAgentStatus.getControllerStatus()) { + case NOT_PROVISIONED: + connectionStatus = "not provisioned"; + break; + case BROKEN_CERTIFICATE: + connectionStatus = "broken certificate"; + break; + case OK: + connectionStatus = "ok"; + break; + default: + connectionStatus = "not connected"; + break; + } + + result.append("ioFog daemon : ").append(supervisorStatus.getDaemonStatus().name()); + result.append("\\nMemory Usage : about ").append(String.format("%.2f MiB", resourceConsumptionManagerStatus.getMemoryUsage())); + if (diskUsage < 1) + result.append("\\nDisk Usage : about ").append(String.format("%.2f MiB", diskUsage * 1024)); + else + result.append("\\nDisk Usage : about ").append(String.format("%.2f GiB", diskUsage)); + result.append("\\nCPU Usage : about ").append(String.format("%.2f %%", resourceConsumptionManagerStatus.getCpuUsage())); + result.append("\\nRunning Microservices : ").append(processManagerStatus.getRunningMicroservicesCount()); + result.append("\\nConnection to Controller : ").append(connectionStatus); + result.append(String.format(Locale.US, "\\nMessages Processed : about %,d", messageBusStatus.getProcessedMessages())); + result.append("\\nSystem Time : ").append(dateFormat.format(cal.getTime())); + + result.append("\\nSystem Available Disk : ").append(String.format("%.2f MB (%.2f %%)", availableDisk, ((availableDisk * Constants.MiB) / getTotalDisk()) * 100.0f)); + result.append("\\nSystem Available Memory : ").append(String.format("%.2f MB", availableMemory)); + result.append("\\nSystem Total CPU : ").append(String.format("%.2f %%", totalCpu)); + + LoggingService.logInfo(MODULE_NAME, "Finished Getting Status Report : " + result.toString()); + return result.toString(); + } + + public static SupervisorStatus setSupervisorStatus() { + LoggingService.logDebug(MODULE_NAME, "set Supervisor Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return supervisorStatus; + } + + public static ResourceConsumptionManagerStatus setResourceConsumptionManagerStatus() { + LoggingService.logDebug(MODULE_NAME, "set ResourceConsumption Manager Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return resourceConsumptionManagerStatus; + } + + public static ResourceManagerStatus setResourceManagerStatus() { + LoggingService.logDebug(MODULE_NAME, "set Resource Manager Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return resourceManagerStatus; + } + + public static MessageBusStatus setMessageBusStatus() { + LoggingService.logDebug(MODULE_NAME, "set Message Bus Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return messageBusStatus; + } + + public static FieldAgentStatus setFieldAgentStatus() { + LoggingService.logDebug(MODULE_NAME, "set Field Agent Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return fieldAgentStatus; + } + + public static StatusReporterStatus setStatusReporterStatus() { + LoggingService.logDebug(MODULE_NAME, "set Status Reporter Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return statusReporterStatus; + } + + public static ProcessManagerStatus setProcessManagerStatus() { + LoggingService.logDebug(MODULE_NAME, "set Process Manager Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return processManagerStatus; + } + + public static SshProxyManagerStatus setSshProxyManagerStatus() { + LoggingService.logDebug(MODULE_NAME, "set SshProxy Manager Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return sshManagerStatus; + } + + public static ProcessManagerStatus getProcessManagerStatus() { + return processManagerStatus; + } + + public static LocalApiStatus setLocalApiStatus() { + LoggingService.logDebug(MODULE_NAME, "set Local Api Status"); + statusReporterStatus.setLastUpdate(System.currentTimeMillis()); + return localApiStatus; + } + + public static SupervisorStatus getSupervisorStatus() { + return supervisorStatus; + } + + public static MessageBusStatus getMessageBusStatus() { + return messageBusStatus; + } + + public static ResourceConsumptionManagerStatus getResourceConsumptionManagerStatus() { + return resourceConsumptionManagerStatus; + } + + public static ResourceManagerStatus getResourceManagerStatus() { + return resourceManagerStatus; + } + + public static FieldAgentStatus getFieldAgentStatus() { + return fieldAgentStatus; + } + + public static StatusReporterStatus getStatusReporterStatus() { + return statusReporterStatus; + } + + public static LocalApiStatus getLocalApiStatus() { + return localApiStatus; + } + + public static SshProxyManagerStatus getSshManagerStatus() { + return sshManagerStatus; + } + + /** + * starts Status Reporter module + */ + public static void start() { + LoggingService.logInfo(MODULE_NAME, "Starting Status Reporter"); + ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(setStatusReporterSystemTime, Configuration.getSetSystemTimeFreqSeconds(), Configuration.getSetSystemTimeFreqSeconds(), TimeUnit.SECONDS); + LoggingService.logInfo(MODULE_NAME, "Started Status Reporter"); + } + + private static float getTotalDisk() { + File root = new File("/"); + return root.getTotalSpace(); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporterStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporterStatus.java new file mode 100644 index 00000000..bdf5d8be --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporterStatus.java @@ -0,0 +1,53 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.status_reporter; + +/** + * represent Status Reporter status + * + * @author saeid + * + */ +public class StatusReporterStatus { + private long systemTime; + private long lastUpdate; + + public StatusReporterStatus() { + systemTime = System.currentTimeMillis(); + lastUpdate = systemTime; + } + + public StatusReporterStatus(long systemTime, long lastUpdate) { + this.systemTime = systemTime; + this.lastUpdate = lastUpdate; + } + + public long getSystemTime() { + return systemTime; + } + + public StatusReporterStatus setSystemTime(long systemTime) { + this.systemTime = systemTime; + return this; + } + + public long getLastUpdate() { + return lastUpdate; + } + + public StatusReporterStatus setLastUpdate(long lastUpdate) { + this.lastUpdate = lastUpdate; + return this; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/Supervisor.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/Supervisor.java new file mode 100644 index 00000000..ca84e94c --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/Supervisor.java @@ -0,0 +1,158 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.supervisor; + +import org.eclipse.iofog.IOFogModule; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.local_api.LocalApi; +import org.eclipse.iofog.message_bus.MessageBus; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.process_manager.ProcessManager; +import org.eclipse.iofog.pruning.DockerPruningManager; +import org.eclipse.iofog.resource_consumption_manager.ResourceConsumptionManager; +import org.eclipse.iofog.resource_manager.ResourceManager; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import static java.lang.System.currentTimeMillis; +import static java.lang.Thread.State.TERMINATED; +import static java.util.concurrent.TimeUnit.SECONDS; +import static org.eclipse.iofog.utils.Constants.*; +import static org.eclipse.iofog.utils.Constants.ModulesStatus.RUNNING; +import static org.eclipse.iofog.utils.Constants.ModulesStatus.STARTING; + +/** + * Supervisor module + * + * @author saeid + * + */ +public class Supervisor implements IOFogModule { + + private static final String MODULE_NAME = "Supervisor"; + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + private MessageBus messageBus; + private Thread localApiThread; + private LocalApi localApi; + + /** + * monitors {@link LocalApi} module status + * + */ + private Runnable checkLocalApiStatus = () -> { + Thread.currentThread().setName(Constants.SUPERVISOR_CHECK_LOCAL_API_STATUS); + logDebug("Check local API status"); + try { + if (localApiThread != null && localApiThread.getState() == TERMINATED) { + localApiThread = new Thread(localApi, Constants.LOCAL_API_EVENT); + logInfo("Start local API : status not running"); + localApiThread.start(); + logInfo("Finished starting local API "); + } + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "", new AgentSystemException(e.getMessage(), e)); + } + logDebug("Finished Checking local API status"); + }; + + public Supervisor() {} + + /** + * starts Supervisor module + * + * @throws Exception + */ + public void start() throws Exception { + Runtime.getRuntime().addShutdownHook(new Thread(shutdownHook, Constants.SHUTDOWN_HOOK)); + logDebug("Starting Supervisor"); + StatusReporter.start(); + StatusReporter.setSupervisorStatus().setModuleStatus(STATUS_REPORTER, RUNNING); + + StatusReporter.setSupervisorStatus() + .setDaemonStatus(STARTING) + .setDaemonLastStart(currentTimeMillis()) + .setOperationDuration(0); + IOFogNetworkInterfaceManager.getInstance().start(); + startModule(ResourceConsumptionManager.getInstance()); + startModule(FieldAgent.getInstance()); + startModule(ProcessManager.getInstance()); + startModule(new ResourceManager()); + messageBus = MessageBus.getInstance(); + startModule(messageBus); + + localApi = LocalApi.getInstance(); + localApiThread = new Thread(localApi, Constants.LOCAL_API_EVENT); + localApiThread.start(); + scheduler.scheduleAtFixedRate(checkLocalApiStatus, 0, 10, SECONDS); + + StatusReporter.setSupervisorStatus().setDaemonStatus(RUNNING); + logDebug("Started Supervisor"); + DockerPruningManager.getInstance().start(); + operationDuration(); + } + + private void startModule(IOFogModule ioFogModule) throws Exception { + logInfo(" Starting " + ioFogModule.getModuleName()); + StatusReporter.setSupervisorStatus().setModuleStatus(ioFogModule.getModuleIndex(), STARTING); + ioFogModule.start(); + StatusReporter.setSupervisorStatus().setModuleStatus(ioFogModule.getModuleIndex(), RUNNING); + logInfo(" Started " + ioFogModule.getModuleName()); + } + + private void operationDuration(){ + logDebug(" Start checking operation duration "); + while (true) { + StatusReporter.setSupervisorStatus() + .setOperationDuration(currentTimeMillis()); + try { + Thread.sleep(Configuration.getStatusReportFreqSeconds() * 1000); + } catch (InterruptedException e) { + logError("Error checking operation duration", new AgentSystemException("Error checking operation duration", e)); + System.exit(1); + } + logDebug(" Finished checking operation duration "); + } + } + + /** + * shutdown hook to stop {@link MessageBus} and {@link LocalApi} + * + */ + private final Runnable shutdownHook = () -> { + try { + scheduler.shutdownNow(); + if (localApi != null) + localApi.stopServer(); + if (messageBus != null) + messageBus.stop(); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error in shutdown hook to stop message bus and local api", + new AgentSystemException(e.getMessage(), e)); + } + }; + + @Override + public int getModuleIndex() { + return MESSAGE_BUS; + } + + @Override + public String getModuleName() { + return MODULE_NAME; + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/SupervisorStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/SupervisorStatus.java new file mode 100644 index 00000000..97503848 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/SupervisorStatus.java @@ -0,0 +1,76 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.supervisor; + +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.Constants.ModulesStatus; + +/** + * represents Supervisor status + * + * @author saeid + * + */ +public class SupervisorStatus { + private ModulesStatus daemonStatus; + private final ModulesStatus[] modulesStatus; + private long daemonLastStart; + private long operationDuration; + + + public SupervisorStatus() { + modulesStatus = new ModulesStatus[Constants.NUMBER_OF_MODULES]; + for (int i = 0; i < Constants.NUMBER_OF_MODULES; i++) + modulesStatus[i] = ModulesStatus.STARTING; + } + + public SupervisorStatus setModuleStatus(int module, ModulesStatus status) { + if (modulesStatus.length > module) + modulesStatus[module] = status; + return this; + } + + public ModulesStatus getModuleStatus(int module) { + if (modulesStatus.length > module) + return modulesStatus[module]; + return null; + } + + public ModulesStatus getDaemonStatus() { + return daemonStatus; + } + + public SupervisorStatus setDaemonStatus(ModulesStatus daemonStatus) { + this.daemonStatus = daemonStatus; + return this; + } + + public long getDaemonLastStart() { + return daemonLastStart; + } + + public SupervisorStatus setDaemonLastStart(long daemonLastStart) { + this.daemonLastStart = daemonLastStart; + return this; + } + + public long getOperationDuration() { + long opDuration = operationDuration - daemonLastStart; + return opDuration >= 0 ? opDuration : 0; + } + + public SupervisorStatus setOperationDuration(long operationDuration) { + this.operationDuration = operationDuration; + return this; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/BytesUtil.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/BytesUtil.java new file mode 100644 index 00000000..9240d49b --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/BytesUtil.java @@ -0,0 +1,114 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils; + +import java.util.Arrays; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * provides methods for "number <=> byte array" conversions + * + * @author saeid + * + */ +public class BytesUtil { + + private static final String MODULE_NAME = "BytesUtil"; + + public static byte[] copyOfRange(byte[] src, int from, int to) { + return (from < 0 || from >= src.length || to < from || to > src.length) ? new byte[]{} : Arrays.copyOfRange(src, from, to); + } + + public static byte[] longToBytes(long x) { + byte[] b = new byte[8]; + for (int i = 0; i < 8; ++i) { + b[i] = (byte) (x >> (8 - i - 1 << 3)); + } + return b; + } + + public static long bytesToLong(byte[] bytes) { + long result = 0; + for (byte aByte : bytes) { + result = (result << 8) + (aByte & 0xff); + } + return result; + } + + public static byte[] integerToBytes(int x) { + byte[] b = new byte[4]; + for (int i = 0; i < 4; ++i) { + b[i] = (byte) (x >> (4 - i - 1 << 3)); + } + return b; + } + + public static int bytesToInteger(byte[] bytes) { + int result = 0; + for (byte aByte : bytes) { + result = (result << 8) + (aByte & 0xff); + } + return result; + } + + public static byte[] shortToBytes(short x) { + byte[] b = new byte[2]; + for (int i = 0; i < 2; ++i) { + b[i] = (byte) (x >> (2 - i - 1 << 3)); + } + return b; + } + + public static short bytesToShort(byte[] bytes) { + short result = 0; + for (byte aByte : bytes) { + result = (short) ((result << 8) + (aByte & 0xff)); + } + return result; + } + + public static byte[] stringToBytes(String s) { + if (s == null) { + return new byte[] {}; + } + else { + return s.getBytes(UTF_8); + } + + } + + public static String bytesToString(byte[] bytes) { + return new String(bytes); + } + + /** + * returns string presentation of byte array + * byte[] a = {1, 2, 3, 4} => String a = "[1, 2, 3, 4]" + * + * @param bytes + * @return string + */ + public static String byteArrayToString(byte[] bytes) { + StringBuilder result = new StringBuilder(); + + result.append("["); + for (byte b : bytes) { + if (result.length() > 1) + result.append(", "); + result.append(b); + } + result.append("]"); + return result.toString(); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/CmdProperties.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/CmdProperties.java new file mode 100644 index 00000000..d04e8a06 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/CmdProperties.java @@ -0,0 +1,93 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils; + +import org.eclipse.iofog.command_line.CommandLineConfigParam; +import org.eclipse.iofog.utils.logging.LoggingService; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * Properties files Util + * + * @since 1/25/18. + * @author ilaryionava + */ +public class CmdProperties { + + private static final String MODULE_NAME = "CmdProperties"; + private static final String FILE_PATH = "/cmd_messages.properties"; + private static final String VERSION_FILE_PATH = "/version.properties"; + + private static final Properties cmdProperties; + private static final Properties versionProperties; + + static { + cmdProperties = new Properties(); + try (InputStream in = CmdProperties.class.getResourceAsStream(FILE_PATH)) { + cmdProperties.load(in); + } catch (IOException e) { + LoggingService.logError(MODULE_NAME, e.getMessage(), e); + } + + versionProperties = new Properties(); + try (InputStream in = CmdProperties.class.getResourceAsStream(VERSION_FILE_PATH)) { + versionProperties.load(in); + } catch (IOException e) { + LoggingService.logError(MODULE_NAME, e.getMessage(), e); + } + } + + public static String getVersionMessage() { + return cmdProperties.getProperty("version_msg"); + } + + public static String getVersion() { + return versionProperties.getProperty("version"); + } + + public static String getDeprovisionMessage() { + return cmdProperties.getProperty("deprovision_msg"); + } + + public static String getProvisionMessage() { + return cmdProperties.getProperty("provision_msg"); + } + + public static String getProvisionCommonErrorMessage() { + return cmdProperties.getProperty("provision_common_error"); + } + + public static String getProvisionStatusErrorMessage() { + return cmdProperties.getProperty("provision_status_error"); + } + + public static String getProvisionStatusSuccessMessage() { + return cmdProperties.getProperty("provision_status_success"); + } + + public static String getConfigParamMessage(CommandLineConfigParam configParam) { + return cmdProperties.getProperty(configParam.name().toLowerCase()); + } + + public static String getIofogUuidMessage() { + return cmdProperties.getProperty("iofog_uuid"); + } + + public static String getIpAddressMessage() { + return cmdProperties.getProperty("ip_address"); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Constants.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Constants.java new file mode 100755 index 00000000..76427cd5 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Constants.java @@ -0,0 +1,135 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils; + +import org.apache.commons.lang.SystemUtils; + +import java.io.PrintStream; + +/** + * holds IOFog constants + * + * @author saeid + */ +public class Constants { + + public enum ModulesStatus { + STARTING, RUNNING, STOPPED + } + + public enum DockerStatus { + NOT_PRESENT, RUNNING, STOPPED + } + + public enum LinkStatus { + FAILED_VERIFICATION, FAILED_LOGIN, CONNECTED + } + + public enum ControllerStatus { + NOT_PROVISIONED, BROKEN_CERTIFICATE, OK, NOT_CONNECTED + } + + public enum ConfigSwitcherState { + DEFAULT(new String[]{"default", "def"}), + DEVELOPMENT(new String[]{"development", "dev"}), + PRODUCTION(new String[]{"production", "prod"}); + + ConfigSwitcherState(String[] aliases) { + this.aliases = aliases; + } + + public static ConfigSwitcherState parse(String stringState) throws IllegalArgumentException { + for (ConfigSwitcherState switcherState : ConfigSwitcherState.values()) { + for (String alias : switcherState.aliases) { + if (alias.equalsIgnoreCase(stringState)) { + return switcherState; + } + } + } + + throw new IllegalArgumentException("Invalid switcher state"); + } + + private String[] aliases; + + public String fullValue() { + return aliases[0]; + } + } + + public static final int NUMBER_OF_MODULES = 8; + + public static final int RESOURCE_CONSUMPTION_MANAGER = 0; + public static final int PROCESS_MANAGER = 1; + public static final int STATUS_REPORTER = 2; + public static final int LOCAL_API = 3; + public static final int MESSAGE_BUS = 4; + public static final int FIELD_AGENT = 5; + public static final int RESOURCE_MANAGER = 6; + + public static PrintStream systemOut; + + public static final int KiB = 1024; + public static final int MiB = KiB * KiB; + public static final int GiB = KiB * KiB * KiB; + + + public static final String SNAP = System.getenv("SNAP") != null ? + System.getenv("SNAP") : ""; + public static final String SNAP_COMMON = System.getenv("SNAP_COMMON") != null ? + System.getenv("SNAP_COMMON") : ""; + private static final String WINDOWS_IOFOG_PATH = System.getenv("IOFOG_PATH") != null ? + System.getenv("IOFOG_PATH") : "./"; + public static final String VAR_RUN = SystemUtils.IS_OS_WINDOWS ? + SNAP_COMMON + "./var/run/iofog-agent" : SNAP_COMMON + "/var/run/iofog-agent"; + private static final String CONFIG_DIR = SystemUtils.IS_OS_WINDOWS ? + WINDOWS_IOFOG_PATH : SNAP_COMMON + "/etc/iofog-agent/"; + public static final String LOCAL_API_TOKEN_PATH = CONFIG_DIR + "local-api"; + public static final String DEFAULT_CONFIG_PATH = CONFIG_DIR + "config.xml"; + public static final String DEVELOPMENT_CONFIG_PATH = CONFIG_DIR + "config-development.xml"; + public static final String PRODUCTION_CONFIG_PATH = CONFIG_DIR + "config-production.xml"; + public static String BACKUP_CONFIG_PATH = CONFIG_DIR + "config-bck.xml"; + + public static final String CONFIG_SWITCHER_PATH = CONFIG_DIR + "config-switcher.xml"; + public static final String SWITCHER_ELEMENT = "switcher"; + public static final String SWITCHER_NODE = "current_config"; + public static final String OS_GROUP = "iofog-agent"; + public static final String IOFOG_DOCKER_CONTAINER_NAME_PREFIX = "iofog_"; + + public static final String MICROSERVICE_FILE = "microservices.json"; + + public static final String FIELD_AGENT_PING_CONTROLLER = "FAPC"; + public static final String FIELD_AGENT_GET_CHANGE_LIST = "FACL"; + public static final String FIELD_AGENT_POST_STATUS = "FAPS"; + public static final String FIELD_AGENT_POST_DIAGNOSTIC = "FAPD"; + public static final String MESSAGE_BUS_CALCULATE_SPEED = "MBCS"; + public static final String STATUS_REPORTER_SET_STATUS_REPORTER_SYSTEM_TIME = "SRST"; + public static final String LOCAL_API_EVENT = "LAPI"; + public static final String RESOURCE_CONSUMPTION_MANAGER_GET_USAGE_DATA = "RCUD"; + public static final String PROCESS_MANAGER_CONTAINERS_MONITOR = "PMCM"; + public static final String PROCESS_MANAGER_CHECK_TASKS = "PMCT"; + public static final String RESOURCE_MANAGER_GET_USAGE_DATA = "RMUD"; + public static final String LOCAL_API_CONTROL_WEBSOCKET_WORKER = "LACW"; + public static final String LOCAL_API_MESSAGE_WEBSOCKET_WORKER = "LAMW"; + + public static final String SHUTDOWN_HOOK = "SDHK"; + + public static final String SUPERVISOR_CHECK_LOCAL_API_STATUS = "SCLA"; + public static final String NETWORK_INTERFACE_MANAGER = "INIM"; + + public static final float MAX_DISK_CONSUMPTION_LIMIT = 100; + public static final float PERCENTAGE_COMPLETION = 100; + public static final String EDGE_RESOURCE_FILE = "edge_resources.json"; + + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Orchestrator.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Orchestrator.java new file mode 100644 index 00000000..cb9c0b11 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Orchestrator.java @@ -0,0 +1,430 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils; + +import org.apache.commons.lang.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.*; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.Header; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.message.BasicHeader; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.field_agent.enums.RequestType; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.eclipse.iofog.utils.trustmanager.TrustManagers; + +import javax.json.Json; +import javax.json.JsonException; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.stream.JsonParsingException; +import javax.naming.AuthenticationException; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.ClientErrorException; +import javax.ws.rs.ForbiddenException; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.ServerErrorException; + +import java.io.*; +import java.net.MalformedURLException; +import java.net.SocketException; +import java.net.UnknownHostException; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.util.Map; +import java.util.UUID; + +import static org.eclipse.iofog.utils.logging.LoggingService.*; + +/** + * provides methods for IOFog controller + * + * @author saeid + */ +public class Orchestrator { + private static final int CONNECTION_TIMEOUT = 10000; + private String controllerUrl; + private String iofogUuid; + private String iofogAccessToken; + private Certificate controllerCert; + private CloseableHttpClient client; + + private static final String MODULE_NAME = "Orchestrator"; + + public Orchestrator() { + this.update(); + } + + /** + * ping IOFog controller + * + * @return ping result + * @throws Exception + */ + public boolean ping() throws Exception { + logDebug(MODULE_NAME, "Inside ping"); + try { + JsonObject result = getJSON(controllerUrl + "status"); + logDebug(MODULE_NAME, "Finished pinging"); + return !result.isNull("status"); + } catch (Exception exp) { + logError(MODULE_NAME, "Error pinging", new AgentSystemException(exp.getMessage(), exp)); + throw exp; + } + } + + /** + * does provisioning + * + * @param key - provisioning key + * @return result in Json format + * @throws AgentSystemException + */ + public JsonObject provision(String key) throws AgentSystemException { + logDebug(MODULE_NAME, "Inside provision"); + try { + JsonObject result; + JsonObject json = Json.createObjectBuilder() + .add("key", key) + .add("type", Configuration.getFogType().getCode()) + .build(); + + result = request("provision", RequestType.POST, null, json); + logDebug(MODULE_NAME, "Finished provision"); + return result; + } catch (Exception e) { + logError(MODULE_NAME, "Error while provision", new AgentSystemException(e.getMessage(), e)); + throw new AgentSystemException(e.getMessage(), e); + } + } + + private RequestConfig getRequestConfig() throws Exception { + logDebug(MODULE_NAME, "get request config"); + return RequestConfig.copy(RequestConfig.DEFAULT) + .setLocalAddress(IOFogNetworkInterfaceManager.getInstance().getInetAddress()) + .setConnectTimeout(CONNECTION_TIMEOUT) + .build(); + } + + /** + * initialize {@link TrustManager} + * + * @throws Exception + */ + private void initialize(boolean secure) throws AgentSystemException { + logDebug(MODULE_NAME, "Start initialize TrustManager"); + if (secure) { + SSLContext sslContext; + try { + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, TrustManagers.createTrustManager(controllerCert), new SecureRandom()); + SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext); + client = HttpClients.custom().setSSLSocketFactory(sslsf).build(); + } catch (Exception e) { + throw new AgentSystemException(e.getMessage(), e ); + } + + } else { + client = HttpClients.createDefault(); + } + logDebug(MODULE_NAME, "Finished initialize TrustManager"); + } + + /** + * converts {@link InputStream} to {@link Certificate} + * + * @param is - {@link InputStream} + * @return {@link Certificate} + */ + private Certificate getCert(InputStream is) { + logDebug(MODULE_NAME, "Start get Certificate"); + Certificate result = null; + try { + CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509"); + result = certificateFactory.generateCertificate(is); + } catch (CertificateException exp) { + logError(MODULE_NAME, "unable to get certificate", + new AgentUserException(exp.getMessage(), exp)); + } + logDebug(MODULE_NAME, "Finished get Certificate"); + return result; + } + + /** + * gets Json result of a IOFog Controller endpoint + * + * @param surl - endpoind to be called + * @return result in Json format + * @throws AgentSystemException + */ + private JsonObject getJSON(String surl) throws AgentUserException { + logDebug(MODULE_NAME, "Start getJSON for result of a IOFog Controller endpoint"); + // disable certificates for secure mode + boolean secure = true; + if (!surl.toLowerCase().startsWith("https")) { + if (Configuration.isSecureMode()) { + logError(MODULE_NAME, "unable to connect over non-secure connection", + new AgentUserException("unable to connect over non-secure connection", null)); + throw new AgentUserException("unable to connect over non-secure connection", null ); + } else + secure = false; + } + + JsonObject result = null; + + try { + initialize(secure); + RequestConfig config = getRequestConfig(); + HttpGet get = new HttpGet(surl); + get.setConfig(config); + CloseableHttpResponse response = client.execute(get); + + if (response !=null && response.getStatusLine().getStatusCode() != 200) { + if (response.getStatusLine().getStatusCode() == 404) { + logError(MODULE_NAME, "unable to connect to IOFog Controller endpoint", + new AgentUserException("unable to connect to IOFog Controller endpoint", null)); + throw new AgentUserException("unable to connect to IOFog Controller endpoint" , + new UnknownHostException()); + } else { + logError(MODULE_NAME, "unable to connect to IOFog Controller endpoint", + new AgentUserException("unable to connect to IOFog Controller endpoint", null)); + throw new AgentUserException("unable to connect to IOFog Controller endpoint" , null); + } + } + + Reader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8")); + JsonReader jsonReader = Json.createReader(in); + result = jsonReader.readObject(); + + + } catch (UnsupportedEncodingException e) { + logError(MODULE_NAME, "unable to connect to IOFog Controller endpoint", + new AgentUserException(e.getMessage(), e)); + throw new AgentUserException(e.getMessage(), e ); + + } catch (UnsupportedOperationException e) { + logError(MODULE_NAME, "unable to connect to IOFog Controller endpoint", + new AgentUserException(e.getMessage(), e)); + throw new AgentUserException(e.getMessage(), e ); + + } catch (ClientProtocolException e) { + logError(MODULE_NAME, "unable to connect to IOFog Controller endpoint", + new AgentUserException(e.getMessage(), e)); + throw new AgentUserException(e.getMessage(), e ); + + } catch (IOException e) { + try { + IOFogNetworkInterfaceManager.getInstance().updateIOFogNetworkInterface(); + } catch (SocketException | MalformedURLException ex) { + LoggingService.logWarning(MODULE_NAME, "Unable to update network interface : " + ex.getMessage()); + } + logError(MODULE_NAME, "unable to connect to IOFog Controller endpoint", + new AgentUserException(e.getMessage(), e)); + throw new AgentUserException(e.getMessage(), e ); + + }catch (Exception e) { + logError(MODULE_NAME, "unable to connect to IOFog Controller endpoint", + new AgentUserException(e.getMessage(), e)); + throw new AgentUserException(e.getMessage(), e ); + } + logDebug(MODULE_NAME, "Finished getJSON for result of a IOFog Controller endpoint"); + return result; + } + + public JsonObject request(String command, RequestType requestType, Map queryParams, JsonObject json) throws Exception { + if (json == null) { + json = Json.createObjectBuilder().build(); + } + return getJsonObject(queryParams, requestType, new StringEntity(json.toString(), ContentType.APPLICATION_JSON), createUri(command)); + } + + private StringBuilder createUri(String command) { + StringBuilder uri = new StringBuilder(controllerUrl); + uri.append("agent/") + .append(command); + return uri; + } + + + private JsonObject getJsonObject(Map queryParams, RequestType requestType, HttpEntity httpEntity, StringBuilder uri) throws Exception { + // disable certificates for secure mode + logDebug(MODULE_NAME, "Start get JsonObject"); + boolean secure = true; + if (!controllerUrl.toLowerCase().startsWith("https")) { + if (Configuration.isSecureMode()) + throw new AgentUserException("unable to connect over non-secure connection", null); + else + secure = false; + } + + JsonObject result = Json.createObjectBuilder().build(); + + if (queryParams != null) + queryParams.forEach((key, value) -> uri.append("/").append(key) + .append("/").append(value)); + + initialize(secure); + HttpRequestBase req; + + RequestConfig config = getRequestConfig(); + + switch (requestType) { + case GET: + req = new HttpGet(uri.toString()); + break; + case POST: + req = new HttpPost(uri.toString()); + ((HttpPost) req).setEntity(httpEntity); + break; + case PUT: + req = new HttpPut(uri.toString()); + ((HttpPut) req).setEntity(httpEntity); + break; + case PATCH: + req = new HttpPatch(uri.toString()); + ((HttpPatch) req).setEntity(httpEntity); + break; + case DELETE: + req = new HttpDelete(uri.toString()); + break; + default: + req = new HttpGet(uri.toString()); + break; + } + + req.setConfig(config); + + String token = Configuration.getAccessToken(); + if (!StringUtils.isEmpty(token)) { + req.addHeader(new BasicHeader("Authorization", token)); + } + + UUID requestId = UUID.randomUUID(); + req.addHeader("Request-Id", requestId.toString()); + logDebug("Orchestrator", String.format("(%s) %s %s", requestId, requestType.name(), uri.toString())); + + try (CloseableHttpResponse response = client.execute(req)) { + String errorMessage = ""; + HttpEntity responseBody = response.getEntity(); + if (responseBody != null) { + try (BufferedReader in = new BufferedReader(new InputStreamReader(response.getEntity().getContent(), "UTF-8"))) { + JsonReader jsonReader = Json.createReader(in); + result = jsonReader.readObject(); + errorMessage = result.getString("message", ""); + } catch (JsonException e) { + logInfo(MODULE_NAME, "get config response contains non JSON payload, content-type: " + responseBody.getContentType()); + } + } + + int statusCode = response.getStatusLine().getStatusCode(); + switch (statusCode) { + case 204: + return Json.createObjectBuilder().build(); + case 400: + throw new BadRequestException(errorMessage); + case 401: + logWarning(MODULE_NAME, "Invalid authentication ioFog token, switching controller status to Not provisioned"); +// FieldAgent.getInstance().deProvision(true); + throw new AuthenticationException(errorMessage); + case 403: + throw new ForbiddenException(errorMessage); + case 404: + throw new NotFoundException(errorMessage); + case 500: + throw new InternalServerErrorException(errorMessage); + default: + if (statusCode >= 400 && statusCode < 500) { + throw new ClientErrorException(response.getStatusLine().getReasonPhrase(), statusCode); + } else if (statusCode >= 500 && statusCode < 600) { + throw new ServerErrorException(response.getStatusLine().getReasonPhrase(), statusCode); + } + } + + } catch (UnsupportedEncodingException exp) { + logError(MODULE_NAME, "Error while executing the request", new AgentUserException(exp.getMessage(), exp)); + throw new AgentUserException(exp.getMessage(), exp); + } + logDebug(MODULE_NAME, "Finish get JsonObject"); + return result; + } + + /** + * calls IOFog Controller endpoind to send file and returns Json result + * + * @param command - endpoint to be called + * @param file - file to send + * @return result in Json format + * @throws Exception + */ + public void sendFileToController(String command, File file) throws Exception { + logDebug(MODULE_NAME, "Start send file to Controller"); + InputStream inputStream = new FileInputStream(file); + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + builder.addBinaryBody("upstream", inputStream, ContentType.create("application/zip"), file.getName()); + + HttpEntity entity = builder.build(); + + getJsonObject(null, RequestType.PUT, entity, createUri(command)); + logDebug(MODULE_NAME, "Finished send file to Controller"); + } + + /** + * updates local variables when changes applied + */ + public void update() { + logDebug(MODULE_NAME, "Start updates local variables when changes applied"); + iofogUuid = Configuration.getIofogUuid(); + iofogAccessToken = Configuration.getAccessToken(); + controllerUrl = Configuration.getControllerUrl(); + // disable certificates for secure mode + boolean secure = true; + if (controllerUrl.toLowerCase().startsWith("https")) { + try (FileInputStream fileInputStream = new FileInputStream(Configuration.getControllerCert())) { + controllerCert = getCert(fileInputStream); + } catch (IOException e) { + controllerCert = null; + } + } else { + controllerCert = null; + secure = false; + } + try { + initialize(secure); + } catch (AgentSystemException exp) { + logError(MODULE_NAME,"Error while updating local variables when changes applied", + new AgentUserException(exp.getMessage(), exp)); + } catch (Exception exp) { + logError(MODULE_NAME,"Error while updating local variables when changes applied", + new AgentUserException(exp.getMessage(), exp)); + } + logDebug(MODULE_NAME, "Finished updates local variables when changes applied"); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/Configuration.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/Configuration.java new file mode 100644 index 00000000..2727c469 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/Configuration.java @@ -0,0 +1,1514 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils.configuration; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.SystemUtils; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.eclipse.iofog.command_line.CommandLineConfigParam; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.gps.GpsMode; +import org.eclipse.iofog.gps.GpsWebHandler; +import org.eclipse.iofog.message_bus.MessageBus; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.process_manager.ProcessManager; +import org.eclipse.iofog.resource_consumption_manager.ResourceConsumptionManager; +import org.eclipse.iofog.supervisor.Supervisor; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.device_info.ArchitectureType; +import org.eclipse.iofog.utils.functional.Pair; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.xml.sax.SAXException; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.util.*; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.regex.Pattern; + +import static java.io.File.separatorChar; +import static java.lang.String.format; +import static java.util.Arrays.stream; +import static java.util.Collections.list; +import static org.apache.commons.lang.StringUtils.*; +import static org.eclipse.iofog.command_line.CommandLineConfigParam.*; +import static org.eclipse.iofog.utils.CmdProperties.*; +import static org.eclipse.iofog.utils.Constants.*; +import static org.eclipse.iofog.utils.logging.LoggingService.logError; + +/** + * holds IOFog instance configuration + * + * @author saeid + */ +public final class Configuration { + + private static final String MODULE_NAME = "Configuration"; + + private static Element configElement; + private static Document configFile; + private static Element configSwitcherElement; + private static Document configSwitcherFile; + private static ConfigSwitcherState currentSwitcherState; + //Directly configurable params + private static String accessToken; + private static String iofogUuid; + private static String controllerUrl; + private static String controllerCert; + private static String networkInterface; + private static String dockerUrl; + private static float diskLimit; + private static float memoryLimit; + private static String diskDirectory; + private static float cpuLimit; + private static float logDiskLimit; + private static String logDiskDirectory; + private static int logFileCount; + private static String logLevel; + private static int statusFrequency; + private static int changeFrequency; + private static int deviceScanFrequency; + private static int postDiagnosticsFreq; + private static boolean watchdogEnabled; + private static String gpsCoordinates; + private static GpsMode gpsMode; + private static ArchitectureType fogType; + private static final Map defaultConfig; + private static boolean secureMode; + private static String ipAddressExternal; + private static long dockerPruningFrequency; + private static long availableDiskThreshold; + private static int readyToUpgradeScanFrequency; + private static String timeZone; + + + public static boolean debugging = false; + + //Automatic configurable params + private static int statusReportFreqSeconds; + private static int pingControllerFreqSeconds; + private static int speedCalculationFreqMinutes; + private static int monitorContainersStatusFreqSeconds; + private static int monitorRegistriesStatusFreqSeconds; + private static long getUsageDataFreqSeconds; + private static String dockerApiVersion; + private static int setSystemTimeFreqSeconds; + private static int monitorSshTunnelStatusFreqSeconds; + private static String routerHost; + private static int routerPort; + private static boolean devMode; + + public static boolean isDevMode() { + return devMode; + } + + public static void setDevMode(boolean devMode) { + Configuration.devMode = devMode; + } + + public static String getRouterHost() { + return routerHost; + } + + public static void setRouterHost(String routerHost) { + Configuration.routerHost = routerHost; + } + + public static int getRouterPort() { + return routerPort; + } + + public static void setRouterPort(int routerPort) { + Configuration.routerPort = routerPort; + } + + private static void updateAutomaticConfigParams() { + LoggingService.logInfo(MODULE_NAME, "Start update Automatic ConfigParams "); + switch (fogType) { + case ARM: + statusReportFreqSeconds = 10; + pingControllerFreqSeconds = 60; + speedCalculationFreqMinutes = 1; + monitorContainersStatusFreqSeconds = 30; + monitorRegistriesStatusFreqSeconds = 120; + getUsageDataFreqSeconds = 20; + dockerApiVersion = "1.23"; + setSystemTimeFreqSeconds = 60; + monitorSshTunnelStatusFreqSeconds = 30; + break; + case INTEL_AMD: + statusReportFreqSeconds = 5; + pingControllerFreqSeconds = 60; + speedCalculationFreqMinutes = 1; + monitorContainersStatusFreqSeconds = 10; + monitorRegistriesStatusFreqSeconds = 60; + getUsageDataFreqSeconds = 5; + dockerApiVersion = "1.23"; + setSystemTimeFreqSeconds = 60; + monitorSshTunnelStatusFreqSeconds = 10; + break; + } + LoggingService.logInfo(MODULE_NAME, "Finished update Automatic ConfigParams "); + } + + public static int getStatusReportFreqSeconds() { + return statusReportFreqSeconds; + } + + public static int getPingControllerFreqSeconds() { + return pingControllerFreqSeconds; + } + + public static int getSpeedCalculationFreqMinutes() { + return speedCalculationFreqMinutes; + } + + public static int getMonitorSshTunnelStatusFreqSeconds() { + return monitorSshTunnelStatusFreqSeconds; + } + + public static int getMonitorContainersStatusFreqSeconds() { + return monitorContainersStatusFreqSeconds; + } + + public static int getMonitorRegistriesStatusFreqSeconds() { + return monitorRegistriesStatusFreqSeconds; + } + + public static long getGetUsageDataFreqSeconds() { + return getUsageDataFreqSeconds; + } + + public static String getDockerApiVersion() { + return dockerApiVersion; + } + + public static int getSetSystemTimeFreqSeconds() { + return setSystemTimeFreqSeconds; + } + + static { + defaultConfig = new HashMap<>(); + stream(values()).forEach(cmdParam -> defaultConfig.put(cmdParam.getCommandName(), cmdParam.getDefaultValue())); + } + + public static boolean isWatchdogEnabled() { + return watchdogEnabled; + } + + public static void setWatchdogEnabled(boolean watchdogEnabled) { + Configuration.watchdogEnabled = watchdogEnabled; + } + + public static int getStatusFrequency() { + return statusFrequency; + } + + public static void setStatusFrequency(int statusFrequency) { + Configuration.statusFrequency = statusFrequency; + } + + public static int getChangeFrequency() { + return changeFrequency; + } + + public static void setChangeFrequency(int changeFrequency) { + Configuration.changeFrequency = changeFrequency; + } + + public static int getDeviceScanFrequency() { + return deviceScanFrequency; + } + + public static void setDeviceScanFrequency(int deviceScanFrequency) { + Configuration.deviceScanFrequency = deviceScanFrequency; + } + + public static String getGpsCoordinates() { + return gpsCoordinates; + } + + public static void setGpsCoordinates(String gpsCoordinates) { + Configuration.gpsCoordinates = gpsCoordinates; + try { + Configuration.writeGpsToConfigFile(); + } catch (Exception e) { + LoggingService.logError("Configuration", "Error saving GPS coordinates", e); + } + } + + public static GpsMode getGpsMode() { + return gpsMode; + } + + public static void setGpsMode(GpsMode gpsMode) { + Configuration.gpsMode = gpsMode; + } + + public static void resetToDefault() throws Exception { + setConfig(defaultConfig, true); + } + + public static int getPostDiagnosticsFreq() { + return postDiagnosticsFreq; + } + + public static void setPostDiagnosticsFreq(int postDiagnosticsFreq) { + Configuration.postDiagnosticsFreq = postDiagnosticsFreq; + } + + public static ArchitectureType getFogType() { + return fogType; + } + + public static void setFogType(ArchitectureType fogType) { + Configuration.fogType = fogType; + } + + public static boolean isSecureMode() { + return secureMode; + } + + public static void setSecureMode(boolean secureMode) { + Configuration.secureMode = secureMode; + } + + /** + * return XML node value + * + * @param param - node name + * @return node value + * @throws ConfigurationItemException + */ + private static String getNode(CommandLineConfigParam param, Document document) { + + Supplier nodeReader = () -> { + String res = null; + try { + res = getFirstNodeByTagName(param.getXmlTag(), document).getTextContent(); + } catch (ConfigurationItemException e) { + LoggingService.logError(MODULE_NAME, "Error getting node", e); + System.out.println("[" + MODULE_NAME + "] <" + param.getXmlTag() + "> " + + " item not found or defined more than once. Default value - " + param.getDefaultValue() + " will be used"); + + }catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error getting node", e); + System.out.println("[" + MODULE_NAME + "] <" + param.getXmlTag() + "> " + + " item not found or defined more than once. Default value - " + param.getDefaultValue() + " will be used"); + } + return res; + }; + return Optional.ofNullable(nodeReader.get()). + orElseGet(param::getDefaultValue); + } + + /** + * sets XML node value + * + * @param param - node param + * @param content - node value + * @throws ConfigurationItemException + */ + private static void setNode(CommandLineConfigParam param, String content, Document document, Element node) throws ConfigurationItemException { + LoggingService.logDebug(MODULE_NAME, "Start Setting node : " + param.getCommandName()); + createNodeIfNotExists(param.getXmlTag(), document, node); + getFirstNodeByTagName(param.getXmlTag(), document).setTextContent(content); + LoggingService.logDebug(MODULE_NAME, "Finished Setting node : " + param.getCommandName()); + } + + private static void createNodeIfNotExists(String name, Document document, Element node) { + LoggingService.logDebug(MODULE_NAME, "Start create Node IfNotExists : " + name); + NodeList nodes = node.getElementsByTagName(name); + if (nodes.getLength() == 0) { + node.appendChild(document.createElement(name)); + } + LoggingService.logDebug(MODULE_NAME, "Finished create Node IfNotExists : " + name); + } + + /** + * return first XML node from list of nodes found based on provided tag name + * + * @param name - node name + * @return Node object + * @throws ConfigurationItemException + */ + private static Node getFirstNodeByTagName(String name, Document document) throws ConfigurationItemException { + LoggingService.logDebug(MODULE_NAME, "Start get First Node By TagName : " + name); + NodeList nodes = document.getElementsByTagName(name); + + if (nodes.getLength() != 1) { + throw new ConfigurationItemException("<" + name + "> item not found or defined more than once"); + } + LoggingService.logDebug(MODULE_NAME, "Finished get First Node By TagName : " + name); + return nodes.item(0); + } + + public static HashMap getOldNodeValuesForParameters(Set parameters, Document document) throws ConfigurationItemException { + + LoggingService.logDebug(MODULE_NAME, "Start get Old Node Values For Parameters : "); + + HashMap result = new HashMap<>(); + + for (String option : parameters) { + CommandLineConfigParam cmdOption = getCommandByName(option) + .orElseThrow(() -> new ConfigurationItemException("Invalid parameter -" + option)); + result.put(cmdOption.getCommandName(), getNode(cmdOption, document)); + } + + LoggingService.logDebug(MODULE_NAME, "Finished get Old Node Values For Parameters : "); + + return result; + } + + /** + * saves configuration data to config.xml + * and informs other modules + * + * @throws Exception + */ + public static void saveConfigUpdates() throws Exception { + LoggingService.logInfo(MODULE_NAME, "Start updating agent configurations"); + + FieldAgent.getInstance().instanceConfigUpdated(); + ProcessManager.getInstance().instanceConfigUpdated(); + ResourceConsumptionManager.getInstance().instanceConfigUpdated(); + MessageBus.getInstance().instanceConfigUpdated(); +// LoggingService.instanceConfigUpdated(); + + updateConfigFile(getCurrentConfigPath(), configFile); + LoggingService.logInfo(MODULE_NAME, "Finished updating agent configurations"); + } + + public static void updateConfigBackUpFile() { + try { + updateConfigFile(getBackUpConfigPath(), configFile); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error saving backup config File", e); + } + } + + /** + * saves configuration data to config.xml + * + * @throws Exception + */ + private static void updateConfigFile(String filePath, Document newFile) throws Exception { + try { + LoggingService.logInfo(MODULE_NAME, "Start updating configuration data to config.xml"); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + StreamResult result = new StreamResult(new File(filePath)); + DOMSource source = new DOMSource(newFile); + transformer.transform(source, result); + LoggingService.logInfo(MODULE_NAME, "Finished saving configuration data to config.xml"); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error saving config File", e); + throw new AgentSystemException("Error updating config file : "+ filePath, e); + } + } + + /** + * sets configuration base on commandline parameters + * + * @param commandLineMap - map of config parameters + * @throws Exception + */ + public static HashMap setConfig(Map commandLineMap, boolean defaults) throws Exception { + LoggingService.logInfo(MODULE_NAME, "Starting setting configuration base on commandline parameters"); + boolean updateLogger = false; + HashMap messageMap = new HashMap<>(); + if (commandLineMap != null) { + for (Map.Entry command : commandLineMap.entrySet()) { + String option = command.getKey(); + CommandLineConfigParam cmdOption = CommandLineConfigParam.getCommandByName(option).get(); + String value = command.getValue().toString(); + + if (value.startsWith("+")) value = value.substring(1); + + if (isBlank(option) || isBlank(value)) { + if (!option.equals(CONTROLLER_CERT.getCommandName())) { + LoggingService.logInfo(MODULE_NAME, "Parameter error : Command or value is invalid"); + messageMap.put("Parameter error", "Command or value is invalid"); + continue; + } + } + + int intValue; + long longValue; + switch (cmdOption) { + case DISK_CONSUMPTION_LIMIT: + LoggingService.logInfo(MODULE_NAME, "Setting disk consumption limit"); + try { + Float.parseFloat(value); + } catch (Exception e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + + if (Float.parseFloat(value) < 1 || Float.parseFloat(value) > 1048576) { + messageMap.put(option, "Disk limit range must be 1 to 1048576 GB"); + break; + } + setDiskLimit(Float.parseFloat(value)); + setNode(DISK_CONSUMPTION_LIMIT, value, configFile, configElement); + break; + + case DISK_DIRECTORY: + LoggingService.logInfo(MODULE_NAME, "Setting disk directory"); + value = addSeparator(value); + setDiskDirectory(value); + setNode(DISK_DIRECTORY, value, configFile, configElement); + break; + case MEMORY_CONSUMPTION_LIMIT: + LoggingService.logInfo(MODULE_NAME, "Setting memory consumption limit"); + try { + Float.parseFloat(value); + } catch (Exception e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (Float.parseFloat(value) < 128 || Float.parseFloat(value) > 1048576) { + messageMap.put(option, "Memory limit range must be 128 to 1048576 MB"); + break; + } + setMemoryLimit(Float.parseFloat(value)); + setNode(MEMORY_CONSUMPTION_LIMIT, value, configFile, configElement); + break; + case PROCESSOR_CONSUMPTION_LIMIT: + LoggingService.logInfo(MODULE_NAME, "Setting processor consumption limit"); + try { + Float.parseFloat(value); + } catch (Exception e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (Float.parseFloat(value) < 5 || Float.parseFloat(value) > 100) { + messageMap.put(option, "CPU limit range must be 5% to 100%"); + break; + } + setCpuLimit(Float.parseFloat(value)); + setNode(PROCESSOR_CONSUMPTION_LIMIT, value, configFile, configElement); + break; + case CONTROLLER_URL: + LoggingService.logInfo(MODULE_NAME, "Setting controller url"); + setNode(CONTROLLER_URL, value, configFile, configElement); + setControllerUrl(value); + break; + case CONTROLLER_CERT: + LoggingService.logInfo(MODULE_NAME, "Setting controller cert"); + setNode(CONTROLLER_CERT, value, configFile, configElement); + setControllerCert(value); + break; + case DOCKER_URL: + LoggingService.logInfo(MODULE_NAME, "Setting docker url"); + if (value.startsWith("tcp://") || value.startsWith("unix://")) { + setNode(DOCKER_URL, value, configFile, configElement); + setDockerUrl(value); + } else { + messageMap.put(option, "Unsupported protocol scheme. Only 'tcp://' or 'unix://' supported.\n"); + break; + } + break; + case NETWORK_INTERFACE: + LoggingService.logInfo(MODULE_NAME, "Setting disk network interface"); + if (defaults || isValidNetworkInterface(value.trim())) { + setNode(NETWORK_INTERFACE, value, configFile, configElement); + setNetworkInterface(value); + IOFogNetworkInterfaceManager.getInstance().updateIOFogNetworkInterface(); + } else { + messageMap.put(option, "Invalid network interface"); + break; + } + break; + case LOG_DISK_CONSUMPTION_LIMIT: + LoggingService.logInfo(MODULE_NAME, "Setting log disk consumption limit"); + try { + Float.parseFloat(value); + } catch (Exception e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (Float.parseFloat(value) < 0.5 || Float.parseFloat(value) > Constants.MAX_DISK_CONSUMPTION_LIMIT) { + messageMap.put(option, "Log disk limit range must be 0.5 to 100 GB"); + break; + } + setNode(LOG_DISK_CONSUMPTION_LIMIT, value, configFile, configElement); + setLogDiskLimit(Float.parseFloat(value)); + updateLogger = true; + break; + case LOG_DISK_DIRECTORY: + LoggingService.logInfo(MODULE_NAME, "Setting log disk directory"); + value = addSeparator(value); + setNode(LOG_DISK_DIRECTORY, value, configFile, configElement); + setLogDiskDirectory(value); + updateLogger = true; + break; + case LOG_FILE_COUNT: + LoggingService.logInfo(MODULE_NAME, "Setting log file count"); + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (intValue < 1 || intValue > 100) { + messageMap.put(option, "Log file count range must be 1 to 100"); + break; + } + setNode(LOG_FILE_COUNT, value, configFile, configElement); + setLogFileCount(Integer.parseInt(value)); + updateLogger = true; + break; + case LOG_LEVEL: + LoggingService.logInfo(MODULE_NAME, "Setting log level"); + try { + Level.parse(value.toUpperCase()); + } catch (Exception e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + setNode(LOG_LEVEL, value.toUpperCase(), configFile, configElement); + setLogLevel(value.toUpperCase()); + updateLogger = true; + break; + case STATUS_FREQUENCY: + LoggingService.logInfo(MODULE_NAME, "Setting status frequency"); + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (intValue < 1) { + messageMap.put(option, "Status update frequency must be greater than 1"); + break; + } + setNode(STATUS_FREQUENCY, value, configFile, configElement); + setStatusFrequency(Integer.parseInt(value)); + break; + case CHANGE_FREQUENCY: + LoggingService.logInfo(MODULE_NAME, "Setting change frequency"); + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (intValue < 1) { + messageMap.put(option, "Get changes frequency must be greater than 1"); + break; + } + setNode(CHANGE_FREQUENCY, value, configFile, configElement); + setChangeFrequency(Integer.parseInt(value)); + break; + case DEVICE_SCAN_FREQUENCY: + LoggingService.logInfo(MODULE_NAME, "Setting device scan frequency"); + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (intValue < 1) { + messageMap.put(option, "Get scan devices frequency must be greater than 1"); + break; + } + setNode(DEVICE_SCAN_FREQUENCY, value, configFile, configElement); + setDeviceScanFrequency(Integer.parseInt(value)); + break; + case POST_DIAGNOSTICS_FREQ: + LoggingService.logInfo(MODULE_NAME, "Setting post diagnostic frequency"); + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (intValue < 1) { + messageMap.put(option, "Post diagnostics frequency must be greater than 1"); + break; + } + setNode(POST_DIAGNOSTICS_FREQ, value, configFile, configElement); + setPostDiagnosticsFreq(Integer.parseInt(value)); + break; + case WATCHDOG_ENABLED: + LoggingService.logInfo(MODULE_NAME, "Setting watchdog enabled"); + if (!"off".equalsIgnoreCase(value) && !"on".equalsIgnoreCase(value)) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + setNode(WATCHDOG_ENABLED, value, configFile, configElement); + setWatchdogEnabled(!value.equals("off")); + break; + case GPS_MODE: + LoggingService.logInfo(MODULE_NAME, "Setting gps mode"); + try { + configureGps(value, gpsCoordinates); + writeGpsToConfigFile(); + } catch (ConfigurationItemException e){ + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + break; + case FOG_TYPE: + LoggingService.logInfo(MODULE_NAME, "Setting fogtype"); + try { + configureFogType(value); + setNode(FOG_TYPE, value, configFile, configElement); + } catch (ConfigurationItemException e){ + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + break; + case SECURE_MODE: + LoggingService.logInfo(MODULE_NAME, "Setting secure mode"); + setNode(SECURE_MODE, value, configFile, configElement); + setSecureMode(!value.equals("off")); + break; + case ROUTER_HOST: + LoggingService.logInfo(MODULE_NAME, "Setting router host"); + setRouterHost(value); + break; + case ROUTER_PORT: + LoggingService.logInfo(MODULE_NAME, "Setting router port"); + setRouterPort(Integer.parseInt(value)); + break; + case DOCKER_PRUNING_FREQUENCY: + LoggingService.logInfo(MODULE_NAME, "Setting docker pruning frequency"); + try { + longValue = Long.parseLong(value); + } catch (NumberFormatException e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (longValue < 1) { + messageMap.put(option, "Docker pruning frequency must be greater than 1"); + break; + } + setNode(DOCKER_PRUNING_FREQUENCY, value, configFile, configElement); + setDockerPruningFrequency(Long.parseLong(value)); + break; + case AVAILABLE_DISK_THRESHOLD: + LoggingService.logInfo(MODULE_NAME, "Setting available disk threshold"); + try { + longValue = Long.parseLong(value); + } catch (NumberFormatException e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (longValue < 1) { + messageMap.put(option, "Available disk threshold must be greater than 1"); + break; + } + setNode(AVAILABLE_DISK_THRESHOLD, value, configFile, configElement); + setAvailableDiskThreshold(Long.parseLong(value)); + break; + case READY_TO_UPGRADE_SCAN_FREQUENCY: + LoggingService.logInfo(MODULE_NAME, "Setting isReadyToUpgrade scan frequency"); + try { + intValue = Integer.parseInt(value); + } catch (NumberFormatException e) { + messageMap.put(option, "Option -" + option + " has invalid value: " + value); + break; + } + if (intValue < 1) { + messageMap.put(option, "isReadyToUpgrade scan frequency must be greater than 1"); + break; + } + setNode(READY_TO_UPGRADE_SCAN_FREQUENCY, value, configFile, configElement); + setReadyToUpgradeScanFrequency(Integer.parseInt(value)); + FieldAgent.getInstance().changeReadInterval(); + break; + case DEV_MODE: + LoggingService.logInfo(MODULE_NAME, "Setting dev mode"); + setNode(DEV_MODE, value, configFile, configElement); + setDevMode(!value.equals("off")); + break; + case TIME_ZONE: + LoggingService.logInfo(MODULE_NAME, "Setting timeZone"); + setTimeZone(value); + break; + default: + throw new ConfigurationItemException("Invalid parameter -" + option); + } + } + boolean configUpdateError = true; + try { + saveConfigUpdates(); + } catch (Exception e){ + configUpdateError = false; + try { + LoggingService.logError(MODULE_NAME, "Error updating configuration",e); + } catch (Exception ex){ + LoggingService.logError(MODULE_NAME, "This should not happen",e); + } + throw e; + } finally { + if (configUpdateError) { + updateConfigFile(getBackUpConfigPath(), configFile); + } + } + } else { + messageMap.put("invalid", "Option and value are null"); + } + if (updateLogger) { + LoggingService.instanceConfigUpdated(); + } + LoggingService.logInfo(MODULE_NAME, "Finished setting configuration base on commandline parameters"); + + return messageMap; + } + + /** + * Configures fogType. + * + * @param fogTypeCommand could be "auto" or string that matches one of the {@link ArchitectureType} patterns + * @throws ConfigurationItemException if {@link ArchitectureType} undefined + */ + private static void configureFogType(String fogTypeCommand) throws ConfigurationItemException { + LoggingService.logInfo(MODULE_NAME, "Start configure FogType "); + ArchitectureType newFogType = ArchitectureType.UNDEFINED; + switch (fogTypeCommand) { + case "auto": { + newFogType = ArchitectureType.getArchTypeByArchName(System.getProperty("os.arch")); + break; + } + case "intel_amd": { + newFogType = ArchitectureType.INTEL_AMD; + break; + } + case "arm": { + newFogType = ArchitectureType.ARM; + break; + } + } + + if (newFogType == ArchitectureType.UNDEFINED) { + throw new ConfigurationItemException("Couldn't autodetect fogType or unknown fogType type was set."); + } + + setFogType(newFogType); + updateAutomaticConfigParams(); + LoggingService.logInfo(MODULE_NAME, "Finished configure FogType : " + newFogType); + } + + /** + * Configures GPS coordinates and mode in config file + * + * @param gpsModeCommand GPS Mode + * @param gpsCoordinatesCommand lat,lon string (prefer using DD GPS format) + * @throws ConfigurationItemException + */ + private static void configureGps(String gpsModeCommand, String gpsCoordinatesCommand) throws ConfigurationItemException { + LoggingService.logDebug(MODULE_NAME, "Start configures GPS coordinates and mode in config file "); + String gpsCoordinates; + GpsMode currentMode; + + if (GpsMode.AUTO.name().toLowerCase().equals(gpsModeCommand)) { + gpsCoordinates = GpsWebHandler.getGpsCoordinatesByExternalIp(); + if ("".equals(gpsCoordinates) && (gpsCoordinatesCommand == null || !"".equals(gpsCoordinatesCommand))) { + gpsCoordinates = gpsCoordinatesCommand; + } + currentMode = GpsMode.AUTO; + } else if (GpsMode.OFF.name().toLowerCase().equals(gpsModeCommand)) { + gpsCoordinates = ""; + currentMode = GpsMode.OFF; + } else { + if (GpsMode.MANUAL.name().toLowerCase().equals(gpsModeCommand)) { + gpsCoordinates = gpsCoordinatesCommand; + } else { + gpsCoordinates = gpsModeCommand; + } + currentMode = GpsMode.MANUAL; + } + + setGpsDataIfValid(currentMode, gpsCoordinates); + LoggingService.logDebug(MODULE_NAME, "Finished configures GPS coordinates and mode in config file "); + } + + public static void setGpsDataIfValid(GpsMode mode, String gpsCoordinates) throws ConfigurationItemException { + LoggingService.logDebug(MODULE_NAME, "Start set Gps Data If Valid "); + if (!isValidCoordinates(gpsCoordinates)) { + throw new ConfigurationItemException("Incorrect GPS coordinates value: " + gpsCoordinates + "\n" + + "Correct format is (GPS DD format)"); + } + setGpsMode(mode); + if (gpsCoordinates != null && !StringUtils.isBlank(gpsCoordinates) && !gpsCoordinates.isEmpty()) { + setGpsCoordinates(gpsCoordinates.trim()); + } + LoggingService.logDebug(MODULE_NAME, "Finished set Gps Data If Valid "); + } + + /** + * Writes GPS coordinates and GPS mode to config file + * + * @throws ConfigurationItemException + */ + public static void writeGpsToConfigFile() throws ConfigurationItemException { + + LoggingService.logDebug(MODULE_NAME, "Start writing GPS coordinates and GPS mode to config file"); + + setNode(GPS_MODE, gpsMode.name().toLowerCase(), configFile, configElement); + setNode(GPS_COORDINATES, gpsCoordinates, configFile, configElement); + + LoggingService.logDebug(MODULE_NAME, "Finished writing GPS coordinates and GPS mode to config file"); + } + + /** + * Checks is string a valid DD GPS coordinates + * + * @param gpsCoordinates + * @return + */ + private static boolean isValidCoordinates(String gpsCoordinates) { + + LoggingService.logDebug(MODULE_NAME, "Start is Valid Coordinates "); + + boolean isValid = true; + + String fpRegex = "[+-]?[0-9]+(.?[0-9]+)?,?" + + "[+-]?[0-9]+(.?[0-9]+)?"; + + if (Pattern.matches(fpRegex, gpsCoordinates)) { + + String[] latLon = gpsCoordinates.split(","); + double lat = Double.parseDouble(latLon[0]); + double lon = Double.parseDouble(latLon[1]); + + if (lat > 90 || lat < -90 || lon > 180 || lon < -180) { + isValid = false; + } + } else { + isValid = gpsCoordinates.isEmpty(); + } + + LoggingService.logDebug(MODULE_NAME, "Start is Valid Coordinates : " + isValid); + + return isValid; + } + + /** + * checks if given network interface is valid + * + * @param eth - network interface + * @return + */ + private static boolean isValidNetworkInterface(String eth) { + LoggingService.logDebug(MODULE_NAME, "Start is Valid network interface "); + + if (SystemUtils.IS_OS_WINDOWS) { // any name could be used for network interface on Win + return true; + } + + try { + if (CommandLineConfigParam.NETWORK_INTERFACE.getDefaultValue().equals(eth)) { + LoggingService.logDebug(MODULE_NAME, "Finished is Valid network interface : true"); + return true; + } + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface networkInterface : list(networkInterfaces)) { + if (networkInterface.getName().equalsIgnoreCase(eth)) + LoggingService.logDebug(MODULE_NAME, "Finished is Valid network interface : true"); + return true; + } + } catch (Exception e) { + logError(MODULE_NAME, "Error validating network interface", new AgentUserException(e.getMessage(), e)); + } + LoggingService.logDebug(MODULE_NAME, "Finished is Valid network interface : false"); + return false; + } + + /** + * adds file separator to end of directory names, if not exists + * + * @param value - name of directory + * @return directory containing file separator at the end + */ + private static String addSeparator(String value) { + if (value.charAt(value.length() - 1) == separatorChar) + return value; + else + return value + separatorChar; + } + + + /** + * loads configuration from config.xml file + * + * @throws ConfigurationItemException + */ + public static void loadConfig() throws ConfigurationItemException { + LoggingService.logInfo(MODULE_NAME, "Start load Config"); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = null; + boolean isConfigError = false; + try { + builder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + LoggingService.logError(MODULE_NAME, "Error while parsing config xml", new ConfigurationItemException(e.getMessage(), e)); + throw new ConfigurationItemException(e.getMessage(), e); + } + + try { + configFile = builder.parse(getCurrentConfigPath()); + } catch (Exception e) { + isConfigError = true; + LoggingService.logError(MODULE_NAME, "Error while parsing config xml", new ConfigurationItemException("Error while parsing config xml", e)); + } + if (isConfigError) { + try { + configFile = builder.parse(getBackUpConfigPath()); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error while parsing backup config xml", new ConfigurationItemException("Error while parsing config xml", e)); + throw new ConfigurationItemException("Error while parsing config xml and backup config xml"); + } + } + + configFile.getDocumentElement().normalize(); + + configElement = (Element) getFirstNodeByTagName("config", configFile); + + setIofogUuid(getNode(IOFOG_UUID, configFile)); + setAccessToken(getNode(ACCESS_TOKEN, configFile)); + setControllerUrl(getNode(CONTROLLER_URL, configFile)); + setControllerCert(getNode(CONTROLLER_CERT, configFile)); + setNetworkInterface(getNode(NETWORK_INTERFACE, configFile)); + setDockerUrl(getNode(DOCKER_URL, configFile)); + setDiskLimit(Float.parseFloat(getNode(DISK_CONSUMPTION_LIMIT, configFile))); + setDiskDirectory(getNode(DISK_DIRECTORY, configFile)); + setMemoryLimit(Float.parseFloat(getNode(MEMORY_CONSUMPTION_LIMIT, configFile))); + setCpuLimit(Float.parseFloat(getNode(PROCESSOR_CONSUMPTION_LIMIT, configFile))); + setLogDiskDirectory(getNode(LOG_DISK_DIRECTORY, configFile)); + setLogDiskLimit(Float.parseFloat(getNode(LOG_DISK_CONSUMPTION_LIMIT, configFile))); + setLogFileCount(Integer.parseInt(getNode(LOG_FILE_COUNT, configFile))); + setLogLevel(getNode(LOG_LEVEL, configFile)); + configureGps(getNode(GPS_MODE, configFile), getNode(GPS_COORDINATES, configFile)); + setChangeFrequency(Integer.parseInt(getNode(CHANGE_FREQUENCY, configFile))); + setDeviceScanFrequency(Integer.parseInt(getNode(DEVICE_SCAN_FREQUENCY, configFile))); + setStatusFrequency(Integer.parseInt(getNode(STATUS_FREQUENCY, configFile))); + setPostDiagnosticsFreq(Integer.parseInt(getNode(POST_DIAGNOSTICS_FREQ, configFile))); + setWatchdogEnabled(!getNode(WATCHDOG_ENABLED, configFile).equals("off")); + configureFogType(getNode(FOG_TYPE, configFile)); + setSecureMode(!getNode(SECURE_MODE, configFile).equals("off")); + setIpAddressExternal(GpsWebHandler.getExternalIp()); + setRouterHost(getNode(ROUTER_HOST, configFile)); + setRouterPort(!getNode(ROUTER_PORT, configFile).equals("") ? Integer.parseInt(getNode(ROUTER_PORT, configFile)) : 0); + + setDockerPruningFrequency(Long.parseLong(getNode(DOCKER_PRUNING_FREQUENCY, configFile))); + setAvailableDiskThreshold(Long.parseLong(getNode(AVAILABLE_DISK_THRESHOLD, configFile))); + setReadyToUpgradeScanFrequency(Integer.parseInt(getNode(READY_TO_UPGRADE_SCAN_FREQUENCY, configFile))); + setDevMode(!getNode(DEV_MODE, configFile).equals("off")); + configureTimeZone(getNode(TIME_ZONE, configFile)); + + try { + updateConfigFile(getCurrentConfigPath(), configFile); + } catch (Exception e) { + try { + LoggingService.logError(MODULE_NAME, "Error saving config", e); + } catch (Exception ex) { + LoggingService.logError(MODULE_NAME, "This error should not print ever on loadConfig!", new AgentSystemException("Error Logging exception in saving config updates on loadConfig")); + } + } finally { + try { + updateConfigFile(getBackUpConfigPath(), configFile); + } catch (Exception e) { + LoggingService.logError(MODULE_NAME, "Error saving config back up file", e); + } + } + LoggingService.logInfo(MODULE_NAME, "Finished load Config"); + } + + /** + * loads configuration about current config from config-switcher.xml + * + * @throws ConfigurationItemException + */ + public static void loadConfigSwitcher() throws ConfigurationItemException { + LoggingService.logInfo(MODULE_NAME, "Start loads configuration about current config from config-switcher.xml"); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = null; + try { + builder = factory.newDocumentBuilder(); + } catch (ParserConfigurationException e) { + LoggingService.logError(MODULE_NAME, "Error while parsing config switcher xml", e); + throw new ConfigurationItemException(e.getMessage(), e); + } + + try { + configSwitcherFile = builder.parse(CONFIG_SWITCHER_PATH); + } catch (SAXException e) { + LoggingService.logError(MODULE_NAME, "Error while parsing config switcher xml", + new ConfigurationItemException(e.getMessage(), e)); + throw new ConfigurationItemException(e.getMessage(), e); + } catch (IOException e) { + LoggingService.logError(MODULE_NAME, "Error while parsing config switcher xml", + new ConfigurationItemException(e.getMessage(), e)); + throw new ConfigurationItemException(e.getMessage(), e); + } + configSwitcherFile.getDocumentElement().normalize(); + + configSwitcherElement = (Element) getFirstNodeByTagName(SWITCHER_ELEMENT, configSwitcherFile); + + verifySwitcherNode(SWITCHER_NODE, ConfigSwitcherState.DEFAULT.fullValue()); + LoggingService.logInfo(MODULE_NAME, "Finished loading configuration about current config from config-switcher.xml"); + } + + // this code will be triggered in case of iofog updated (not newly installed) and add new option for config + private static void createConfigProperty(CommandLineConfigParam cmdParam) throws Exception { + LoggingService.logDebug(MODULE_NAME, "Start create config property"); + // TODO: add appropriate handling of case when 0 nodes found or multiple before adding new property to file + Element el = configFile.createElement(cmdParam.getXmlTag()); + el.appendChild(configFile.createTextNode(cmdParam.getDefaultValue())); + configElement.appendChild(el); + + DOMSource source = new DOMSource(configFile); + TransformerFactory transformerFactory = TransformerFactory.newInstance(); + Transformer transformer = transformerFactory.newTransformer(); + StreamResult result = new StreamResult(getCurrentConfigPath()); + transformer.transform(source, result); + LoggingService.logDebug(MODULE_NAME, "Finished create config property"); + } + + public static String getAccessToken() { + return accessToken; + } + + public static String getControllerUrl() { + return controllerUrl; + } + + public static String getControllerCert() { + return controllerCert; + } + + public static String getNetworkInterface() { + return networkInterface; + } + + public static String getDockerUrl() { + return dockerUrl; + } + + public static float getDiskLimit() { + return diskLimit; + } + + public static float getMemoryLimit() { + return memoryLimit; + } + + public static String getDiskDirectory() { + return diskDirectory; + } + + public static float getCpuLimit() { + return cpuLimit; + } + + public static String getIofogUuid() { + return iofogUuid; + } + + public static int getLogFileCount() { + return logFileCount; + } + + public static float getLogDiskLimit() { + return logDiskLimit; + } + + public static String getLogDiskDirectory() { + return logDiskDirectory; + } + + public static void setLogDiskDirectory(String logDiskDirectory) { + LoggingService.logDebug(MODULE_NAME, "Start set Log Disk Directory"); + if (logDiskDirectory.charAt(0) != separatorChar) + logDiskDirectory = separatorChar + logDiskDirectory; + if (logDiskDirectory.charAt(logDiskDirectory.length() - 1) != separatorChar) + logDiskDirectory += separatorChar; + Configuration.logDiskDirectory = SNAP_COMMON + logDiskDirectory; + LoggingService.logDebug(MODULE_NAME, "Finished set Log Disk Directory"); + } + + public static void setAccessToken(String accessToken) throws ConfigurationItemException { + LoggingService.logDebug(MODULE_NAME, "Start set access token"); + setNode(ACCESS_TOKEN, accessToken, configFile, configElement); + Configuration.accessToken = accessToken; + LoggingService.logDebug(MODULE_NAME, "Finished set access token"); + } + + public static void setIofogUuid(String iofogUuid) throws ConfigurationItemException { + LoggingService.logDebug(MODULE_NAME, "Start set Iofog uuid"); + setNode(IOFOG_UUID, iofogUuid, configFile, configElement); + Configuration.iofogUuid = iofogUuid; + LoggingService.logDebug(MODULE_NAME, "Finished set Iofog uuid"); + } + + private static void verifySwitcherNode(String switcher, String defaultValue) throws ConfigurationItemException { + LoggingService.logInfo(MODULE_NAME, "Start verify Switcher Node"); + + NodeList nodes = configSwitcherElement.getElementsByTagName(switcher); + if (nodes.getLength() == 0) { + configSwitcherElement.appendChild(configSwitcherFile.createElement(switcher)); + getFirstNodeByTagName(switcher, configSwitcherFile).setTextContent(defaultValue); + currentSwitcherState = ConfigSwitcherState.DEFAULT; + } else { + String currentState = getFirstNodeByTagName(switcher, configSwitcherFile).getTextContent(); + try { + currentSwitcherState = ConfigSwitcherState.parse(currentState); + } catch (IllegalArgumentException e) { + currentSwitcherState = ConfigSwitcherState.DEFAULT; + System.out.println("Error while reading current switcher state, using default config"); + LoggingService.logError(MODULE_NAME, "Error while reading current switcher state, using default config", + new ConfigurationItemException(e.getMessage(), e)); + throw new ConfigurationItemException(e.getMessage(), e); + } + } + LoggingService.logInfo(MODULE_NAME, "Finished verify Switcher Node"); + } + + private static void setControllerUrl(String controllerUrl) { + LoggingService.logDebug(MODULE_NAME, "Set ControllerUrl"); + if (controllerUrl != null && controllerUrl.length() > 0 && controllerUrl.charAt(controllerUrl.length() - 1) != '/') + controllerUrl += '/'; + Configuration.controllerUrl = controllerUrl; + } + + private static void setControllerCert(String controllerCert) { + Configuration.controllerCert = SNAP_COMMON + controllerCert; + } + + private static void setNetworkInterface(String networkInterface) { + Configuration.networkInterface = networkInterface; + } + + private static void setDockerUrl(String dockerUrl) { + Configuration.dockerUrl = dockerUrl; + } + + private static void setDiskLimit(float diskLimit) { + Configuration.diskLimit = diskLimit; + } + + private static void setMemoryLimit(float memoryLimit) { + Configuration.memoryLimit = memoryLimit; + } + + private static void setDiskDirectory(String diskDirectory) { + if (diskDirectory.charAt(0) != separatorChar) + diskDirectory = separatorChar + diskDirectory; + if (diskDirectory.charAt(diskDirectory.length() - 1) != separatorChar) + diskDirectory += separatorChar; + Configuration.diskDirectory = SNAP_COMMON + diskDirectory; + } + + private static void setCpuLimit(float cpuLimit) { + Configuration.cpuLimit = cpuLimit; + } + + private static void setLogDiskLimit(float logDiskLimit) { + Configuration.logDiskLimit = logDiskLimit; + } + + private static void setLogFileCount(int logFileCount) { + Configuration.logFileCount = logFileCount; + } + + /** + * returns report for "info" commandline parameter + * + * @return info report + */ + public static String getConfigReport() { + LoggingService.logDebug(MODULE_NAME, "Start get Config Report"); + String ipAddress = IOFogNetworkInterfaceManager.getInstance().getCurrentIpAddress(); + String networkInterface = getNetworkInterfaceInfo(); + ipAddress = "".equals(ipAddress) ? "unable to retrieve ip address" : ipAddress; + + StringBuilder result = new StringBuilder(); + // iofog UUID + result.append(buildReportLine(getIofogUuidMessage(), isNotBlank(iofogUuid) ? iofogUuid : "not provisioned")); + //ip address + result.append(buildReportLine(getIpAddressMessage(), ipAddress)); + // network interface + result.append(buildReportLine(getConfigParamMessage(NETWORK_INTERFACE), networkInterface)); + // secure mode + result.append(buildReportLine(getConfigParamMessage(SECURE_MODE), (secureMode ? "on" : "off"))); + // controller url + result.append(buildReportLine(getConfigParamMessage(CONTROLLER_URL), controllerUrl)); + // controller cert dir + result.append(buildReportLine(getConfigParamMessage(CONTROLLER_CERT), controllerCert)); + // docker url + result.append(buildReportLine(getConfigParamMessage(DOCKER_URL), dockerUrl)); + // disk usage limit + result.append(buildReportLine(getConfigParamMessage(DISK_CONSUMPTION_LIMIT), format("%.2f GiB", diskLimit))); + // disk directory + result.append(buildReportLine(getConfigParamMessage(DISK_DIRECTORY), diskDirectory)); + // memory ram limit + result.append(buildReportLine(getConfigParamMessage(MEMORY_CONSUMPTION_LIMIT), format("%.2f MiB", memoryLimit))); + // cpu usage limit + result.append(buildReportLine(getConfigParamMessage(PROCESSOR_CONSUMPTION_LIMIT), format("%.2f%%", cpuLimit))); + // log disk limit + result.append(buildReportLine(getConfigParamMessage(LOG_DISK_CONSUMPTION_LIMIT), format("%.2f GiB", logDiskLimit))); + // log file directory + result.append(buildReportLine(getConfigParamMessage(LOG_DISK_DIRECTORY), logDiskDirectory)); + // log files count + result.append(buildReportLine(getConfigParamMessage(LOG_FILE_COUNT), format("%d", logFileCount))); + // log files level + result.append(buildReportLine(getConfigParamMessage(LOG_LEVEL), format("%s", logLevel))); + // status update frequency + result.append(buildReportLine(getConfigParamMessage(STATUS_FREQUENCY), format("%d", statusFrequency))); + // status update frequency + result.append(buildReportLine(getConfigParamMessage(CHANGE_FREQUENCY), format("%d", changeFrequency))); + // scan devices frequency + result.append(buildReportLine(getConfigParamMessage(DEVICE_SCAN_FREQUENCY), format("%d", deviceScanFrequency))); + // post diagnostics frequency + result.append(buildReportLine(getConfigParamMessage(POST_DIAGNOSTICS_FREQ), format("%d", postDiagnosticsFreq))); + // log file directory + result.append(buildReportLine(getConfigParamMessage(WATCHDOG_ENABLED), (watchdogEnabled ? "on" : "off"))); + // gps mode + result.append(buildReportLine(getConfigParamMessage(GPS_MODE), gpsMode.name().toLowerCase())); + // gps coordinates + result.append(buildReportLine(getConfigParamMessage(GPS_COORDINATES), gpsCoordinates)); + //fog type + result.append(buildReportLine(getConfigParamMessage(FOG_TYPE), fogType.name().toLowerCase())); + // docker pruning frequency + result.append(buildReportLine(getConfigParamMessage(DOCKER_PRUNING_FREQUENCY), format("%d", dockerPruningFrequency))); + // available disk threshold + result.append(buildReportLine(getConfigParamMessage(AVAILABLE_DISK_THRESHOLD), format("%d", availableDiskThreshold))); + // is ready to upgrade scan frequency + result.append(buildReportLine(getConfigParamMessage(READY_TO_UPGRADE_SCAN_FREQUENCY), format("%d", readyToUpgradeScanFrequency))); + // dev mode + result.append(buildReportLine(getConfigParamMessage(DEV_MODE), (devMode ? "on" : "off"))); + // timeZone + result.append(buildReportLine(getConfigParamMessage(TIME_ZONE), timeZone)); + LoggingService.logDebug(MODULE_NAME, "Finished get Config Report"); + + return result.toString(); + } + + private static String buildReportLine(String messageDescription, String value) { + return rightPad(messageDescription, 40, ' ') + " : " + value + "\\n"; + } + + public static String getNetworkInterfaceInfo() { + LoggingService.logDebug(MODULE_NAME, "get Network Interface Info"); + if (!NETWORK_INTERFACE.getDefaultValue().equals(networkInterface)) { + return networkInterface; + } + + Pair connectedAddress = IOFogNetworkInterfaceManager.getInstance().getNetworkInterface(); + String networkInterfaceName = connectedAddress == null ? "not found" : connectedAddress._1().getName(); + return networkInterfaceName + "(" + NETWORK_INTERFACE.getDefaultValue() + ")"; + } + + public static Document getCurrentConfig() { + return configFile; + } + + public static String getCurrentConfigPath() { + switch (currentSwitcherState) { + case DEVELOPMENT: + return Constants.DEVELOPMENT_CONFIG_PATH; + case PRODUCTION: + return Constants.PRODUCTION_CONFIG_PATH; + case DEFAULT: + default: + return Constants.DEFAULT_CONFIG_PATH; + } + } + + public static String getBackUpConfigPath() { + return Constants.BACKUP_CONFIG_PATH; + } + + public static String setupConfigSwitcher(ConfigSwitcherState state) { + ConfigSwitcherState previousState = currentSwitcherState; + if (state.equals(previousState)) { + return "Already using this configuration."; + } + return reload(state, previousState); + } + + /** + * loads config-switcher.xml and config-*.xml file + */ + public static void load() { + try { + Configuration.loadConfigSwitcher(); + } catch (ConfigurationItemException e) { + System.out.println("invalid configuration item(s)."); + System.out.println(e.getMessage()); + System.out.println(ExceptionUtils.getFullStackTrace(e)); + System.exit(1); + } catch (Exception e) { + System.out.println("Error while parsing " + Constants.CONFIG_SWITCHER_PATH); + System.out.println(e.getMessage()); + System.out.println(ExceptionUtils.getFullStackTrace(e)); + System.exit(1); + } + + try { + Configuration.loadConfig(); + } catch (ConfigurationItemException e) { + System.out.println("invalid configuration item(s)."); + System.out.println(e.getMessage()); + System.out.println(ExceptionUtils.getFullStackTrace(e)); + System.exit(1); + } catch (Exception e) { + System.out.println("Error while parsing " + Configuration.getCurrentConfigPath()); + System.out.println(e.getMessage()); + System.out.println(ExceptionUtils.getFullStackTrace(e)); + System.exit(1); + } + + } + + private static String reload(ConfigSwitcherState newState, ConfigSwitcherState previousState) { + try { + getFirstNodeByTagName(SWITCHER_NODE, configSwitcherFile).setTextContent(newState.fullValue()); + updateConfigFile(CONFIG_SWITCHER_PATH, configSwitcherFile); + + Configuration.loadConfigSwitcher(); + Configuration.loadConfig(); + + FieldAgent.getInstance().instanceConfigUpdated(); + ProcessManager.getInstance().instanceConfigUpdated(); + ResourceConsumptionManager.getInstance().instanceConfigUpdated(); + MessageBus.getInstance().instanceConfigUpdated(); + + return "Successfully switched to new configuration."; + } catch (Exception e) { + try { + getFirstNodeByTagName(SWITCHER_NODE, configSwitcherFile).setTextContent(previousState.fullValue()); + updateConfigFile(CONFIG_SWITCHER_PATH, configSwitcherFile); + + load(); + + FieldAgent.getInstance().instanceConfigUpdated(); + ProcessManager.getInstance().instanceConfigUpdated(); + ResourceConsumptionManager.getInstance().instanceConfigUpdated(); + MessageBus.getInstance().instanceConfigUpdated(); + + return "Error while loading new config file, falling back to current configuration"; + } catch (Exception fatalException) { + System.out.println("Error while loading previous configuration, try to restart iofog-agent"); + System.exit(1); + return ""; + } + } + } + + public static void setupSupervisor() { + LoggingService.logInfo(MODULE_NAME, "Starting supervisor"); + + try { + Supervisor supervisor = new Supervisor(); + supervisor.start(); + } catch (Exception exp) { + LoggingService.logError(MODULE_NAME, "Error while starting supervisor", new AgentSystemException("Error while starting supervisor", exp)); + } + LoggingService.logInfo(MODULE_NAME, "Started supervisor"); + } + + public static String getIpAddressExternal() { + return ipAddressExternal; + } + + public static void setIpAddressExternal(String ipAddressExternal) { + Configuration.ipAddressExternal = ipAddressExternal; + } + + public static String getLogLevel() { + return logLevel; + } + + public static void setLogLevel(String logLevel) { + Configuration.logLevel = logLevel; + } + + public static long getDockerPruningFrequency() { + return dockerPruningFrequency; + } + + public static void setDockerPruningFrequency(long dockerPruningFrequency) { + Configuration.dockerPruningFrequency = dockerPruningFrequency; + } + + public static long getAvailableDiskThreshold() { + return availableDiskThreshold; + } + + public static void setAvailableDiskThreshold(long availableDiskThreshold) { + Configuration.availableDiskThreshold = availableDiskThreshold; + } + + public static int getReadyToUpgradeScanFrequency() { + return readyToUpgradeScanFrequency; + } + + public static void setReadyToUpgradeScanFrequency(int readyToUpgradeScanFrequency) { + Configuration.readyToUpgradeScanFrequency = readyToUpgradeScanFrequency; + } + + + /** + * Configures the default timezone of the node + * @param timeZone + */ + private static void configureTimeZone(String timeZone) throws ConfigurationItemException { + LoggingService.logDebug(MODULE_NAME, "Configuring timezone"); + TimeZone zone; + String tzId; + if ("".equals(timeZone)) { + zone = TimeZone.getDefault(); + TimeZone.setDefault(zone); + tzId = zone.getID(); + setTimeZone(tzId); + } else { + setTimeZone(timeZone); + } + } + + public static String getTimeZone() { + return timeZone; + } + + public static void setTimeZone(String timeZone) throws ConfigurationItemException { + LoggingService.logDebug(MODULE_NAME, "Start set timeZone"); + setNode(TIME_ZONE, timeZone, configFile, configElement); + Configuration.timeZone = timeZone; + LoggingService.logDebug(MODULE_NAME, "Finished set timeZone"); + + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/ConfigurationItemException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/ConfigurationItemException.java new file mode 100644 index 00000000..d4e37158 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/ConfigurationItemException.java @@ -0,0 +1,28 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils.configuration; + +public class ConfigurationItemException extends Exception { + + private static final long serialVersionUID = 1L; + + public ConfigurationItemException(String message) { + super(message); + } + public ConfigurationItemException(String message, Throwable cause) { + super(message, cause); + } + public ConfigurationItemException(Throwable cause) { + super(cause); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/device_info/ArchitectureType.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/device_info/ArchitectureType.java new file mode 100644 index 00000000..f194709a --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/device_info/ArchitectureType.java @@ -0,0 +1,81 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.device_info; + +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; + +import java.util.Arrays; +import java.util.List; + +public enum ArchitectureType { + UNDEFINED("") { + @Override + public int getCode() { + return 0; + } + }, + /** + * common type for popular intel and amd architectures: + * x8664, x86_64, amd64, ia32e, em64t, x64, x8632, x86_32, x86, i386, i486, i586, i686, ia32, x32 + */ + INTEL_AMD("^(x86[_]?64|amd64|ia32e|em64t|x64|x86[_]?32|x86|i[3-6]86|ia32|x32)$") { + @Override + public int getCode() { + return 1; + } + }, + /** + * common type for popular arm architectures: + * arm, arm32, aarch64, armv{VERSION}{CODE} + */ + ARM("^(arm|arm32|armv[0-9]+.*|aarch64)$") { + @Override + public int getCode() { + return 2; + } + }; + + private final String pattern; + + ArchitectureType(String pattern) { + this.pattern = pattern; + } + + public abstract int getCode(); + + public static ArchitectureType getArchTypeByArchName(String archName) { + return Arrays.stream(ArchitectureType.values()) + .filter(el -> archName.matches(el.pattern)) + .findFirst().orElse(UNDEFINED); + } + + /** + * Gets device arch name by "uname -m" command + * + * @return device arch name + */ + private static String getDeviceArchName() { + return CommandShellExecutor.executeCommand("uname -m").getValue().get(0); + } + + /** + * Gets device arch type by "uname -m" command + * + * @return device arch name + */ + public static ArchitectureType getDeviceArchType() { + return getArchTypeByArchName(getDeviceArchName()); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Either.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Either.java new file mode 100755 index 00000000..9bf36054 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Either.java @@ -0,0 +1,146 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.functional; + + +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Class that models Sum types, or binary CoProducts.Note that the universal + * mapping property (UMP) of this binary CoProduct is not enforced inside the + * implementation, but must be validate in tests. A binary CoProduct of + * A, B is The triple + * (Either Either, {right()}:: B -> Either) + * such that if there is another triple with the same shape: + * (C, pi1():: A ->Either, pi2():: B -> Either) then + * there is UNIQUE function (really need not be a function, just + * arrow) from + * :: Either -> C, such that pi1() = o f and pi2()= o g + * Note that : right() and left() are called injection functions and Either is written (A + B) + * + * Created by ekrylovich + * on 13.6.17. + */ +public abstract class Either { + + /** + * Applies the given function for the value of type B it's + * {@link Right} otherwise do nothing + * + * @param f + * the function to apply when this is instance of {@link Right} + * @return An new instance of Either by applying f to the value inside + * a Right instance otherwise return this + */ + public abstract Either map(final Function f); + + /** + * Applies the given function if this instance is {@link Right} and then + * flatten the result, because when applying the function we add one layer + * of Either, returns this otherwise. + * + * @param f + * function to apply to the value inside a {@link Right} instance + * @return The flatten result after applying f it this is instance of + * {@link Right} otherwise returns this unmodified + */ + public abstract Either bind(final Function> f); + + /** + * Return this instance if it's {@link Right} otherwise f.app(); + * + * @param f + * the function that will return a fall-back value + * @return this instance if it's {@link Right} f.apply() otherwise + */ + public abstract Either orElse(final Supplier> f); + + /** + * Tells whether this instance is {@link Left} or not. + * + * @return true if this is instance of {@link Left} + */ + public abstract boolean isLeft(); + + /** + * Tells whether this instance is {@link Right} or not. + * + * @return true if this is instance of {@link Right} + */ + public abstract boolean isRight(); + + /** + * Returns the value inside a {@link Left} instance otherwise throws an + * exception if this is an instance of {@link Right} + * + * @return the value inside a {@link Left} instance + */ + public abstract A leftValue(); + + /** + * Returns the value inside a {@link Right} instance otherwise throws an + * exception if this is an instance of {@link Left} + * + * @return the value inside a {@link Right} instance + */ + public abstract B rightValue(); + + /** + * Left injection function + * + * @return + */ + public static Either left(final A value) { + return new Left<>(value); + } + + /** + * Right injection function + * + * @return + */ + public static Either right(final B value) { + return new Right<>(value); + } + + public static Function> pointRight() { + return Either::right; + } + + /** + * Returns a Function that injects on the left + * + * @return + */ + public static Function> pointLeft() { + return Either::left; + } + + /** + * Applies one of the given functions depending on whether this is Left or + * Right. If this is instance of Left then apply left otherwise + * apply right + * + * @param left + * The function to call if this is left. + * @param right + * The function to call if this is right. + * @return The resulting value. + */ + public final C either(final Function left, final Function right) { + return isLeft() ? left.apply(leftValue()) : right.apply(rightValue()); + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function3.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function3.java new file mode 100755 index 00000000..eb7fb087 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function3.java @@ -0,0 +1,23 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.functional; + +/** + * Created by ekrylovich + * on 13.6.17. + */ +@FunctionalInterface +public interface Function3 { + D apply(final A a, final B b, final C c); +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function4.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function4.java new file mode 100755 index 00000000..16874974 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function4.java @@ -0,0 +1,23 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.functional; + +/** + * Created by ekrylovich + * on 15.6.17. + */ +@FunctionalInterface +public interface Function4 { + E apply(final A a, final B b, final C c, final D d); +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Functions.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Functions.java new file mode 100644 index 00000000..f850fa43 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Functions.java @@ -0,0 +1,101 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.functional; + + +import java.util.List; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + +import static java.util.Optional.empty; +import static org.eclipse.iofog.utils.functional.Unit.UNIT; + + +/** + * Created by ekrylovich + * on 13.6.17. + */ +public final class Functions { + private Functions() { + throw new UnsupportedOperationException("com.digitalfuel.util.Functions could not be instantiated as it is util class"); + } + + public static Function> alwaysPresent() { + return Optional::of; + } + + public static Function> alwaysAbsent() { + return value -> empty(); + } + + public static Optional bind(final Optional value, final Function> f) { + return value.flatMap(f::apply); + } + + public static Function, Function>, Optional>> bind() { + return value -> f -> bind(value, f); + } + + public static Function> composeM(final Function> g, final Function> f) { + return value -> bind(f.apply(value), g); + } + + public static Function>, Function>, Function>>> composeM() { + return g -> f -> composeM(g, f); + } + + public static BiFunction flip(final BiFunction f) { + return (b, a) -> f.apply(a, b); + } + + public static Function> flip(final Function> f) { + return first -> second -> f.apply(second).apply(first); + } + + public static Function> curry(final BiFunction f) { + return first -> (Function) second -> f.apply(first, second); + } + + public static BiFunction unCurry(final Function> f) { + return (first, second) -> f.apply(first).apply(second); + } + + public static Function>> curry(final Function3 f) { + return a -> (Function>) b -> (Function) c -> f.apply(a, b, c); + } + public static Function andThen(final Function f, final Function g) { + return f.andThen(g); + } + public static Optional apply(final Optional val1, final Optional val2, final Function> f) { + return val1.flatMap(a -> val2.map(b -> f.apply(a).apply(b))); + } + + public static Function apply(final Function3 f, final A a, final B b) { + return c -> f.apply(a, b, c); + } + + public static Either fromOptional(final Optional value) { + return value.map((Function>) Either::right).orElse(Either.left(UNIT)); + } + + public static Function> constant() { + return curry((a, b) -> a); + } + + public static Predicate> nullOrEmpty() { + return list -> list == null || list.isEmpty(); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Left.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Left.java new file mode 100755 index 00000000..7721bae5 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Left.java @@ -0,0 +1,64 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.functional; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +final class Left extends Either { + + private final E value; + + Left(E value) { + this.value = Objects.requireNonNull(value); + } + + public Either map(final Function f) { + return new Left<>(value); + } + + public Either bind(final Function> f) { + return new Left<>(value); + } + + public Either orElse(final Supplier> a) { + return a.get(); + } + + @Override + public String toString() { + return String.format("Left(%s)", value); + } + + @Override + public boolean isLeft() { + return true; + } + + @Override + public boolean isRight() { + return false; + } + + @Override + public E leftValue() { + return this.value; + } + + @Override + public A rightValue() { + throw new IllegalStateException("getRight called on Left"); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Pair.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Pair.java new file mode 100644 index 00000000..cc706249 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Pair.java @@ -0,0 +1,250 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.functional; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; + +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; + +import static org.eclipse.iofog.utils.functional.Functions.curry; + + +/** + * Created by ekrylovich + * on 13.6.17. + */ +public final class Pair { + private final A first; + private final B second; + + private Pair(final A first, final B second) { + this.first = Objects.requireNonNull(first); + this.second = Objects.requireNonNull(second); + } + + public A _1() { + return first; + } + + public B _2() { + return second; + } + + public static Pair of(final T first, final V second) { + return new Pair<>(first, second); + } + + /** + * Map the first element of the product. + * + * @param f The function to map with. + * @return A product with the given function applied. + */ + public Pair map1(final Function f) { + return bimap(f, Function.identity()); + } + + /** + * Map the second element of the product. + * + * @param f The function to map with. + * @return A product with the given function applied. + */ + public Pair map2(final Function f) { + return bimap(Function.identity(), f); + } + + /** + * Swaps this product-2 (pair) + * + * @return the swapped pair + */ + public Pair swap() { + return of(_2(), _1()); + } + + /** + * Split this product between two argument functions and combine their + * output. + * + * @param f A function that will map the first element of this product. + * @param g A function that will map the second element of this product. + * @return A new product with the first function applied to the second + * element and the second function applied to the second element. + */ + public Pair bimap(final Function f, final Function g) { + return of(f.apply(_1()), g.apply(_2())); + } + + /** + * Transforms a curried function of arity-2 to a function of a product-2 + * + * @param f a curried function of arity-2 to transform into a function of + * a product-2 + * @return The function, transformed to operate on on a product-2 + */ + public static Function, C> tuple(final Function> f) { + return pair -> f.apply(pair._1()).apply(pair._2()); + } + + /** + * Transforms an uncurried function of arity-2 to a function of a product-2 + * + * @param f an uncurried function of arity-2 to transform into a function + * of a product-2 + * @return The function, transformed to operate on on a product-2 + */ + public static Function, C> tuple(final BiFunction f) { + return tuple(curry(f)); + } + + /** + * Transforms a function of a product-2 to an uncurried function or arity-2. + * + * @param f A function of a product-2 to transform into an uncurried + * function. + * @return The function, transformed to an uncurried function of arity-2. + */ + public static BiFunction untuple(final Function, C> f) { + return (a, b) -> f.apply(of(a, b)); + } + + /** + * Returns a new Pair (Product2) with the first element is the application + * of the given function to this pair (current instance) and the second + * element is the same as this instance. It duplicates this product2 (pair) + * in the first element and then apply the given function to generate the + * new first element of the result pair and leave the second element + * unchanged. + * + * @param f A function to map over the duplicated product. + * @return A new product with the result of the given function applied to + * this product as the first element, and with the second element + * unchanged. + */ + public Pair cobind(final Function, C> f) { + return Pair.extend().apply(f).apply(this); + } + + /** + * Defines the comonad coextension operator ((D A -> B)-> (D A -> D B) A + * computation of type D A can be seen as a context-dependent + * computation that produces a value + * A if some context D. Thus this method + * propagates the context-dependence to the result. This function is the + * implementation of coextension comonad operator for the Product comonad. + * In this case, this function duplicates the give Product2 (Pair) in the + * first element and then apply the given function. + *

+ * A function to map over the duplicated product. + * + * @return A new product with the result of the given function applied to + * this product as the first element, and with the second element + * unchanged. + */ + public static Function, C>, Function, Pair>> extend() { + return f -> pair -> of(f.apply(pair), pair._2()); + } + + /** + * Defines the first natural transformation(epsi:: D -> 1) required to + * define a comonad. The intuition for this function is, given a + * context-dependent computation D A run it in the `empty` or + * `current` context and get the result. + * + * @return the function that runs a given context-dependent computation in + * the `empty` or `current` context. + */ + public static Function, A> extract() { + return Pair::_1; + } + + /** + * Duplicates this product into the first element. This function corresponds + * to the second natural transformation (d :: D -> D D) required to define a + * comonad. The intuition for this function is : given a context-dependent + * computation returns context-dependent context depend computation where + * the context of the inner computation given by the outer. + * + * @return A new product with this product in its first element and with the + * second element unchanged + */ + public Pair, B> duplicate() { + return cobind(Function.identity()); + } + + /** + * Replaces the first element of this product with the given value. + * + * @param c The value with which to replace the first element of this + * product. + * @return A new product with the first element replaced with the given + * value. + */ + public Pair replace1(final C c) { + final Function, C> co = Functions.>constant().apply(c); + return cobind(co); + } + + /** + * Replaces the first element of this product with the given value. + * + * @param c The value with which to replace the first element of this + * product. + * @return A new product with the first element replaced with the given + * value. + */ + public Pair replace2(final C c) { + return swap().replace1(c).swap(); + } + + /** + * Returns a first projection as a function. To be used with higher order + * functions. + * + * @return function that will project the given Pair to the first component + */ + public static Function, A> fst() { + return Pair::_1; + } + + /** + * Returns a second projection as a function. To be used with higher order + * functions. + * + * @return function that will project the given Pair to the second component + */ + public static Function, B> snd() { + return Pair::_2; + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + @Override + public boolean equals(final Object obj) { + return EqualsBuilder.reflectionEquals(this, obj); + } + + @Override + public String toString() { + return "(" + first + "," + second + ")"; + } +} + diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Right.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Right.java new file mode 100755 index 00000000..19194ec0 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Right.java @@ -0,0 +1,64 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.functional; + +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; + +final class Right extends Either { + + private final A value; + + Right(final A value) { + this.value = Objects.requireNonNull(value); + } + + public Either map(final Function f) { + return new Right<>(f.apply(value)); + } + + public Either bind(final Function> f) { + return f.apply(value); + } + + public Either orElse(final Supplier> a) { + return this; + } + + @Override + public String toString() { + return String.format("Right(%s)", value); + } + + @Override + public boolean isLeft() { + return false; + } + + @Override + public boolean isRight() { + return true; + } + + @Override + public E leftValue() { + throw new IllegalStateException("getLeft called on Right"); + } + + @Override + public A rightValue() { + return this.value; + } +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Unit.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Unit.java new file mode 100755 index 00000000..0e508b69 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Unit.java @@ -0,0 +1,27 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.utils.functional; + +/** + * The unit type which has only one inhabitent value. it's () type in Functional + * languages. If we forget about null type, the Unit type serves as + * a Terminal Object in The category Setspf (Sets with partial functions) of + * java types and functions. + * + * Created by ekrylovich + * on 13.6.17. + */ +public enum Unit { + UNIT +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LogFormatter.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LogFormatter.java new file mode 100644 index 00000000..385286da --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LogFormatter.java @@ -0,0 +1,60 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils.logging; + +import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.logging.Formatter; +import java.util.logging.LogRecord; + +import org.apache.commons.lang.exception.ExceptionUtils; +import org.eclipse.iofog.network.IOFogNetworkInterface; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.jboss.logmanager.Level; + +import javax.json.Json; +import javax.json.JsonBuilderFactory; +import javax.json.JsonObjectBuilder; + +/** + * formats logs + * [MM/dd/yyyy hh:mm:ss.SSS] [WARN/INFO] [MODULE] : Message + * + * @author saeid + * + */ +public class LogFormatter extends Formatter { + public String format(LogRecord record) { + IOFogNetworkInterfaceManager fogNetworkInterfaceManager = IOFogNetworkInterfaceManager.getInstance(); + final DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + JsonBuilderFactory factory = Json.createBuilderFactory(null); + JsonObjectBuilder jsonObjectBuilder = factory.createObjectBuilder(); + jsonObjectBuilder.add("timestamp", df.format(System.currentTimeMillis())); + jsonObjectBuilder.add("level", record.getLevel().toString()); + jsonObjectBuilder.add("agent_id", Configuration.getIofogUuid()); + jsonObjectBuilder.add("pid", fogNetworkInterfaceManager.getPid() != 0 ? fogNetworkInterfaceManager.getPid() : fogNetworkInterfaceManager.getFogPid()); + jsonObjectBuilder.add("hostname", fogNetworkInterfaceManager.getHostName() != null ? fogNetworkInterfaceManager.getHostName(): IOFogNetworkInterface.getHostName()); + jsonObjectBuilder.add("thread", record.getSourceClassName()); + jsonObjectBuilder.add("module", record.getSourceMethodName()); + jsonObjectBuilder.add("message", record.getMessage()); + if (record.getThrown() != null) { + jsonObjectBuilder.add("exception_message", record.getThrown().getLocalizedMessage()); + jsonObjectBuilder.add("stacktrace", ExceptionUtils.getFullStackTrace(record.getThrown())); + } + return jsonObjectBuilder.build().toString().concat("\n"); + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LoggingService.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LoggingService.java new file mode 100755 index 00000000..e37ff434 --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LoggingService.java @@ -0,0 +1,261 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils.logging; + +import org.apache.commons.lang.SystemUtils; +import org.apache.commons.lang.exception.ExceptionUtils; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; + + +import javax.json.*; +import java.io.*; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.LinkOption; +import java.nio.file.attribute.*; +import java.util.*; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.eclipse.iofog.utils.CmdProperties.getVersion; + +/** + * sets up and starts logging + * + * @author saeid + * + */ +public final class LoggingService { + + private static final String MODULE_NAME = "LoggingService"; + + private static Logger logger = null; + private static final Map microserviceLogger = new HashMap<>(); + + private LoggingService() { + + } + + /** + * logs Level.INFO message + * + * @param moduleName - name of module + * @param msg - message + */ + public static void logInfo(String moduleName, String msg) { + if (Configuration.debugging || logger == null) + System.out.println(String.format("%s %s : %s (%s)", Thread.currentThread().getName(), moduleName, msg, new Date(System.currentTimeMillis()))); + else { + logger.logp(Level.INFO, Thread.currentThread().getName(), moduleName, msg); + } + } + + /** + * logs Level.WARNING message + * + * @param moduleName - name of module + * @param msg - message + */ + public static void logWarning(String moduleName, String msg) { + if (Configuration.debugging || logger == null) { + System.out.println(String.format("%s %s : %s (%s)", Thread.currentThread().getName(), moduleName, msg, new Date(System.currentTimeMillis()))); + } else { + logger.logp(Level.WARNING, Thread.currentThread().getName(), moduleName, msg); + } + } + /** + * logs Level.FINE message + * For debug purpose + * @param moduleName - name of module + * @param msg - message + */ + public static void logDebug(String moduleName, String msg) { + if (Configuration.debugging || logger == null) + System.out.println(String.format("%s %s : %s (%s)", Thread.currentThread().getName(), moduleName, msg, new Date(System.currentTimeMillis()))); + else { + logger.logp(Level.FINE, Thread.currentThread().getName(), moduleName, msg); + } + } + /** + * logs Level.Error message + * + * @param moduleName - name of module + * @param msg - message + * @param e - exception + */ + public static void logError(String moduleName, String msg, Throwable e) { + if (Configuration.debugging || logger == null) { + System.out.println(String.format("%s %s : %s (%s) - Exception: %s - Stack trace: %s", Thread.currentThread().getName(), moduleName, msg, new Date(System.currentTimeMillis()), e.getMessage(), ExceptionUtils.getStackTrace(e))); + } else { + logger.logp(Level.SEVERE, Thread.currentThread().getName(), moduleName, msg, e); + } + } + + /** + * sets up logging + * + * @throws IOException + */ + public static void setupLogger() throws IOException { + int maxFileSize = (int) (Configuration.getLogDiskLimit() * Constants.MiB); + int logFileCount = Configuration.getLogFileCount(); + String logLevel = Configuration.getLogLevel().toUpperCase(); + final File logDirectory = new File(Configuration.getLogDiskDirectory()); + + logDirectory.mkdirs(); + + final String logFilePattern = logDirectory.getPath() + "/iofog-agent.%g.log"; + + if (maxFileSize < Constants.MiB) { + System.out.println("[" + MODULE_NAME + "] Warning: current " + + " config parameter's value is negative, using default 1 Mb limit"); + maxFileSize = Constants.MiB; + } + + if (logFileCount < 1) { + System.out.println("[" + MODULE_NAME + "] Warning: current config parameter's" + + " value is below l, using default 1 log file value"); + logFileCount = 1; + } + + long limit = (maxFileSize / logFileCount) * 1_000L; + if (limit > Integer.MAX_VALUE) { + System.out.println("[" + MODULE_NAME + "] Warning: current config parameter's" + + " value is above 2GB, using max 2GB value"); + limit = 2L * Constants.MiB * 1_000L; + } + + int intLimit = (int) limit; + + Handler logFileHandler = new FileHandler(logFilePattern, intLimit, logFileCount); + + logFileHandler.setFormatter(new LogFormatter()); + + if (logger != null) { + for (Handler f : logger.getHandlers()) + f.close(); + } + + logger = Logger.getLogger("org.eclipse.iofog"); + logger.addHandler(logFileHandler); + + logger.setUseParentHandlers(false); + // Disabling the log level off + logger.setLevel(Level.parse(logLevel).equals(Level.OFF) ? Level.INFO : Level.parse(logLevel)); + + logger.info("main, Logging Service, logger started."); + + } + + /** + * sets up microservice logging + * + * @throws IOException + */ + public static void setupMicroserviceLogger(String microserviceUuid, long logSize) throws IOException { + int maxFileSize = (int) (logSize * 1_000_000); + int logFileCount = Math.round(logSize); + final File logDirectory = new File(Configuration.getLogDiskDirectory()); + + logDirectory.mkdirs(); + + UserPrincipalLookupService lookupservice = FileSystems.getDefault().getUserPrincipalLookupService(); + final GroupPrincipal group = lookupservice.lookupPrincipalByGroupName(Constants.OS_GROUP); + if (SystemUtils.IS_OS_LINUX || SystemUtils.IS_OS_MAC) { + PosixFileAttributeView fileAttributeView = Files.getFileAttributeView(logDirectory.toPath(), PosixFileAttributeView.class, + LinkOption.NOFOLLOW_LINKS); + fileAttributeView.setGroup(group); + + Set perms = PosixFilePermissions.fromString("rwxrwx---"); + Files.setPosixFilePermissions(logDirectory.toPath(), perms); + + } else if (SystemUtils.IS_OS_WINDOWS) { + DosFileAttributeView fileAttributeView = Files.getFileAttributeView(logDirectory.toPath(), DosFileAttributeView.class, + LinkOption.NOFOLLOW_LINKS); + fileAttributeView.setReadOnly(false); + + File dir = logDirectory.toPath().toFile(); + dir.setReadable(true, false); + dir.setExecutable(true, false); + dir.setWritable(true, false); + } + + + final String logFilePattern = logDirectory.getPath() + "/" + microserviceUuid + ".%g.log"; + Logger logger = microserviceLogger.get(microserviceUuid); + + if (logger != null) { + for (Handler f : logger.getHandlers()) + f.close(); + } + + if (logFileCount == 0) { + logFileCount = 1; + } + + Handler logFileHandler = new FileHandler(logFilePattern, maxFileSize / logFileCount, logFileCount); + + logFileHandler.setFormatter(new LogFormatter()); + + logger = Logger.getLogger(microserviceUuid); + logger.addHandler(logFileHandler); + + logger.setUseParentHandlers(false); + + microserviceLogger.put(microserviceUuid, logger); + } + + public static boolean microserviceLogInfo(String microserviceUuid, String msg) { + Logger logger = microserviceLogger.get(microserviceUuid); + if (logger == null) { + logNullLogger(); + return false; + } + + logger.info(msg); + return true; + } + + public static boolean microserviceLogWarning(String microserviceUuid, String msg) { + Logger logger = microserviceLogger.get(microserviceUuid); + if (logger == null) { + logNullLogger(); + return false; + } + + logger.warning(msg); + return true; + } + + private static void logNullLogger() { + String errorMsg = " Log message parsing error, Logger initialized null"; + LoggingService.logWarning(MODULE_NAME, errorMsg); + + } + + /** + * resets logging with new configurations + * this method called by {@link Configuration} + */ + public static void instanceConfigUpdated() { + try { + setupLogger(); + } catch (Exception exp) { + logError(MODULE_NAME, "Error updating logger instance", exp); + } + } + +} diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/trustmanager/TrustManagers.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/trustmanager/TrustManagers.java new file mode 100644 index 00000000..6b5dea8b --- /dev/null +++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/trustmanager/TrustManagers.java @@ -0,0 +1,111 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat Inc. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Jens Reimann + *******************************************************************************/ +package org.eclipse.iofog.utils.trustmanager; + +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +public final class TrustManagers { + + private TrustManagers() { + } + + private static void addAllX509(List x509TrustManagers, TrustManager[] trustManagers) { + for (TrustManager tm : trustManagers) { + if (tm instanceof X509TrustManager) { + x509TrustManagers.add((X509TrustManager) tm); + } + } + } + + public static javax.net.ssl.TrustManager[] createTrustManager(final Certificate controllerCert) throws Exception { + + // the final list of trust managers + + final List trustManagers = new ArrayList<>(); + + // add the default system trust anchors + + { + + // create the trust manager factory using he default + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + + // add the trust managers + + addAllX509(trustManagers, tmf.getTrustManagers()); + } + + // now add the specific controller certificate + + if (controllerCert != null) { + + // create the keystore + + KeyStore controllerCertStore = KeyStore.getInstance(KeyStore.getDefaultType()); + controllerCertStore.load(null, null); + controllerCertStore.setCertificateEntry("cert", controllerCert); + + // create the trust manager factory + + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(controllerCertStore); + + // add the trust managers + + addAllX509(trustManagers, tmf.getTrustManagers()); + + } + + X509TrustManager combinedTrustManager = new X509TrustManager() { + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + CertificateException last = null; + + for (X509TrustManager tm : trustManagers) { + try { + tm.checkServerTrusted(chain, authType); + return; + } catch (CertificateException ex) { + last = ex; + } + } + + throw new CertificateException("Unable to validate server certificate", last); + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + throw new CertificateException("Client certificates validation is not supported"); + } + }; + + return new javax.net.ssl.TrustManager[]{combinedTrustManager}; + } + +} diff --git a/iofog-agent-daemon/src/main/resources/cmd_messages.properties b/iofog-agent-daemon/src/main/resources/cmd_messages.properties new file mode 100644 index 00000000..8ab455f2 --- /dev/null +++ b/iofog-agent-daemon/src/main/resources/cmd_messages.properties @@ -0,0 +1,34 @@ +version_msg=ioFog %s \\nCopyright (C) 2022 Edgeworx, Inc. \\nEclipse ioFog is provided under the Eclipse Public License 2.0 (EPL-2.0) \\nhttps://www.eclipse.org/legal/epl-v20.html +deprovision_msg=Deprovisioning from controller ... %s +provision_msg=Provisioning with key "%s" ... Result: %s +provision_common_error=\\nProvisioning failed +provision_status_error=\\nProvision failed with error message: "%s" +provision_status_success=\\nProvision success - Iofog UUID is %s +disk_consumption_limit=Message Storage Limit +disk_directory=Message Storage Directory +memory_consumption_limit=Memory RAM Limit +processor_consumption_limit=CPU Usage Limit +controller_url=ioFog Controller +controller_cert=ioFog Certificate +docker_url=Docker URL +network_interface=Network Interface +log_disk_consumption_limit=Log Disk Limit +log_disk_directory=Log File Directory +log_file_count=Log Rolling File Count +log_level=Log Level +status_frequency=Status Update Frequency +change_frequency=Get Changes Frequency +device_scan_frequency=Scan Devices Frequency +post_diagnostics_freq=Post Diagnostics Frequency +watchdog_enabled=Isolated Docker Containers Mode +iofog_uuid=Iofog UUID +ip_address=IP Address +gps_mode=GPS mode +gps_coordinates=GPS coordinates(lat,lon) +fog_type=Fog type +dev_mode=Developer's Mode +available_disk_threshold=Available Disk Threshold +docker_pruning_frequency=Docker Pruning Frequency +ready_to_upgrade_scan_frequency=Ready To Upgrade Scan Frequency +secure_mode=Secure Mode +time_zone=Time Zone \ No newline at end of file diff --git a/iofog-agent-daemon/src/main/resources/version.properties b/iofog-agent-daemon/src/main/resources/version.properties new file mode 100644 index 00000000..a50bf5c8 --- /dev/null +++ b/iofog-agent-daemon/src/main/resources/version.properties @@ -0,0 +1 @@ +version=${version} diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineActionTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineActionTest.java new file mode 100644 index 00000000..9b448f6b --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineActionTest.java @@ -0,0 +1,456 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog.command_line; + +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.CmdProperties; +import org.eclipse.iofog.utils.Orchestrator; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; + +import java.text.SimpleDateFormat; +import java.util.*; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyMap; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; + + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({CommandLineAction.class, StatusReporter.class, FieldAgent.class, Configuration.class, Orchestrator.class, CmdProperties.class, LoggingService.class}) +public class CommandLineActionTest { + private CommandLineAction commandLineAction; + private StatusReporter statusReporter; + private FieldAgent fieldAgent; + private List stop; + private HashMap result; + private CmdProperties cmdProperties; + + @Before + public void setUp() throws Exception { + commandLineAction = mock(CommandLineAction.class); + statusReporter = mock(StatusReporter.class); + fieldAgent = mock(FieldAgent.class); + cmdProperties = mock(CmdProperties.class); + mockStatic(LoggingService.class); + stop = new ArrayList(Collections.singleton("stop")); + result = new HashMap<>(); + result.put("ll", "info"); + mockStatic(FieldAgent.class); + mockStatic(Orchestrator.class); + mockStatic(Configuration.class); + mockStatic(StatusReporter.class); + mockStatic(CmdProperties.class); + PowerMockito.when(FieldAgent.getInstance()).thenReturn(fieldAgent); + PowerMockito.when(fieldAgent.provision("dummy")).thenReturn(Json.createObjectBuilder().add("status", "success").add("errorMessage", "").add("uuid", "uuid").build()); + PowerMockito.when(fieldAgent.provision("anotherkey")).thenReturn(Json.createObjectBuilder().add("status", "success").add("errorMessage", "Key not valid").build()); + PowerMockito.when(fieldAgent.provision("prod")).thenReturn(null); + PowerMockito.when(statusReporter.getStatusReport()).thenReturn(status); + // CommandProperties mock + PowerMockito.when(CmdProperties.getVersion()).thenReturn("1.2.2"); + PowerMockito.when(CmdProperties.getVersionMessage()).thenReturn(version); + PowerMockito.when(CmdProperties.getDeprovisionMessage()).thenReturn("Deprovisioning from controller ... %s"); + PowerMockito.when(CmdProperties.getProvisionMessage()).thenReturn("Provisioning with key \"%s\" ... Result: %s"); + PowerMockito.when(CmdProperties.getProvisionCommonErrorMessage()).thenReturn("\nProvisioning failed"); + PowerMockito.when(CmdProperties.getProvisionStatusErrorMessage()).thenReturn("\nProvision failed with error message: \"%s\""); + PowerMockito.when(CmdProperties.getProvisionStatusSuccessMessage()).thenReturn("\nProvision success - Iofog UUID is %s"); + } + + @After + public void tearDown() throws Exception { + stop = null; + result = null; + reset(statusReporter); + reset(fieldAgent); + } + + /** + * Get Keys method + */ + @Test + public void testGetKeys() { + assertFalse(CommandLineAction.HELP_ACTION.getKeys().isEmpty()); + assertTrue(isEqual(stop, CommandLineAction.getActionByKey("stop").getKeys())); + } + + /** + * help command with success + */ + @Test + public void testHelpActionPerform() { + String[] helpArgs = {"help", "--help", "-h", "-?"}; + try { + assertEquals(helpContent, CommandLineAction.getActionByKey(helpArgs[0]).perform(helpArgs)); + assertEquals(helpContent, CommandLineAction.getActionByKey(helpArgs[1]).perform(helpArgs)); + assertEquals(helpContent, CommandLineAction.getActionByKey(helpArgs[2]).perform(helpArgs)); + assertEquals(helpContent, CommandLineAction.getActionByKey(helpArgs[3]).perform(helpArgs)); + } catch (AgentUserException e) { + fail("Shall never happen"); + } + } + + /** + * Version command with success + */ + @Test + public void testVersionActionPerform() { + String[] args = {"version", "--version", "-v"}; + try { + assertEquals(version, CommandLineAction.getActionByKey(args[0]).perform(args)); + } catch (AgentUserException e) { + fail("Shall never happen"); + } + } + + /** + * status command with success + */ + @Test + public void testStatusActionPerform() { + String[] args = {"status"}; + try { + assertTrue(! CommandLineAction.getActionByKey(args[0]).perform(args).isEmpty()); + assertEquals(status, CommandLineAction.getActionByKey(args[0]).perform(args)); + } catch (AgentUserException e) { + fail("Shall never happen"); + } + } + + /** + * deprovision command with success + */ + @Test + public void testDeProvisionActionPerform() { + String[] args = {"deprovision"}; + assertTrue(! CommandLineAction.getActionByKey(args[0]).getKeys().isEmpty()); + PowerMockito.when(fieldAgent.deProvision(anyBoolean())).thenReturn("\nSuccess - tokens, identifiers and keys removed"); + try { + assertEquals("Deprovisioning from controller ... \nSuccess - tokens, identifiers and keys removed", commandLineAction.getActionByKey(args[0]).perform(args)); + } catch (AgentUserException e) { + fail("This shall never happen"); + } + } + + /** + * deprovision command with failure + * throws AgentSystemException + */ + @Test(expected = AgentUserException.class) + public void throwsAgentUserExcpetionWhenDeProvisionActionPerform() throws AgentUserException { + String[] args = {"deprovision"}; + assertTrue(! CommandLineAction.getActionByKey(args[0]).getKeys().isEmpty()); + PowerMockito.when(fieldAgent.deProvision(anyBoolean())).thenReturn("\nFailure - not provisioned"); + commandLineAction.getActionByKey(args[0]).perform(args); + } + + + /** + * Info command displaying the config report + */ + @Test + public void testInfoActionPerform() { + String[] args = {"info"}; + assertTrue(! CommandLineAction.getActionByKey(args[0]).getKeys().isEmpty()); + when(Configuration.getConfigReport()).thenReturn("Config report"); + try { + assertEquals("Config report", commandLineAction.getActionByKey(args[0]).perform(args)); + } catch (AgentUserException e) { + fail("This shall never happen"); + } + } + + /** + * Switch command with no option displays help content + */ + @Test + public void testSwitchActionPerformWithNoValue() { + String[] args = {"switch"}; + assertTrue(! CommandLineAction.getActionByKey(args[0]).getKeys().isEmpty()); + try { + assertEquals(helpContent, CommandLineAction.getActionByKey(args[0]).perform(args)); + } catch (AgentUserException e) { + fail("This shall never happen"); + } + } + + /** + * Switch command with valid option + */ + @Test + public void testSwitchActionPerformWithValidValue() { + String[] anotherArgs = {"switch", "prod"}; + when(Configuration.setupConfigSwitcher(any())).thenReturn("success"); + try { + assertEquals("success", commandLineAction.getActionByKey(anotherArgs[0]).perform(anotherArgs)); + } catch (AgentUserException e) { + fail("This shall never happen"); + } + } + + /** + * Switch option with Invalid value + * nn + */ + @Test + public void testSwitchActionPerformWithInvalidValue() { + String[] anotherArgs = {"switch", "dummy"}; + try { + assertEquals("Invalid switcher state", commandLineAction.getActionByKey(anotherArgs[0]).perform(anotherArgs)); + } catch (AgentUserException e) { + fail("This shall never happen"); + } + } + + /** + * When no value is passed with provision option + */ + @Test + public void testProvisionActionPerformWithNoValue() { + String[] args = {"provision"}; + assertTrue(! CommandLineAction.getActionByKey(args[0]).getKeys().isEmpty()); + try { + assertEquals(helpContent, CommandLineAction.getActionByKey(args[0]).perform(args)); + } catch (AgentUserException e) { + fail("This shall never happen"); + } + } + + /** + * When provisioningResult of FieldAgent returns null response. + */ + @Test(expected = AgentUserException.class) + public void testProvisionActionPerformWithTwoArgs() throws AgentUserException { + String[] anotherArgs = {"provision", "prod"}; + commandLineAction.getActionByKey(anotherArgs[0]).perform(anotherArgs); + } + + /** + * When FieldAgent.provision(provisionKey) returns the mock response with uuid + */ + @Test + public void testProvisionActionPerformReturnsUUID() { + String[] anotherArgs1 = {"provision", "dummy"}; + try { + assertEquals("Provisioning with key \"dummy\" ... Result: \n" + "Provision success - Iofog UUID is uuid", commandLineAction.getActionByKey(anotherArgs1[0]).perform(anotherArgs1)); + } catch (AgentUserException e) { + fail("This shall never happen"); + } + } + + /** + * When FieldAgent.provision(provisionKey) returns the mock response without uuid + * throws AgentUserException + */ + @Test(expected = AgentUserException.class) + public void throwsAgentUserExceptionProvisionActionPerformResponseWithoutUUID() throws AgentUserException { + String[] anotherArgs2 = {"provision", "anotherkey"}; + commandLineAction.getActionByKey(anotherArgs2[0]).perform(anotherArgs2); + + } + + /** + * When config command with invalid options display help + */ + @Test + public void testConfigActionPerformWithOutOption() throws Exception { + String[] args = {"config"}; + assertTrue(! CommandLineAction.getActionByKey(args[0]).getKeys().isEmpty()); + assertEquals(helpContent, CommandLineAction.getActionByKey(args[0]).perform(args)); + + } + + /** + * When config command without options display help + * Test when config option value is ambiguous + */ + @Test + public void tesConfigActionPerformWithInvalidOption() throws Exception { + String[] anotherArgs = {"config", "ambiguous"}; + assertEquals(helpContent, CommandLineAction.getActionByKey(anotherArgs[0]).perform(anotherArgs)); + } + + /** + * When config command with default to reset the configuration Success + * Test when config reset to defaults + */ + @Test + public void testConfigActionPerformWithDefaultOption() throws Exception { + String[] anotherArgs1 = {"config", "defaults"}; + assertEquals("Configuration has been reset to its defaults.", CommandLineAction.getActionByKey(anotherArgs1[0]).perform(anotherArgs1)); + + } + + /** + * When config command with valid option with no value + */ + @Test + public void testConfigActionPerformWithValidOptionAndNoValue() throws Exception { + String[] logArgs = {"config", "-ll"}; + assertEquals(helpContent, CommandLineAction.getActionByKey(logArgs[0]).perform(logArgs)); + } + + /** + * When config command with valid option with InValid value then Configuration throw exception + */ + @Test + public void throwsExceptionsWhenConfigActionPerformWithValidOptionAndInvalidValue() throws Exception { + String[] logArgs = {"config", "-ll", "severeAndInfo"}; + PowerMockito.when(Configuration.getOldNodeValuesForParameters(anySet(), any())). + thenReturn(result); + PowerMockito.when(Configuration.setConfig(anyMap(), anyBoolean())). + thenThrow(new Exception("item not found or defined more than once")); + assertEquals("Error updating new config : item not found or defined more than once", CommandLineAction.getActionByKey(logArgs[0]).perform(logArgs)); + } + + + /** + * When config command with valid option and value + */ + @Test + public void testConfigActionPerformWithValidOptionAndValue() throws Exception { + + String[] logArgs = {"config", "-ll", "severe"}; + PowerMockito.when(Configuration.setConfig(anyMap(), anyBoolean())).thenReturn(new HashMap<>()); + PowerMockito.when(Configuration.getOldNodeValuesForParameters(anySet(), any())). + thenReturn(result); + assertEquals("\\n\tChange accepted for Parameter : - ll, Old value was :info, New Value is : severe", CommandLineAction.getActionByKey(logArgs[0]).perform(logArgs)); + + } + + /** + * Helper method for comparing lists + */ + private static boolean isEqual(List list1, List list2) { + boolean value = list1.size() == list2.size() && list1.equals(list2); + return value; + } + + private SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy hh:mm a"); + + private String status = "ioFog daemon : " + + "STARTING\\nMemory Usage :" + + " about 0.00 MiB\\nDisk Usage : " + + "about 0.00 MiB\\nCPU Usage : " + + "about 0.00 %\\nRunning Microservices : " + + "0\\nConnection to Controller : " + + "not connected\\nMessages Processed " + + ": about 0\\nSystem Time : " + + "18/09/2019 05:58 PM\\nSystem Available Disk : " + + "0.00 MB\\nSystem Available Memory : " + + "0.00 MB\\nSystem Total CPU : 0.00 %"; + + private String version = "ioFog 1 \n" + + "Copyright (C) 2018-2022 Edgeworx, Inc. \n" + + "Eclipse ioFog is provided under the Eclipse Public License 2.0 (EPL-2.0) \n" + + "https://www.eclipse.org/legal/epl-v20.html"; + + private String helpContent = "Usage 1: iofog-agent [OPTION]\\n" + + "Usage 2: iofog-agent [COMMAND] \\n" + + "Usage 3: iofog-agent [COMMAND] [Parameter] \\n" + + "\\n" + + "Option GNU long option Meaning\\n" + + "====== =============== =======\\n" + + "-h, -? --help Show this message\\n" + + "-v --version Display the software version and\\n" + + " license information\\n" + + "\\n" + + "\\n" + + "Command Arguments Meaning\\n" + + "======= ========= =======\\n" + + "help Show this message\\n" + + "version Display the software version and\\n" + + " license information\\n" + + "status Display current status information\\n" + + " about the software\\n" + + "provision Attach this software to the\\n" + + " configured ioFog controller\\n" + + "deprovision Detach this software from all\\n" + + " ioFog controllers\\n" + + "info Display the current configuration\\n" + + " and other information about the\\n" + + " software\\n" + + "switch Switch to different config \\n" + + "config [Parameter] [VALUE] Change the software configuration\\n" + + " according to the options provided\\n" + + " defaults Reset configuration to default values\\n" + + " -d <#GB Limit> Set the limit, in GiB, of disk space\\n" + + " that the message archive is allowed to use\\n" + + " -dl

Set the message archive directory to use for disk\\n" + + " storage\\n" + + " -m <#MB Limit> Set the limit, in MiB, of RAM memory that\\n" + + " the software is allowed to use for\\n" + + " messages\\n" + + " -p <#cpu % Limit> Set the limit, in percentage, of CPU\\n" + + " time that the software is allowed\\n" + + " to use\\n" + + " -a Set the uri of the fog controller\\n" + + " to which this software connects\\n" + + " -ac Set the file path of the SSL/TLS\\n" + + " certificate for validating the fog\\n" + + " controller identity\\n" + + " -c Set the UNIX socket or network address\\n" + + " that the Docker daemon is using\\n" + + " -n Set the name of the network adapter\\n" + + " that holds the correct IP address of \\n" + + " this machine\\n" + + " -l <#GB Limit> Set the limit, in GiB, of disk space\\n" + + " that the log files can consume\\n" + + " -ld Set the directory to use for log file\\n" + + " storage\\n" + + " -lc <#log files> Set the number of log files to evenly\\n" + + " split the log storage limit\\n" + + " -ll Set the standard logging levels that\\n"+ + " can be used to control logging output\\n" + + " -sf <#seconds> Set the status update frequency\\n" + + " -cf <#seconds> Set the get changes frequency\\n" + + " -df <#seconds> Set the post diagnostics frequency\\n" + + " -sd <#seconds> Set the scan devices frequency\\n" + + " -uf <#hours> Set the isReadyToUpgradeScan frequency\\n" + + " -dt <#percentage> Set the available disk threshold\\n" + + " -idc Set the mode on which any not\\n" + + " registered docker container will be\\n" + + " shut down\\n" + + " -gps Use auto to detect fog type by system commands,\\n" + + " use arm or intel_amd to set it manually\\n" + + " -sec Set the secure mode without using ssl \\n" + + " certificates. \\n" + + " -dev Set the developer's mode\\n" + + " -tz Set the device timeZone\\n" + + "\\n" + + "\\n" + + "Report bugs to: edgemaster@iofog.org\\n" + + "ioFog home page: http://iofog.org\\n" + + "For users with Eclipse accounts, report bugs to: https://bugs.eclipse.org/bugs/enter_bug.cgi?product=iofog"; +} diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineConfigParamTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineConfigParamTest.java new file mode 100644 index 00000000..3eb488a1 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineConfigParamTest.java @@ -0,0 +1,213 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.command_line; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({CommandLineConfigParam.class}) +public class CommandLineConfigParamTest { + private CommandLineConfigParam commandLineConfigParam; + + @Before + public void setUp() throws Exception { + + commandLineConfigParam = mock(CommandLineConfigParam.class); + } + + @After + public void tearDown() throws Exception { + commandLineConfigParam = null; + } + + @SuppressWarnings("static-access") + @Test + public void testGetCommandName() { + assertEquals("", commandLineConfigParam.ACCESS_TOKEN.getCommandName()); + assertEquals("", commandLineConfigParam.IOFOG_UUID.getCommandName()); + assertEquals("d", commandLineConfigParam.DISK_CONSUMPTION_LIMIT.getCommandName()); + assertEquals("dl", commandLineConfigParam.DISK_DIRECTORY.getCommandName()); + assertEquals("m", commandLineConfigParam.MEMORY_CONSUMPTION_LIMIT.getCommandName()); + assertEquals("p", commandLineConfigParam.PROCESSOR_CONSUMPTION_LIMIT.getCommandName()); + assertEquals("a", commandLineConfigParam.CONTROLLER_URL.getCommandName()); + assertEquals("ac", commandLineConfigParam.CONTROLLER_CERT.getCommandName()); + assertEquals("c", commandLineConfigParam.DOCKER_URL.getCommandName()); + assertEquals("n", commandLineConfigParam.NETWORK_INTERFACE.getCommandName()); + assertEquals("l", commandLineConfigParam.LOG_DISK_CONSUMPTION_LIMIT.getCommandName()); + assertEquals("ld", commandLineConfigParam.LOG_DISK_DIRECTORY.getCommandName()); + assertEquals("lc", commandLineConfigParam.LOG_FILE_COUNT.getCommandName()); + assertEquals("ll", commandLineConfigParam.LOG_LEVEL.getCommandName()); + assertEquals("sf", commandLineConfigParam.STATUS_FREQUENCY.getCommandName()); + assertEquals("cf", commandLineConfigParam.CHANGE_FREQUENCY.getCommandName()); + assertEquals("sd", commandLineConfigParam.DEVICE_SCAN_FREQUENCY.getCommandName()); + assertEquals("idc", commandLineConfigParam.WATCHDOG_ENABLED.getCommandName()); + assertEquals("gps", commandLineConfigParam.GPS_MODE.getCommandName()); + assertEquals("", commandLineConfigParam.GPS_COORDINATES.getCommandName()); + assertEquals("df", commandLineConfigParam.POST_DIAGNOSTICS_FREQ.getCommandName()); + assertEquals("ft", commandLineConfigParam.FOG_TYPE.getCommandName()); + assertEquals("dev", commandLineConfigParam.DEV_MODE.getCommandName()); + assertEquals("pf", commandLineConfigParam.DOCKER_PRUNING_FREQUENCY.getCommandName()); + assertEquals("dt", commandLineConfigParam.AVAILABLE_DISK_THRESHOLD.getCommandName()); + } + + @SuppressWarnings("static-access") + @Test + public void testGetXmlTag() { + assertEquals("access_token", commandLineConfigParam.ACCESS_TOKEN.getXmlTag()); + assertEquals("iofog_uuid", commandLineConfigParam.IOFOG_UUID.getXmlTag()); + assertEquals("disk_consumption_limit", commandLineConfigParam.DISK_CONSUMPTION_LIMIT.getXmlTag()); + assertEquals("disk_directory", commandLineConfigParam.DISK_DIRECTORY.getXmlTag()); + assertEquals("memory_consumption_limit", commandLineConfigParam.MEMORY_CONSUMPTION_LIMIT.getXmlTag()); + assertEquals("processor_consumption_limit", commandLineConfigParam.PROCESSOR_CONSUMPTION_LIMIT.getXmlTag()); + assertEquals("controller_url", commandLineConfigParam.CONTROLLER_URL.getXmlTag()); + assertEquals("controller_cert", commandLineConfigParam.CONTROLLER_CERT.getXmlTag()); + assertEquals("docker_url", commandLineConfigParam.DOCKER_URL.getXmlTag()); + assertEquals("network_interface", commandLineConfigParam.NETWORK_INTERFACE.getXmlTag()); + assertEquals("log_disk_consumption_limit", commandLineConfigParam.LOG_DISK_CONSUMPTION_LIMIT.getXmlTag()); + assertEquals("log_disk_directory", commandLineConfigParam.LOG_DISK_DIRECTORY.getXmlTag()); + assertEquals("log_file_count", commandLineConfigParam.LOG_FILE_COUNT.getXmlTag()); + assertEquals("log_level", commandLineConfigParam.LOG_LEVEL.getXmlTag()); + assertEquals("status_update_freq", commandLineConfigParam.STATUS_FREQUENCY.getXmlTag()); + assertEquals("get_changes_freq", commandLineConfigParam.CHANGE_FREQUENCY.getXmlTag()); + assertEquals("scan_devices_freq", commandLineConfigParam.DEVICE_SCAN_FREQUENCY.getXmlTag()); + assertEquals("isolated_docker_container", commandLineConfigParam.WATCHDOG_ENABLED.getXmlTag()); + assertEquals("gps", commandLineConfigParam.GPS_MODE.getXmlTag()); + assertEquals("gps_coordinates", commandLineConfigParam.GPS_COORDINATES.getXmlTag()); + assertEquals("post_diagnostics_freq", commandLineConfigParam.POST_DIAGNOSTICS_FREQ.getXmlTag()); + assertEquals("fog_type", commandLineConfigParam.FOG_TYPE.getXmlTag()); + assertEquals("dev_mode", commandLineConfigParam.DEV_MODE.getXmlTag()); + assertEquals("docker_pruning_freq", commandLineConfigParam.DOCKER_PRUNING_FREQUENCY.getXmlTag()); + assertEquals("available_disk_threshold", commandLineConfigParam.AVAILABLE_DISK_THRESHOLD.getXmlTag()); + } + + @SuppressWarnings("static-access") + @Test + public void testGetJsonProperty() { + assertEquals("", commandLineConfigParam.ACCESS_TOKEN.getJsonProperty()); + assertEquals("", commandLineConfigParam.IOFOG_UUID.getJsonProperty()); + assertEquals("diskLimit", commandLineConfigParam.DISK_CONSUMPTION_LIMIT.getJsonProperty()); + assertEquals("diskDirectory", commandLineConfigParam.DISK_DIRECTORY.getJsonProperty()); + assertEquals("memoryLimit", commandLineConfigParam.MEMORY_CONSUMPTION_LIMIT.getJsonProperty()); + assertEquals("cpuLimit", commandLineConfigParam.PROCESSOR_CONSUMPTION_LIMIT.getJsonProperty()); + assertEquals("", commandLineConfigParam.CONTROLLER_URL.getJsonProperty()); + assertEquals("", commandLineConfigParam.CONTROLLER_CERT.getJsonProperty()); + assertEquals("dockerUrl", commandLineConfigParam.DOCKER_URL.getJsonProperty()); + assertEquals("networkInterface", commandLineConfigParam.NETWORK_INTERFACE.getJsonProperty()); + assertEquals("logLimit", commandLineConfigParam.LOG_DISK_CONSUMPTION_LIMIT.getJsonProperty()); + assertEquals("logDirectory", commandLineConfigParam.LOG_DISK_DIRECTORY.getJsonProperty()); + assertEquals("logFileCount", commandLineConfigParam.LOG_FILE_COUNT.getJsonProperty()); + assertEquals("logLevel", commandLineConfigParam.LOG_LEVEL.getJsonProperty()); + assertEquals("statusFrequency", commandLineConfigParam.STATUS_FREQUENCY.getJsonProperty()); + assertEquals("changeFrequency", commandLineConfigParam.CHANGE_FREQUENCY.getJsonProperty()); + assertEquals("deviceScanFrequency", commandLineConfigParam.DEVICE_SCAN_FREQUENCY.getJsonProperty()); + assertEquals("watchdogEnabled", commandLineConfigParam.WATCHDOG_ENABLED.getJsonProperty()); + assertEquals("gpsMode", commandLineConfigParam.GPS_MODE.getJsonProperty()); + assertEquals("gpscoordinates", commandLineConfigParam.GPS_COORDINATES.getJsonProperty()); + assertEquals("postdiagnosticsfreq", commandLineConfigParam.POST_DIAGNOSTICS_FREQ.getJsonProperty()); + assertEquals("", commandLineConfigParam.FOG_TYPE.getJsonProperty()); + assertEquals("", commandLineConfigParam.DEV_MODE.getJsonProperty()); + assertEquals("dockerPruningFrequency", commandLineConfigParam.DOCKER_PRUNING_FREQUENCY.getJsonProperty()); + assertEquals("availableDiskThreshold", commandLineConfigParam.AVAILABLE_DISK_THRESHOLD.getJsonProperty()); + } + + @SuppressWarnings("static-access") + @Test + public void testGetDefaultValue() { + assertEquals("", commandLineConfigParam.ACCESS_TOKEN.getDefaultValue()); + assertEquals("", commandLineConfigParam.IOFOG_UUID.getDefaultValue()); + assertEquals("10", commandLineConfigParam.DISK_CONSUMPTION_LIMIT.getDefaultValue()); + assertEquals("/var/lib/iofog-agent/", commandLineConfigParam.DISK_DIRECTORY.getDefaultValue()); + assertEquals("4096", commandLineConfigParam.MEMORY_CONSUMPTION_LIMIT.getDefaultValue()); + assertEquals("80", commandLineConfigParam.PROCESSOR_CONSUMPTION_LIMIT.getDefaultValue()); + assertEquals("https://fogcontroller1.iofog.org:54421/api/v2/", commandLineConfigParam.CONTROLLER_URL.getDefaultValue()); + assertEquals("/etc/iofog-agent/cert.crt", commandLineConfigParam.CONTROLLER_CERT.getDefaultValue()); + assertEquals("unix:///var/run/docker.sock", commandLineConfigParam.DOCKER_URL.getDefaultValue()); + assertEquals("dynamic", commandLineConfigParam.NETWORK_INTERFACE.getDefaultValue()); + assertEquals("10", commandLineConfigParam.LOG_DISK_CONSUMPTION_LIMIT.getDefaultValue()); + assertEquals("/var/log/iofog-agent/", commandLineConfigParam.LOG_DISK_DIRECTORY.getDefaultValue()); + assertEquals("10", commandLineConfigParam.LOG_FILE_COUNT.getDefaultValue()); + assertEquals("INFO", commandLineConfigParam.LOG_LEVEL.getDefaultValue()); + assertEquals("10", commandLineConfigParam.STATUS_FREQUENCY.getDefaultValue()); + assertEquals("20", commandLineConfigParam.CHANGE_FREQUENCY.getDefaultValue()); + assertEquals("60", commandLineConfigParam.DEVICE_SCAN_FREQUENCY.getDefaultValue()); + assertEquals("off", commandLineConfigParam.WATCHDOG_ENABLED.getDefaultValue()); + assertEquals("auto", commandLineConfigParam.GPS_MODE.getDefaultValue()); + assertEquals("", commandLineConfigParam.GPS_COORDINATES.getDefaultValue()); + assertEquals("10", commandLineConfigParam.POST_DIAGNOSTICS_FREQ.getDefaultValue()); + assertEquals("auto", commandLineConfigParam.FOG_TYPE.getDefaultValue()); + assertEquals("off", commandLineConfigParam.SECURE_MODE.getDefaultValue()); + assertEquals("1", commandLineConfigParam.DOCKER_PRUNING_FREQUENCY.getDefaultValue()); + assertEquals("20", commandLineConfigParam.AVAILABLE_DISK_THRESHOLD.getDefaultValue()); + assertEquals("off", commandLineConfigParam.DEV_MODE.getDefaultValue()); + } + + @SuppressWarnings("static-access") + @Test + public void testGetCmdText() { + assertEquals("-", commandLineConfigParam.ACCESS_TOKEN.getCmdText()); + assertEquals("-", commandLineConfigParam.IOFOG_UUID.getCmdText()); + assertEquals("-d", commandLineConfigParam.DISK_CONSUMPTION_LIMIT.getCmdText()); + assertEquals("-dl", commandLineConfigParam.DISK_DIRECTORY.getCmdText()); + assertEquals("-m", commandLineConfigParam.MEMORY_CONSUMPTION_LIMIT.getCmdText()); + assertEquals("-p", commandLineConfigParam.PROCESSOR_CONSUMPTION_LIMIT.getCmdText()); + assertEquals("-a", commandLineConfigParam.CONTROLLER_URL.getCmdText()); + assertEquals("-ac", commandLineConfigParam.CONTROLLER_CERT.getCmdText()); + assertEquals("-c", commandLineConfigParam.DOCKER_URL.getCmdText()); + assertEquals("-n", commandLineConfigParam.NETWORK_INTERFACE.getCmdText()); + assertEquals("-l", commandLineConfigParam.LOG_DISK_CONSUMPTION_LIMIT.getCmdText()); + assertEquals("-ld", commandLineConfigParam.LOG_DISK_DIRECTORY.getCmdText()); + assertEquals("-lc", commandLineConfigParam.LOG_FILE_COUNT.getCmdText()); + assertEquals("-ll", commandLineConfigParam.LOG_LEVEL.getCmdText()); + assertEquals("-sf", commandLineConfigParam.STATUS_FREQUENCY.getCmdText()); + assertEquals("-cf", commandLineConfigParam.CHANGE_FREQUENCY.getCmdText()); + assertEquals("-sd", commandLineConfigParam.DEVICE_SCAN_FREQUENCY.getCmdText()); + assertEquals("-idc", commandLineConfigParam.WATCHDOG_ENABLED.getCmdText()); + assertEquals("-gps", commandLineConfigParam.GPS_MODE.getCmdText()); + assertEquals("-", commandLineConfigParam.GPS_COORDINATES.getCmdText()); + assertEquals("-df", commandLineConfigParam.POST_DIAGNOSTICS_FREQ.getCmdText()); + assertEquals("-ft", commandLineConfigParam.FOG_TYPE.getCmdText()); + assertEquals("-dev", commandLineConfigParam.DEV_MODE.getCmdText()); + assertEquals("-pf", commandLineConfigParam.DOCKER_PRUNING_FREQUENCY.getCmdText()); + assertEquals("-dt", commandLineConfigParam.AVAILABLE_DISK_THRESHOLD.getCmdText()); + assertEquals("-sec", commandLineConfigParam.SECURE_MODE.getCmdText()); + } + + @Test + public void testGetCommandByName() { + assertTrue(CommandLineConfigParam.getCommandByName("dev").isPresent()); + assertFalse(CommandLineConfigParam.getCommandByName("dummyCommand").isPresent()); + } + + @Test + public void testGetAllCmdTextNames() { + assertTrue(CommandLineConfigParam.getAllCmdTextNames().size() != 0); + } + + @Test + public void testExistParam() { + assertTrue(CommandLineConfigParam.existParam("-dev")); + assertFalse(CommandLineConfigParam.existParam("-dummyCommandName")); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineParserTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineParserTest.java new file mode 100644 index 00000000..1c6e660d --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineParserTest.java @@ -0,0 +1,97 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.command_line; + +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.JsonObject; +import java.lang.reflect.Constructor; + +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.mock; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({CommandLineParser.class, CommandLineAction.class, FieldAgent.class}) +public class CommandLineParserTest { + private CommandLineParser commandLineParser; + private String[] mockArguments = {"help", "-h", "--help"}; + private FieldAgent fieldAgent; + + @Before + public void setUp() throws Exception { + commandLineParser = mock(CommandLineParser.class); + mockStatic(CommandLineAction.class); + mockStatic(FieldAgent.class); + fieldAgent = mock(FieldAgent.class); + when(FieldAgent.getInstance()).thenReturn(fieldAgent); + when(fieldAgent.provision(anyString())).thenReturn(null); + when(CommandLineAction.getActionByKey(anyString())).thenReturn(CommandLineAction.HELP_ACTION); + when(CommandLineAction.getActionByKey(anyString()).perform(mockArguments)).thenReturn("Test perform"); + } + + @After + public void tearDown() throws Exception { + commandLineParser = null; + } + + /** + * Test parse method + * throws AgentUserException + */ + @Test(expected = AgentUserException.class) + public void throwsAgentUserExceptionWhenParse() throws AgentUserException { + when(CommandLineAction.getActionByKey(anyString())).thenReturn(CommandLineAction.PROVISION_ACTION); + when(fieldAgent.provision(anyString())).thenReturn(mock(JsonObject.class)); + when(CommandLineAction.getActionByKey(anyString()).perform(mockArguments)).thenThrow(mock(AgentUserException.class)); + commandLineParser.parse("provision key"); + PowerMockito.verifyStatic(CommandLineAction.class); + CommandLineAction.getActionByKey("provision"); + } + + /** + * Test parse method + */ + @Test + public void testParse() { + try { + assertEquals("Test perform", commandLineParser.parse("help")); + PowerMockito.verifyStatic(CommandLineAction.class); + CommandLineAction.getActionByKey("help"); + } catch (AgentUserException e) { + fail("This should never happen"); + } + } + + /** + * Test CommandLineParser constructor throws UnsupportedOperationException + */ + @Test(expected = UnsupportedOperationException.class) + public void testNotSupportedConstructor() throws Exception { + Constructor constructor = CommandLineParser.class.getDeclaredConstructor(); + constructor.setAccessible(true); + commandLineParser = constructor.newInstance(); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellExecutorTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellExecutorTest.java new file mode 100644 index 00000000..5b21df28 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellExecutorTest.java @@ -0,0 +1,123 @@ +package org.eclipse.iofog.command_line.util; +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; + +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({CommandShellExecutor.class}) +public class CommandShellExecutorTest { + private CommandShellExecutor commandShellExecutor; + private CommandShellResultSet, List> commandShellResultSet; + private String command; + List value; + List errors; + + @Before + public void setUp() throws Exception { + commandShellExecutor = spy(new CommandShellExecutor()); + } + + @After + public void tearDown() throws Exception { + command = null; + value = null; + errors = null; + commandShellResultSet = null; + } + + /** + * When execute command is supplied with valid command + */ + @Test + public void testExecuteCommandWithValidInput() { + + command = "echo Iofog"; + value = new ArrayList<>(); + value.add("Iofog"); + errors = new ArrayList<>(); + commandShellResultSet = new CommandShellResultSet<>(value, errors); + assertEquals(commandShellResultSet, commandShellExecutor.executeCommand(command)); + } + + /** + * When execute command is supplied with invalid command + */ + @Test + public void testExecuteCommandWithInvalidCommand() { + command = "some random command"; + value = new ArrayList<>(); + errors = new ArrayList<>(); + errors.add("/bin/sh: some: command not found"); + commandShellResultSet = new CommandShellResultSet<>(value, errors); + assertNotNull( commandShellExecutor.executeCommand(command)); + } + + /** + * When executeScript is called + */ + @Test + public void testExecuteScript() { + command = "echo"; + value = new ArrayList<>(); + errors = new ArrayList<>(); + errors.add("/bin/echo: /bin/echo: cannot execute binary file"); + commandShellResultSet = new CommandShellResultSet<>(value, errors); + assertNotNull(commandShellExecutor.executeScript(command, "agent")); + } + + /** + * When executeSDynamic is called with true value + */ + @Test + public void testExecuteDynamicCommandWithTrueInput() { + command = "echo"; + value = new ArrayList<>(); + errors = new ArrayList<>(); + commandShellResultSet = new CommandShellResultSet<>(value, errors); + assertNotNull(commandShellExecutor.executeDynamicCommand(command,commandShellResultSet, + new AtomicBoolean(true),new Thread())); + + } + + /** + * When executeSDynamic is called with false value + */ + @Test + public void testExecuteDynamicCommandWithFalseInput() { + command = "invalid"; + value = new ArrayList<>(); + errors = new ArrayList<>(); + commandShellResultSet = new CommandShellResultSet<>(value, errors); + assertNotNull(commandShellExecutor.executeDynamicCommand(command,commandShellResultSet, + new AtomicBoolean(false),new Thread())); + + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellResultSetTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellResultSetTest.java new file mode 100644 index 00000000..9ba322b9 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellResultSetTest.java @@ -0,0 +1,115 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.command_line.util; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; + +import static org.junit.Assert.*; +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({CommandShellResultSet.class}) +public class CommandShellResultSetTest { + private CommandShellResultSet commandShellResultSet; + List value; + List errors; + @Before + public void setUp() throws Exception { + value = new ArrayList<>(); + errors = new ArrayList<>(); + commandShellResultSet = new CommandShellResultSet<>(value, errors); + } + + @After + public void tearDown() throws Exception { + value = null; + errors = null; + commandShellResultSet = null; + } + + @Test + public void testGetError() { + assertNotNull(commandShellResultSet.getError()); + } + + @Test + public void testGetValue() { + assertNotNull(commandShellResultSet.getValue()); + } + + + @Test + public void testToString() { + assertNotNull(commandShellResultSet); + assertFalse(commandShellResultSet.toString().contains("@")); + } + + /** + * When objects are same + */ + @Test + public void testEqualsWhenObjectsAreSame() { + List value1 = new ArrayList<>(); + List errors1 = new ArrayList<>(); + CommandShellResultSet commandShellResultSetLocal = new CommandShellResultSet<>(value1, errors1); + assertTrue(commandShellResultSetLocal.equals(commandShellResultSet)); + } + + /** + * When objects are different + */ + @Test + public void testEqualsWhenObjectAreDifferent() { + List value1 = new ArrayList<>(); + value1.add("value"); + List errors1 = new ArrayList<>(); + CommandShellResultSet commandShellResultSetLocal = new CommandShellResultSet<>(value1, errors1); + assertFalse(commandShellResultSetLocal.equals(commandShellResultSet)); + } + + /** + * When objects are same + */ + @Test + public void testHashCodeWhenObjectAreSame() { + List value1 = new ArrayList<>(); + List errors1 = new ArrayList<>(); + CommandShellResultSet commandShellResultSetLocal = new CommandShellResultSet<>(value1, errors1); + assertEquals("HashCodes should be equal", commandShellResultSetLocal.hashCode(), commandShellResultSet.hashCode()); + assertTrue(commandShellResultSetLocal.equals(commandShellResultSet)); + } + + /** + * When objects are different + */ + @Test + public void testHashCodeWhenObjectNotSame() { + List value1 = new ArrayList<>(); + value1.add("value"); + List errors1 = new ArrayList<>(); + errors1.add("error"); + CommandShellResultSet commandShellResultSetLocal = new CommandShellResultSet<>(value1, errors1); + assertNotEquals("HashCodes should not be equal", commandShellResultSetLocal.hashCode(), commandShellResultSet.hashCode()); + assertFalse(commandShellResultSetLocal.equals(commandShellResultSet)); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/ImageDownloadManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/ImageDownloadManagerTest.java new file mode 100644 index 00000000..0c6c1f1f --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/ImageDownloadManagerTest.java @@ -0,0 +1,219 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.diagnostics; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientBuilder; +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.process_manager.DockerUtil; +import org.eclipse.iofog.utils.Orchestrator; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.internal.verification.VerificationModeFactory; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; + +/** + * Agent Exception + * + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ImageDownloadManager.class, DockerUtil.class, DockerClientBuilder.class, DockerClient.class, + LoggingService.class, Orchestrator.class, DefaultDockerClientConfig.class, + Configuration.class, Container.class, CommandShellExecutor.class}) +public class ImageDownloadManagerTest { + private ImageDownloadManager imageDownloadManager; + private Orchestrator orchestrator; + private DockerUtil dockerUtil; + private String microserviceUuid; + private DockerClient dockerClient; + private DefaultDockerClientConfig defaultDockerClientConfig; + private DockerClientBuilder dockerClientBuilder; + private Container container; + private LoggingService loggingService; + private CommandShellResultSet, List> resultSetWithPath; + private List error; + private List value; + private String MODULE_NAME; + + @Before + public void setUp() throws Exception { + microserviceUuid = "microservice-id"; + imageDownloadManager = mock(ImageDownloadManager.class); + mockStatic(Configuration.class); + when(Configuration.getDockerUrl()).thenReturn("unix://dockerUrl/"); + when(Configuration.getDockerApiVersion()).thenReturn("19.03.1"); + orchestrator = mock(Orchestrator.class); + defaultDockerClientConfig = mock(DefaultDockerClientConfig.class); + dockerClientBuilder = mock(DockerClientBuilder.class); + mockStatic(DockerClientBuilder.class); + dockerClient = mock(DockerClient.class); + mockStatic(DockerClient.class); + mockStatic(CommandShellExecutor.class); + when(DockerClientBuilder.getInstance(any(DefaultDockerClientConfig.class))).thenReturn(dockerClientBuilder); + when(dockerClientBuilder.build()).thenReturn(dockerClient); + dockerUtil = mock(DockerUtil.class); + mockStatic(DockerUtil.class); + container = mock(Container.class); + when(DockerUtil.getInstance()).thenReturn(dockerUtil); + loggingService = mock(LoggingService.class); + mockStatic(LoggingService.class); + MODULE_NAME = "Image Download Manager"; + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + return null; + } + }). + when(orchestrator).sendFileToController(any(), any()); + } + + @After + public void tearDown() throws Exception { + error = null; + value = null; + resultSetWithPath = null; + MODULE_NAME = null; + } + + /** + * When DockerUtil.getInstance().getContainer(microserviceUuid) returns null + * then createImageSnapshot returns + */ + @Test + public void createImageSnapshotWhenGetContainerReturnsNull() { + when(dockerUtil.getContainer(microserviceUuid)).thenReturn(null); + imageDownloadManager.createImageSnapshot(orchestrator, microserviceUuid); + verify(dockerUtil, atLeastOnce()).getContainer(microserviceUuid); + PowerMockito.verifyStatic(LoggingService.class, VerificationModeFactory.times(1)); + LoggingService.logWarning(MODULE_NAME, "Image snapshot: container not running."); + } + + /** + * When DockerUtil.getInstance().getContainer(microserviceUuid) returns a container, + * CommandShellExecutor.executeCommand returns a resultset with value and no error + */ + @Test + public void createImageSnapshotWhenCommandExecuteReturnsSuccess() throws Exception { + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add("local/path/newFile"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(dockerUtil.getContainer(microserviceUuid)).thenReturn(Optional.of(container)); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + imageDownloadManager.createImageSnapshot(orchestrator, microserviceUuid); + verify(dockerUtil, atLeastOnce()).getContainer(microserviceUuid); + verify(orchestrator, atLeastOnce()).sendFileToController(any(), any()); + PowerMockito.verifyStatic(LoggingService.class, VerificationModeFactory.times(1)); + LoggingService.logInfo(MODULE_NAME, "Image snapshot newFile deleted"); + LoggingService.logInfo(MODULE_NAME, "Finished Create image snapshot"); + } + + /** + * When DockerUtil.getInstance().getContainer(microserviceUuid) returns a container, + * CommandShellExecutor.executeCommand returns a resultset with error and no value + */ + @Test + public void createImageSnapshotWhenCommandExecuteReturnsError() throws Exception { + error = new ArrayList<>(); + value = new ArrayList<>(); + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(dockerUtil.getContainer(microserviceUuid)).thenReturn(Optional.of(container)); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + imageDownloadManager.createImageSnapshot(orchestrator, microserviceUuid); + verify(dockerUtil, atLeastOnce()).getContainer(microserviceUuid); + verify(orchestrator, never()).sendFileToController(any(), any()); + PowerMockito.verifyStatic(LoggingService.class, VerificationModeFactory.times(1)); + LoggingService.logWarning(MODULE_NAME, "error=[error], value=[]"); + + } + + + /** + * When DockerUtil.getInstance().getContainer(microserviceUuid) returns a container, + * CommandShellExecutor.executeCommand returns a resultset is Empty + */ + @Test + public void createImageSnapshotWhenCommandExecuteReturnsEmpty() throws Exception { + error = new ArrayList<>(); + value = new ArrayList<>(); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(dockerUtil.getContainer(microserviceUuid)).thenReturn(Optional.of(container)); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + imageDownloadManager.createImageSnapshot(orchestrator, microserviceUuid); + verify(dockerUtil, atLeastOnce()).getContainer(microserviceUuid); + verify(orchestrator, never()).sendFileToController(any(), any()); + PowerMockito.verifyStatic(LoggingService.class, VerificationModeFactory.times(1)); + LoggingService.logDebug(MODULE_NAME, "Finished Create image snapshot"); + } + + /** + * When DockerUtil.getInstance().getContainer(microserviceUuid) returns a container, + * CommandShellExecutor.executeCommand returns a resultset value is blank + */ + @Test + public void createImageSnapshotWhenCommandExecuteReturnsBlankValue() throws Exception { + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add(""); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(dockerUtil.getContainer(microserviceUuid)).thenReturn(Optional.of(container)); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + imageDownloadManager.createImageSnapshot(orchestrator, microserviceUuid); + verify(dockerUtil, atLeastOnce()).getContainer(microserviceUuid); + verify(orchestrator, atLeastOnce()).sendFileToController(any(), any()); + PowerMockito.verifyStatic(LoggingService.class, VerificationModeFactory.times(1)); + LoggingService.logDebug(MODULE_NAME, "Finished Create image snapshot"); + } + + /** + * When DockerUtil.getInstance().getContainer(microserviceUuid) returns a container, + * Orchestrator.sendFiletoController returns Exception + */ + @Test + public void throwsExceptionWhenCreateImageSnapshotCallsOrchestrator() throws Exception { + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add(""); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(dockerUtil.getContainer(microserviceUuid)).thenReturn(Optional.of(container)); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + doThrow(new Exception("Error")).when(orchestrator).sendFileToController(any(), any()); + imageDownloadManager.createImageSnapshot(orchestrator, microserviceUuid); + verify(dockerUtil, atLeastOnce()).getContainer(microserviceUuid); + verify(orchestrator, atLeastOnce()).sendFileToController(any(), any()); + PowerMockito.verifyStatic(LoggingService.class, VerificationModeFactory.times(1)); + LoggingService.logError(any(), any(), any()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceDataTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceDataTest.java new file mode 100644 index 00000000..6288da50 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceDataTest.java @@ -0,0 +1,178 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.diagnostics.strace; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +import static org.junit.Assert.*; + +/** + * Agent Exception + * + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MicroserviceStraceData.class}) +public class MicroserviceStraceDataTest { + private MicroserviceStraceData microserviceStraceData; + private String microserviceUuid; + private int pid; + private boolean straceRun; + private List resultBuffer; + + @Before + public void setUp() throws Exception { + microserviceUuid = "microserviceUuid"; + pid = 4001; + straceRun = true; + microserviceStraceData = new MicroserviceStraceData(microserviceUuid, pid, straceRun); + resultBuffer = new CopyOnWriteArrayList<>(); + + } + + @After + public void tearDown() throws Exception { + microserviceUuid = null; + pid = 0; + resultBuffer = null; + } + + /** + * Test getMicroserviceUuid + */ + @Test + public void testGetMicroserviceUuid() { + assertEquals(microserviceUuid, microserviceStraceData.getMicroserviceUuid()); + } + + /** + * Test get and set of Pid + */ + @Test + public void testGetAndSetPid() { + assertEquals(pid, microserviceStraceData.getPid()); + microserviceStraceData.setPid(4002); + assertNotEquals(pid, microserviceStraceData.getPid()); + + } + + /** + * Test get and set of straceRun + */ + @Test + public void testGetAndSetStraceRun() { + assertEquals(straceRun, microserviceStraceData.getStraceRun().get()); + microserviceStraceData.setStraceRun(false); + assertEquals(false, microserviceStraceData.getStraceRun().get()); + assertTrue(microserviceStraceData.equals(microserviceStraceData)); + } + + /** + * Test toString + */ + @Test + public void testToString() { + MicroserviceStraceData newMicroserviceStraceData = new MicroserviceStraceData(microserviceUuid, pid, straceRun); + assertFalse(microserviceStraceData.toString().contains("@")); + assertEquals(microserviceStraceData.toString(), newMicroserviceStraceData.toString()); + assertTrue(microserviceStraceData.equals(newMicroserviceStraceData)); + + } + + /** + * When asserting equals with same object. + */ + @Test + public void testEqualsTestWhenObjectsAreSame() { + assertTrue(microserviceStraceData.equals(microserviceStraceData)); + } + + /** + * When asserting equals with different object but equal values. + */ + @Test + public void testEqualsTestWhenObjectIsDifferentButValuesAreSame() { + MicroserviceStraceData anotherMicroserviceStraceData = new MicroserviceStraceData(microserviceUuid, pid, straceRun); + assertTrue(microserviceStraceData.equals(anotherMicroserviceStraceData)); + } + + /** + * When asserting equals with different object and different values. + */ + @Test + public void testEqualsTestWhenObjectIsDifferent() { + MicroserviceStraceData anotherMicroserviceStraceData = new MicroserviceStraceData("newUuid", pid, straceRun); + assertFalse(microserviceStraceData.equals(anotherMicroserviceStraceData)); + } + + /** + * When asserting equals with object of different type + */ + @Test + public void testEqualsTestWhenObjectIsOfDifferentType() { + Object diffObject = new Object(); + assertFalse(microserviceStraceData.equals(diffObject)); + } + + /** + * when objects are equal + */ + @Test + public void testHashCodeWhenObjectAreEqual() { + MicroserviceStraceData anotherMicroserviceStraceData = new MicroserviceStraceData(microserviceUuid, pid, straceRun); + assertTrue(microserviceStraceData.equals(anotherMicroserviceStraceData)); + assertEquals(microserviceStraceData.hashCode(), anotherMicroserviceStraceData.hashCode()); + } + + /** + * when objects are different + */ + @Test + public void testHashCodeWhenObjectAreDifferent() { + MicroserviceStraceData anotherMicroserviceStraceData = new MicroserviceStraceData(microserviceUuid, 4002, straceRun); + assertFalse(microserviceStraceData.equals(anotherMicroserviceStraceData)); + assertNotEquals(microserviceStraceData.hashCode(), anotherMicroserviceStraceData.hashCode()); + } + + /** + * Test get and set of ResultBuffer + */ + @Test + public void testGetAndSetResultBuffer() { + resultBuffer.add("data"); + microserviceStraceData.setResultBuffer(resultBuffer); + assertEquals(resultBuffer, microserviceStraceData.getResultBuffer()); + assertTrue(microserviceStraceData.equals(microserviceStraceData)); + assertEquals(microserviceStraceData.hashCode(), microserviceStraceData.hashCode()); + } + + /** + * Test get ResultBufferAsString + */ + @Test + public void testGetResultBufferAsString() { + resultBuffer.add("data"); + microserviceStraceData.setResultBuffer(resultBuffer); + assertTrue(microserviceStraceData.getResultBufferAsString() instanceof String); + assertEquals("data\n", microserviceStraceData.getResultBufferAsString()); + assertTrue(microserviceStraceData.getResultBuffer() instanceof List); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManagerTest.java new file mode 100644 index 00000000..fb7397b0 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManagerTest.java @@ -0,0 +1,349 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.diagnostics.strace; + +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonValue; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + + +/** + * Agent Exception + * + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({StraceDiagnosticManager.class, + LoggingService.class, CommandShellExecutor.class}) +public class StraceDiagnosticManagerTest { + private StraceDiagnosticManager straceDiagnosticManager; + private JsonObject jsonObject; + private JsonArray jsonArray; + private JsonValue jsonValue; + private Iterator iterator; + private JsonObject microserviceObject; + private CommandShellResultSet, List> resultSetWithPath; + private List error; + private List value; + private String microserviceUuid; + private MicroserviceStraceData microserviceStraceData; + private String MODULE_NAME; + + @Before + public void setUp() throws Exception { + microserviceUuid = "microserviceUuid"; + PowerMockito.mockStatic(CommandShellExecutor.class); + PowerMockito.mockStatic(LoggingService.class); + jsonObject = mock(JsonObject.class); + jsonArray = mock(JsonArray.class); + when(jsonObject.containsKey("straceValues")).thenReturn(true); + when(jsonObject.getJsonArray("straceValues")).thenReturn(jsonArray); + iterator = mock(Iterator.class); + microserviceObject = mock(JsonObject.class); + when(jsonArray.iterator()).thenReturn(iterator); + when(iterator.hasNext()).thenReturn(true, false); + when(iterator.next()).thenReturn(microserviceObject); + when(microserviceObject.containsKey("microserviceUuid")).thenReturn(true); + when(microserviceObject.getString("microserviceUuid")).thenReturn("microserviceUuid"); + when(microserviceObject.getBoolean("straceRun")).thenReturn(true); + straceDiagnosticManager = StraceDiagnosticManager.getInstance(); + MODULE_NAME = "STrace Diagnostic Manager"; + removeDummyMonitoringServices(); + } + + @After + public void tearDown() throws Exception { + microserviceUuid = null; + jsonObject = null; + straceDiagnosticManager = null; + iterator = null; + reset(microserviceObject); + microserviceObject = null; + value = null; + error = null; + resultSetWithPath = null; + microserviceStraceData = null; + MODULE_NAME = null; + } + + /** + * when updateMonitoringMicroservices is called with valid diagnosticData and StraceRun as true + */ + @Test + public void doesUpdateMonitoringMicroservicesWhenValidMicroserviceUuidAndEnableStraceRun() { + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add("pid 1234"); + value.add("pid 2345"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + PowerMockito.when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + straceDiagnosticManager.updateMonitoringMicroservices(jsonObject); + Mockito.verify(jsonObject, Mockito.times(1)).getJsonArray("straceValues"); + Mockito.verify(iterator, Mockito.atLeastOnce()).hasNext(); + Mockito.verify(microserviceObject, Mockito.atLeastOnce()).getString("microserviceUuid"); + Mockito.verify(microserviceObject, Mockito.atLeastOnce()).getBoolean("straceRun"); + PowerMockito.verifyStatic(CommandShellExecutor.class, Mockito.times(1)); + CommandShellExecutor.executeCommand(any()); + } + + /** + * when updateMonitoringMicroservices is called with valid diagnosticData and StraceRun as false + */ + @Test + public void doesUpdateMonitoringMicroservicesWhenValidMicroserviceUuidAndDisabledStraceRun() { + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add("pid 1234"); + value.add("pid 2345"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + when(microserviceObject.getBoolean("straceRun")).thenReturn(false); + straceDiagnosticManager.updateMonitoringMicroservices(jsonObject); + Mockito.verify(jsonObject, Mockito.times(1)).getJsonArray("straceValues"); + Mockito.verify(iterator, Mockito.atLeastOnce()).hasNext(); + Mockito.verify(microserviceObject, Mockito.atLeastOnce()).getString("microserviceUuid"); + Mockito.verify(microserviceObject, Mockito.atLeastOnce()).getBoolean("straceRun"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Disabling microservice strace diagnostics for miroservice : microserviceUuid"); + } + + /** + * when updateMonitoringMicroservices is called with valid diagnosticData and StraceRun as true + * But getPid returns IllegalArgumentException + */ + @Test + public void throwsIllegalExceptionWhenPidByContainerNameIsNotFound() { + error = new ArrayList<>(); + value = new ArrayList<>(); + resultSetWithPath = new CommandShellResultSet<>(value, error); + PowerMockito.when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + straceDiagnosticManager.updateMonitoringMicroservices(jsonObject); + Mockito.verify(jsonObject, Mockito.times(1)).getJsonArray("straceValues"); + Mockito.verify(iterator, Mockito.atLeastOnce()).hasNext(); + Mockito.verify(microserviceObject, Mockito.atLeastOnce()).getString("microserviceUuid"); + Mockito.verify(microserviceObject, Mockito.atLeastOnce()).getBoolean("straceRun"); + PowerMockito.verifyStatic(CommandShellExecutor.class, Mockito.times(1)); + CommandShellExecutor.executeCommand(any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.times(1)); + LoggingService.logError(any(), any(), any()); + } + + /** + * when updateMonitoringMicroservices is called with invalid diagnosticData + * Doesn't contain straceValues + */ + @Test + public void doesNotUpdateMonitoringMicroservicesWhenDiagnosticDataIsInvalid() { + when(jsonObject.containsKey("straceValues")).thenReturn(false); + straceDiagnosticManager.updateMonitoringMicroservices(jsonObject); + Mockito.verify(jsonObject, Mockito.never()).getJsonArray("straceValues"); + + } + + /** + * when updateMonitoringMicroservices is called with invalid diagnosticData + * straceValues is empty + */ + @Test + public void doesNotUpdateMonitoringMicroservicesWhenStraceValuesIsInvalid() { + when(iterator.hasNext()).thenReturn(false, false); + straceDiagnosticManager.updateMonitoringMicroservices(jsonObject); + Mockito.verify(jsonObject, Mockito.atLeastOnce()).getJsonArray("straceValues"); + Mockito.verify(iterator, Mockito.times(1)).hasNext(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.times(1)); + LoggingService.logDebug(MODULE_NAME, + "Finished update strace monitoring microservices"); + + } + + /** + * when updateMonitoringMicroservices is called with invalid diagnosticData + * straceValues doesn't contain microserviceUuid + */ + @Test + public void doesNotUpdateMonitoringMicroservicesWhenStraceValuesHaveNoMicroserviceUuid() { + when(microserviceObject.containsKey("microserviceUuid")).thenReturn(false); + straceDiagnosticManager.updateMonitoringMicroservices(jsonObject); + Mockito.verify(jsonObject, Mockito.atLeastOnce()).getJsonArray("straceValues"); + Mockito.verify(microserviceObject, Mockito.atLeastOnce()).containsKey("microserviceUuid"); + Mockito.verify(iterator, Mockito.times(2)).hasNext(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.times(1)); + LoggingService.logDebug(MODULE_NAME, + "Finished update strace monitoring microservices"); + } + + /** + * when updateMonitoringMicroservices is called with diagnosticData as null + */ + @Test + public void doesNotUpdateMonitoringMicroservicesWhenDiagnosticDataIsNull() { + straceDiagnosticManager.updateMonitoringMicroservices(null); + Mockito.verify(jsonObject, Mockito.never()).getJsonArray("straceValues"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.times(1)); + LoggingService.logDebug(MODULE_NAME, + "Finished update strace monitoring microservices"); + } + + /** + * when updateMonitoringMicroservices is called with diagnosticData + * microservice is null + */ + @Test + public void doesNotUpdateMonitoringMicroservicesWhenStraceMicroserviceChangesIsNull() { + when(jsonObject.getJsonArray("straceValues")).thenReturn(null); + straceDiagnosticManager.updateMonitoringMicroservices(jsonObject); + Mockito.verify(jsonObject, Mockito.times(1)).getJsonArray("straceValues"); + Mockito.verify(iterator, Mockito.never()).hasNext(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.times(1)); + LoggingService.logDebug(MODULE_NAME, + "Finished update strace monitoring microservices"); + + } + + /** + * Asserts mock is same as the StraceDiagnosticManager.getInstance() + */ + @Test + public void testGetInstanceIsSameAsMock() { + straceDiagnosticManager = mock(StraceDiagnosticManager.class); + PowerMockito.mockStatic(StraceDiagnosticManager.class); + when(straceDiagnosticManager.getInstance()).thenReturn(straceDiagnosticManager); + assertSame(straceDiagnosticManager, StraceDiagnosticManager.getInstance()); + } + + /** + * Asserts straceDiagnosticManager.getMonitoringMicroservices() + */ + @Test + public void testGetMonitoringMicroservices() { + assertEquals(0, straceDiagnosticManager.getMonitoringMicroservices().size()); + microserviceStraceData = new MicroserviceStraceData(microserviceUuid, 5, true); + straceDiagnosticManager.getMonitoringMicroservices().add(microserviceStraceData); + assertEquals(1, straceDiagnosticManager.getMonitoringMicroservices().size()); + + } + + /** + * Test enableMicroserviceStraceDiagnostics with valid microserviceUuid + */ + @Test + public void testEnableMicroserviceStraceDiagnosticsWithValidMicroserviceUuid() { + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add("pid 1234"); + value.add("pid 2345"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + straceDiagnosticManager.enableMicroserviceStraceDiagnostics(microserviceUuid); + PowerMockito.verifyStatic(LoggingService.class, Mockito.times(1)); + LoggingService.logInfo(MODULE_NAME, + "Start enable microservice for strace diagnostics : microserviceUuid"); + LoggingService.logInfo(MODULE_NAME, + "Start getting pid of microservice by container name : microserviceUuid"); + LoggingService.logInfo(MODULE_NAME, + "Finished enable microservice for strace diagnostics : microserviceUuid"); + } + + /** + * Test enableMicroserviceStraceDiagnostics with invalid microserviceUuid + */ + @Test + public void testEnableMicroserviceStraceDiagnosticsWithInvalidMicroserviceUuid() { + error = new ArrayList<>(); + value = new ArrayList<>(); + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + straceDiagnosticManager.enableMicroserviceStraceDiagnostics(null); + PowerMockito.verifyStatic(LoggingService.class, Mockito.times(1)); + LoggingService.logInfo(MODULE_NAME, + "Start enable microservice for strace diagnostics : null"); + LoggingService.logInfo(MODULE_NAME, + "Start getting pid of microservice by container name : null"); + LoggingService.logInfo(MODULE_NAME, + "Finished enable microservice for strace diagnostics : null"); + LoggingService.logError(any(), any(), any()); + } + + /** + * Test disableMicroserviceStraceDiagnostics with valid microserviceUuid + */ + @Test + public void testDisableMicroserviceStraceDiagnosticsWhenMicroserviceUuidIsPresent() { + microserviceStraceData = new MicroserviceStraceData(microserviceUuid, 5, true); + straceDiagnosticManager.getMonitoringMicroservices().add(microserviceStraceData); + straceDiagnosticManager.disableMicroserviceStraceDiagnostics(microserviceUuid); + assertEquals(0, straceDiagnosticManager.getMonitoringMicroservices().size()); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, + "Disabling microservice strace diagnostics for miroservice : microserviceUuid"); + } + + /** + * Test disableMicroserviceStraceDiagnostics with microserviceUuid which is not present + */ + @Test + public void testDisableMicroserviceStraceDiagnosticsWhenMicroserviceUuidIsNotPresent() { + microserviceStraceData = new MicroserviceStraceData("newMicroserviceUuid", 1234, true); + straceDiagnosticManager.getMonitoringMicroservices().add(microserviceStraceData); + straceDiagnosticManager.disableMicroserviceStraceDiagnostics(microserviceUuid); + assertEquals(1, straceDiagnosticManager.getMonitoringMicroservices().size()); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, + "Disabling microservice strace diagnostics for miroservice : microserviceUuid"); + } + + /** + * Test disableMicroserviceStraceDiagnostics with microserviceUuid null + */ + @Test + public void testDisableMicroserviceStraceDiagnosticsWhenMicroserviceUuidIsNull() { + straceDiagnosticManager.disableMicroserviceStraceDiagnostics(null); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, + "Disabling microservice strace diagnostics for miroservice : null"); + } + + /** + * method to empty monitoringservices + */ + private void removeDummyMonitoringServices() { + if (straceDiagnosticManager.getMonitoringMicroservices() != null && + straceDiagnosticManager.getMonitoringMicroservices().size() > 0) { + for (MicroserviceStraceData data : straceDiagnosticManager.getMonitoringMicroservices()) { + straceDiagnosticManager.getMonitoringMicroservices().remove(data); + } + } + + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/edge_resources/EdgeResourceManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/edge_resources/EdgeResourceManagerTest.java new file mode 100644 index 00000000..f1675f8f --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/edge_resources/EdgeResourceManagerTest.java @@ -0,0 +1,101 @@ +package org.eclipse.iofog.edge_resources; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({EdgeResourceManager.class}) +public class EdgeResourceManagerTest { + private EdgeResourceManager edgeResourceManager; + + @Before + public void setUp() throws Exception { + edgeResourceManager = Mockito.spy(EdgeResourceManager.class); + setMock(edgeResourceManager); + } + /** + * Set a mock to the {@link EdgeResourceManager} instance + * Throws {@link RuntimeException} in case if reflection failed, see a {@link Field#set(Object, Object)} method description. + * @param mock the mock to be inserted to a class + */ + private void setMock(EdgeResourceManager mock) { + try { + Field instance = EdgeResourceManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(instance, mock); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @After + public void tearDown() throws Exception { + Field instance = EdgeResourceManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(null, null); + } + + /** + * Asserts mock is same as the StraceDiagnosticManager.getInstance() + */ + @Test + public void testGetInstanceIsSameAsMock() { + assertEquals(edgeResourceManager, EdgeResourceManager.getInstance()); + } + + @Test + public void testGetAndSetLatestEdgeResources() { + assertEquals(0, edgeResourceManager.getLatestEdgeResources().size()); + EdgeResource edgeResource = mock(EdgeResource.class); + List edgeResourceList = new ArrayList<>(); + edgeResourceList.add(edgeResource); + edgeResourceManager.setLatestEdgeResources(edgeResourceList); + assertEquals(edgeResourceManager.getLatestEdgeResources().get(0), edgeResource); + assertEquals(edgeResourceManager.getLatestEdgeResources().size(), edgeResourceList.size()); + } + + @Test + public void testGetAndCurrentEdgeResources() { + assertEquals(0, edgeResourceManager.getCurrentEdgeResources().size()); + EdgeResource edgeResource = mock(EdgeResource.class); + List edgeResourceList = new ArrayList<>(); + edgeResourceList.add(edgeResource); + edgeResourceManager.setCurrentEdgeResources(edgeResourceList); + assertEquals(edgeResourceManager.getCurrentEdgeResources().size(), edgeResourceList.size()); + assertEquals(edgeResourceManager.getCurrentEdgeResources().get(0), edgeResource); + } + + @Test + public void testClear() { + EdgeResource edgeResource = mock(EdgeResource.class); + List edgeResourceList = new ArrayList<>(); + edgeResourceList.add(edgeResource); + edgeResourceManager.setCurrentEdgeResources(edgeResourceList); + edgeResourceManager.setLatestEdgeResources(edgeResourceList); + assertEquals(edgeResourceManager.getCurrentEdgeResources().size(), edgeResourceList.size()); + assertEquals(edgeResourceManager.getLatestEdgeResources().size(), edgeResourceList.size()); + edgeResourceManager.clear(); + assertEquals(0, edgeResourceManager.getCurrentEdgeResources().size()); + assertEquals(0, edgeResourceManager.getLatestEdgeResources().size()); + + + } + @Test(expected = UnsupportedOperationException.class) + public void testIfListIsImmutable(){ + edgeResourceManager.getCurrentEdgeResources().add(null); + edgeResourceManager.getLatestEdgeResources().add(null); + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentStatusTest.java new file mode 100644 index 00000000..60dfb403 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentStatusTest.java @@ -0,0 +1,83 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.field_agent; + +import org.eclipse.iofog.utils.Constants; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static java.lang.System.currentTimeMillis; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest(FieldAgentStatus.class) +public class FieldAgentStatusTest { + private FieldAgentStatus fieldAgentStatus; + private Constants.ControllerStatus controllerStatus; + private long lastCommandTime; + private boolean controllerVerified; + @Before + public void setUp() throws Exception { + fieldAgentStatus = spy(new FieldAgentStatus()); + controllerStatus = Constants.ControllerStatus.OK; + lastCommandTime = currentTimeMillis(); + controllerVerified = true; + } + + @After + public void tearDown() throws Exception { + } + + /** + * Test get and set method of controllerStatus + */ + @Test + public void testGetterAndSetterOfControllerStatus() { + assertEquals("Default Status", + Constants.ControllerStatus.NOT_CONNECTED, fieldAgentStatus.getControllerStatus()); + fieldAgentStatus.setControllerStatus(controllerStatus); + assertEquals("Status after update", + Constants.ControllerStatus.OK, fieldAgentStatus.getControllerStatus()); + } + + /** + * Test get and set method of lastCommandTime + */ + @Test + public void testGetterAndSetterOfLastCommandTime() { + fieldAgentStatus.setLastCommandTime(lastCommandTime); + assertEquals("lastCommandTime after update", + lastCommandTime, fieldAgentStatus.getLastCommandTime()); + } + + /** + * Test get and set method of controllerVerified + */ + @Test + public void testGetterAndSetterOfControllerVerified() { + assertEquals("Default Status", + false, fieldAgentStatus.isControllerVerified()); + fieldAgentStatus.setControllerVerified(controllerVerified); + assertEquals("controllerVerified after update", + controllerVerified, fieldAgentStatus.isControllerVerified()); + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentTest.java new file mode 100644 index 00000000..ff5406e3 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentTest.java @@ -0,0 +1,1706 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.field_agent; + +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.edge_resources.EdgeResource; +import org.eclipse.iofog.edge_resources.EdgeResourceManager; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.enums.RequestType; +import org.eclipse.iofog.local_api.LocalApi; +import org.eclipse.iofog.message_bus.MessageBus; +import org.eclipse.iofog.message_bus.MessageBusStatus; +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.process_manager.ProcessManager; +import org.eclipse.iofog.process_manager.ProcessManagerStatus; +import org.eclipse.iofog.proxy.SshConnection; +import org.eclipse.iofog.proxy.SshProxyManager; +import org.eclipse.iofog.proxy.SshProxyManagerStatus; +import org.eclipse.iofog.resource_consumption_manager.ResourceConsumptionManagerStatus; +import org.eclipse.iofog.resource_manager.ResourceManagerStatus; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.status_reporter.StatusReporterStatus; +import org.eclipse.iofog.supervisor.SupervisorStatus; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.Orchestrator; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.net.ssl.SSLHandshakeException; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.security.cert.CertificateException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.iofog.resource_manager.ResourceManager.COMMAND_USB_INFO; +import static org.junit.Assert.*; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.anyBoolean; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({FieldAgent.class, LoggingService.class, FieldAgentStatus.class, MicroserviceManager.class, + Orchestrator.class, URL.class, HttpURLConnection.class, Configuration.class, StatusReporter.class, + SshProxyManager.class, ProcessManager.class, MessageBus.class, LocalApi.class, Thread.class, BufferedReader.class, + InputStreamReader.class, ResourceManagerStatus.class, IOFogNetworkInterfaceManager.class, VersionHandler.class, CommandShellExecutor.class, EdgeResourceManager.class, + ScheduledExecutorService.class, ScheduledFuture.class}) +public class FieldAgentTest { + private FieldAgent fieldAgent; + private String MODULE_NAME; + private Orchestrator orchestrator = null; + private JsonObject jsonObject; + private JsonObject provisionJsonObject; + private JsonObjectBuilder jsonObjectBuilder = null; + private URL url; + private HttpURLConnection httpURLConnection; + private FieldAgentStatus fieldAgentStatus; + private MicroserviceManager microserviceManager; + private SshProxyManager sshProxyManager; + private ProcessManager processManager; + private MessageBus messageBus; + private LocalApi localApi; + private Thread thread; + private BufferedReader bufferedReader; + private InputStreamReader inputStreamReader; + private ResourceManagerStatus resourceManagerStatus; + private Method method = null; + private IOFogNetworkInterfaceManager ioFogNetworkInterfaceManager; + private EdgeResourceManager edgeResourceManager; + + @Before + public void setUp() throws Exception { + mockStatic(LoggingService.class); + mockStatic(StatusReporter.class); + mockStatic(Configuration.class); + mockStatic(ProcessManager.class); + mockStatic(Orchestrator.class); + mockStatic(MessageBus.class); + mockStatic(LocalApi.class); + mockStatic(VersionHandler.class); + mockStatic(CommandShellExecutor.class); + mockStatic(IOFogNetworkInterfaceManager.class); + mockStatic(BufferedReader.class); + mockStatic(InputStreamReader.class); + mockStatic(EdgeResourceManager.class); + + orchestrator = PowerMockito.mock(Orchestrator.class); + sshProxyManager = PowerMockito.mock(SshProxyManager.class); + processManager = PowerMockito.mock(ProcessManager.class); + messageBus = PowerMockito.mock(MessageBus.class); + localApi = PowerMockito.mock(LocalApi.class); + resourceManagerStatus = PowerMockito.mock(ResourceManagerStatus.class); + edgeResourceManager = PowerMockito.mock(EdgeResourceManager.class); + mockConfiguration(); + mockOthers(); + fieldAgent = PowerMockito.spy(FieldAgent.getInstance()); + fieldAgentStatus = PowerMockito.mock(FieldAgentStatus.class); + ioFogNetworkInterfaceManager = PowerMockito.mock(IOFogNetworkInterfaceManager.class); + setMock(fieldAgent); + MODULE_NAME = "Field Agent"; + when(StatusReporter.getFieldAgentStatus()).thenReturn(fieldAgentStatus); + when(StatusReporter.setFieldAgentStatus()).thenReturn(fieldAgentStatus); + when(StatusReporter.setResourceManagerStatus()).thenReturn(resourceManagerStatus); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.NOT_PROVISIONED); + microserviceManager = PowerMockito.mock(MicroserviceManager.class); + PowerMockito.mockStatic(MicroserviceManager.class); + PowerMockito.whenNew(Orchestrator.class).withNoArguments().thenReturn(orchestrator); + PowerMockito.whenNew(SshProxyManager.class).withArguments(Mockito.any(SshConnection.class)).thenReturn(sshProxyManager); + when(MicroserviceManager.getInstance()).thenReturn(microserviceManager); + when(EdgeResourceManager.getInstance()).thenReturn(edgeResourceManager); + when(ProcessManager.getInstance()).thenReturn(processManager); + when(MessageBus.getInstance()).thenReturn(messageBus); + when(LocalApi.getInstance()).thenReturn(localApi); + PowerMockito.doNothing().when(processManager).deleteRemainingMicroservices(); + when(orchestrator.request(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(PowerMockito.mock(JsonObject.class)); + url = PowerMockito.mock(URL.class); + httpURLConnection = PowerMockito.mock(HttpURLConnection.class); + whenNew(URL.class).withArguments(Mockito.any()).thenReturn(url); + when(url.openConnection()).thenReturn(httpURLConnection ); + when(httpURLConnection.getResponseCode()).thenReturn(200); + PowerMockito.doNothing().when(httpURLConnection).disconnect(); + bufferedReader = PowerMockito.mock(BufferedReader.class); + inputStreamReader = PowerMockito.mock(InputStreamReader.class); + PowerMockito.whenNew(InputStreamReader.class).withParameterTypes(InputStream.class, Charset.class). + withArguments(Mockito.any(String.class), Mockito.eq(UTF_8)).thenReturn(inputStreamReader); + PowerMockito.whenNew(InputStreamReader.class).withParameterTypes(InputStream.class, Charset.class). + withArguments(Mockito.eq(null), Mockito.eq(UTF_8)).thenReturn(inputStreamReader); + PowerMockito.whenNew(BufferedReader.class).withArguments(inputStreamReader).thenReturn(bufferedReader); + when(bufferedReader.readLine()).thenReturn("Response from HAL").thenReturn(null); + when(VersionHandler.isReadyToUpgrade()).thenReturn(false); + when(VersionHandler.isReadyToRollback()).thenReturn(false); + jsonObjectBuilder = Json.createObjectBuilder(); + jsonObject = jsonObjectBuilder + .add("uuid", "uuid") + .add("token", "token") + .add("message", "success").build(); + provisionJsonObject = jsonObjectBuilder + .add("uuid", "uuid") + .add("token", "token") + .add("message", "success").build(); + PowerMockito.doNothing().when(processManager).updateMicroserviceStatus(); + + } + + @After + public void tearDown() throws Exception { + Field instance = FieldAgent.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(null, null); + MODULE_NAME = null; + fieldAgent = null; + Mockito.reset(fieldAgentStatus, orchestrator, bufferedReader, inputStreamReader); + if (method != null) { + method.setAccessible(false); + } + jsonObject = null; + } + /** + * Set a mock to the {@link FieldAgent} instance + * Throws {@link RuntimeException} in case if reflection failed, see a {@link Field#set(Object, Object)} method description. + * @param mock the mock to be inserted to a class + */ + private void setMock(FieldAgent mock) { + try { + Field instance = FieldAgent.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(instance, mock); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void initiateMockStart() { + thread = PowerMockito.mock(Thread.class); + try { + whenNew(Thread.class).withParameterTypes(Runnable.class,String.class).withArguments(Mockito.any(Runnable.class), + Mockito.anyString()).thenReturn(thread); + PowerMockito.doNothing().when(thread).start(); + fieldAgent.start(); + } catch (Exception e) { + fail("this should not happen"); + } + + } + + /** + * Test module index of FieldAgent + */ + @Test + public void testGetModuleIndex() { + assertEquals(5, fieldAgent.getModuleIndex()); + } + + /** + * Test Module Name of FieldAgent + */ + @Test + public void getModuleName() { + assertEquals(MODULE_NAME, fieldAgent.getModuleName()); + + } + + /** + * Test getInstance is same as mock + */ + @Test + public void testGetInstanceIsSameAsMock() { + assertSame(fieldAgent, FieldAgent.getInstance()); + } + + /** + * Test postTracking with valid jsonObject + */ + @Test ( timeout = 5000L ) + public void testPostTrackingWithValidJsonObject() { + try { + initiateMockStart(); + fieldAgent.postTracking(jsonObject); + Mockito.verify(orchestrator).request(eq("tracking"), eq(RequestType.POST), eq(null), eq(jsonObject)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test postTracking with null jsonObject + */ + @Test ( timeout = 5000L ) + public void postTrackingLogsErrorWhenRequestFails() { + try { + initiateMockStart(); + when(orchestrator.request(any(), any(), any(), any())).thenThrow(PowerMockito.mock(Exception.class)); + fieldAgent.postTracking(null); + Mockito.verify(orchestrator).request(eq("tracking"), eq(RequestType.POST), eq(null), eq(null)); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable send tracking logs"), any()); + } catch (Exception e) { + fail("this should never happen"); + } + } + + /** + * Test provision when controller status is not provisioned + */ + @Test + public void testProvisionWhenControllerStatusIsNotProvisioned() { + try { + initiateMockStart(); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + assertEquals("success", response.getString("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processMicroserviceConfig", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processRoutes", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("notifyModules"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + } catch (Exception e) { + fail("this should never happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates is null + */ + @Test + public void testProvisionWhenWhenPostFogConfigGPSCoordinatesNull() { + try { + when(Configuration.getGpsCoordinates()).thenReturn(null); + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + assertEquals("success", response.getString("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, Mockito.atLeastOnce()).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processMicroserviceConfig", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processRoutes", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, Mockito.times(2)).invoke("notifyModules"); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getGpsCoordinates(); + } catch (Exception e) { + fail("this should never happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates are invalid + */ + @Test + public void testProvisionWhenPostFogConfigGPSCoordinatesAreInvalid() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(Configuration.getGpsCoordinates()).thenReturn(""); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + assertEquals("success", response.getString("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processMicroserviceConfig", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processRoutes", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, Mockito.times(2)).invoke("notifyModules"); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getGpsCoordinates(); + } catch (Exception e) { + fail("this should never happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates are valid + * but orchestrator.request for config command returns certificate exception + */ + @Test + public void throwsCertificationExceptionWhenOrchestratorRequestWithConfigIsCalledInsideProvision() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("config"), any(), any(), any())). + thenThrow(new CertificateException("Invalid certificate")); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + assertEquals("success", response.getString("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processMicroserviceConfig", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processRoutes", any()); + PowerMockito.verifyPrivate(fieldAgent, times(2)).invoke("notifyModules"); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getGpsCoordinates(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to post ioFog config due to broken certificate "), any()); + } catch (AgentSystemException e) { + fail("this should never happen"); + } catch (Exception e) { + fail("this should never happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null and valid + */ + @Test + public void testProvisionWhenPostFogConfigGPSCoordinatesAreValid() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + assertEquals("success", response.getString("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("processMicroserviceConfig", any()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("processRoutes", any()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadEdgeResources", anyBoolean()); + + PowerMockito.verifyPrivate(fieldAgent, Mockito.times(2)).invoke("notifyModules"); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getGpsCoordinates(); + } catch (Exception e) { + fail("this should never happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & IoFogController returns valid registries + * And has no microservices + */ + @Test + public void testProvisionWhenControllerStatusIsProvisionedAndOrchestratorReturnsValidRegistries() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + JsonObject registryObject = jsonObjectBuilder + .add("id", 1) + .add("url", "http://url") + .add("username", "username") + .add("password", "password") + .add("userEmail", "userEmail") + .build(); + JsonArray jsonArray = Json.createArrayBuilder().add(registryObject).build(); + jsonObject = jsonObjectBuilder + .add("registries", jsonArray) + .add("uuid", "uuid") + .add("token", "token").build(); + when(orchestrator.request(eq("registries"), any(), any(), any())).thenReturn(jsonObject); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + assertEquals("success", response.getString("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processMicroserviceConfig", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processRoutes", any()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, Mockito.times(2)).invoke("notifyModules"); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getGpsCoordinates(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & + * IoFogController returns Invalid registries + * And has no microservices + */ + @Test + public void testProvisionWhenControllerStatusIsProvisionedAndOrchestratorReturnsInValidRegistries() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + JsonObject registryObject = jsonObjectBuilder + .add("id", 1) + .build(); + JsonArray jsonArray = Json.createArrayBuilder().add(registryObject).build(); + jsonObject = jsonObjectBuilder + .add("registries", jsonArray) + .add("uuid", "uuid") + .add("token", "token").build(); + when(orchestrator.request(eq("registries"), any(), any(), any())).thenReturn(jsonObject); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + assertEquals("success", response.getString("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processMicroserviceConfig", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processRoutes", any()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, times(2)).invoke("notifyModules"); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getGpsCoordinates(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get registries"), any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & IoFogController returns valid registries + * And invalid microservices + */ + @Test + public void testProvisionWhenControllerStatusIsProvisionedAndOrchestratorReturnsValidRegistriesAndInvalidMicroservices() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + JsonObject registryObject = jsonObjectBuilder + .add("id", 1) + .add("url", "http://url") + .add("username", "username") + .add("password", "password") + .add("userEmail", "userEmail") + .build(); + JsonArray jsonRegisteryArray = Json.createArrayBuilder().add(registryObject).build(); + JsonArray jsonMicorserviceArray = Json.createArrayBuilder().add(registryObject).build(); + jsonObject = jsonObjectBuilder + .add("registries", jsonRegisteryArray) + .add("microservices", jsonMicorserviceArray) + .add("uuid", "uuid") + .add("token", "token").build(); + when(orchestrator.request(any(), any(), any(), any())).thenReturn(jsonObject); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + assertEquals("success", response.getString("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processMicroserviceConfig", any()); + PowerMockito.verifyPrivate(fieldAgent).invoke("processRoutes", any()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, times(2)).invoke("notifyModules"); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getGpsCoordinates(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to parse microservices"),any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & IoFogController returns valid registries + * And valid microservices + */ + @Test + public void testProvisionWhenControllerStatusIsProvisionedAndOrchestratorReturnsValidRegistriesAndValidMicroservices() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + JsonObject registryObject = jsonObjectBuilder + .add("id", 1) + .add("url", "http://url") + .add("username", "username") + .add("password", "password") + .add("userEmail", "userEmail") + .build(); + JsonObject portMappingsObject = jsonObjectBuilder + .add("portExternal", 9090) + .add("portInternal", 8002) + .build(); + JsonObject volumeMappingsObject = jsonObjectBuilder + .add("hostDestination", "hostDestination") + .add("containerDestination", "containerDestination") + .add("accessMode", "accessMode") + .build(); + JsonObject envObject = jsonObjectBuilder + .add("key", "key") + .add("value", "value") + .build(); + JsonArray jsonRegisteryArray = Json.createArrayBuilder().add(registryObject).build(); + JsonArray routesJson = Json.createArrayBuilder().add("route").build(); + JsonArray portMappingsJson = Json.createArrayBuilder().add(portMappingsObject).build(); + JsonArray volumeMappingsJson = Json.createArrayBuilder().add(volumeMappingsObject).build(); + JsonArray cmdJson = Json.createArrayBuilder().add("cmd").add("sh").build(); + JsonArray envJson = Json.createArrayBuilder().add(envObject).build(); + JsonObject microserviceObject = jsonObjectBuilder + .add("uuid", "uuid") + .add("imageId", "imageId") + .add("config", "config") + .add("rebuild", false) + .add("delete", false) + .add("rootHostAccess", false) + .add("deleteWithCleanup", false) + .add("registryId", 1) + .add("logSize", 2123l) + .add("routes", routesJson) + .add("portMappings", portMappingsJson) + .add("volumeMappings", volumeMappingsJson) + .add("env", envJson) + .add("cmd", cmdJson) + .build(); + JsonArray jsonMicorserviceArray = Json.createArrayBuilder().add(microserviceObject).build(); + jsonObject = jsonObjectBuilder + .add("registries", jsonRegisteryArray) + .add("microservices", jsonMicorserviceArray) + .add("uuid", "uuid") + .add("token", "token").build(); + when(orchestrator.request(any(), any(), any(), any())).thenReturn(jsonObject); + when(orchestrator.provision(anyString())).thenReturn(provisionJsonObject); + JsonObject response = fieldAgent.provision("provisonKey"); + assertTrue(response.containsKey("message")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyStatic(ProcessManager.class, Mockito.atLeastOnce()); + ProcessManager.getInstance(); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getGpsCoordinates(); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & + * Orchestrator.provision throws AgentSystemException + */ + @Test + public void throwsExceptionWhenOrchestratorProvisionIsCalled() { + try { + initiateMockStart(); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + when(orchestrator.provision(any())).thenThrow(new AgentSystemException("IofogController provisioning failed")); + JsonObject provisioningResult = fieldAgent.provision("provisonKey"); + assertTrue(provisioningResult.containsKey("status")); + assertTrue(provisioningResult.containsKey("errorMessage")); + assertEquals("failed", provisioningResult.getString("status")); + assertEquals("IofogController provisioning failed", provisioningResult.getString("errorMessage")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + Mockito.verify(fieldAgentStatus, atLeastOnce()).getControllerStatus(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Provisioning failed"), any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & + * Orchestrator.provision returns success response & + * loadMicroservices : Orchestrator.request with command microservice throws CertificateException + */ + @Test + public void throwsExceptionWhenOrchestratorRequestIsCalled() { + try { + initiateMockStart(); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.provision(any())).thenReturn(jsonObject); + when(orchestrator.request(eq("microservices"), any(), any(), any())).thenThrow(new CertificateException("Certificate Error")); + fieldAgent.provision("provisonKey"); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to post ioFog config "), any()); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logWarning(MODULE_NAME,"controller verification failed: BROKEN_CERTIFICATE"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get microservices due to broken certificate"), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logError(eq(MODULE_NAME), eq("Provisioning failed"), any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & + * Orchestrator.provision returns success response & + * loadRegistries : Orchestrator.request with command registries throws AgentUserException + */ + @Test + public void throwsAgentUserExceptionWhenLoadRegistriestIsCalledInProvision() { + try { + initiateMockStart(); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + when(StatusReporter.getFieldAgentStatus()).thenReturn(fieldAgentStatus); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.provision(any())).thenReturn(jsonObject); + when(orchestrator.request(eq("registries"), any(), any(), any())).thenThrow(new AgentUserException("Agent user error")); + fieldAgent.provision("provisonKey"); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get registries"), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logError(eq(MODULE_NAME), eq("Provisioning failed"), any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & + * Orchestrator.provision returns success response & + * sendHWInfoFromHalToController method throws exception + */ + @Test + public void throwsExceptionWhenSendHWInfoFromHalToControllerIsCalledInProvision() { + try { + initiateMockStart(); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + when(StatusReporter.getFieldAgentStatus()).thenReturn(fieldAgentStatus); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.provision(any())).thenReturn(jsonObject); + when(orchestrator.request(any(), any(), any(), any())).thenReturn(mock(JsonObject.class)); + PowerMockito.whenNew(BufferedReader.class).withArguments(inputStreamReader).thenThrow(new Exception("invalid operation")); + JsonObject provisioningResult = fieldAgent.provision("provisonKey"); + assertTrue(provisioningResult.containsKey("status")); + assertTrue(provisioningResult.containsKey("errorMessage")); + assertEquals("failed", provisioningResult.getString("status")); + assertEquals("invalid operation", provisioningResult.getString("errorMessage")); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Provisioning failed"), any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test deprovision with isTokenExpired true and Controller status is not provisioned + */ + @Test + public void testDeProvisionFailureWithExpiredToken() { + try { + initiateMockStart(); + String response = fieldAgent.deProvision(true); + assertTrue(response.equals("\nFailure - not provisioned")); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + Mockito.verify(orchestrator, never()).request(eq("deprovision"), eq(RequestType.POST), eq(null), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished Deprovisioning : Failure - not provisioned"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test deprovision with isTokenExpired true and Controller status is provisioned + */ + @Test + public void testDeProvisionSuccessWithExpiredToken() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + String response = fieldAgent.deProvision(true); + assertTrue(response.equals("\nSuccess - tokens, identifiers and keys removed")); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + Mockito.verify(orchestrator, never()).request(eq("deprovision"), eq(RequestType.POST), eq(null), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Start Deprovisioning"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished Deprovisioning : Success - tokens, identifiers and keys removed"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test deprovision with isTokenExpired false and Controller status is provisioned + */ + @Test + public void testDeProvisionSuccessWithNotExpiredToken() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("deprovision"), any(), any(), any())).thenReturn(mock(JsonObject.class)); + String response = fieldAgent.deProvision(false); + assertTrue(response.equals("\nSuccess - tokens, identifiers and keys removed")); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + Mockito.verify(orchestrator).request(eq("deprovision"), eq(RequestType.POST), eq(null), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished Deprovisioning : Success - tokens, identifiers and keys removed"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test deprovision with isTokenExpired false and Controller status is provisioned + * Orchestrator.request throws Exception + */ + @Test + public void throwsExceptionWhenOrchestratorRequestIsCalledForDeProvisioning() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("deprovision"), any(), any(), any())).thenThrow(new Exception("Error while deProvsioning")); + String response = fieldAgent.deProvision(false); + assertTrue(response.equals("\nSuccess - tokens, identifiers and keys removed")); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + Mockito.verify(orchestrator).request(eq("deprovision"), eq(RequestType.POST), eq(null), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME),eq("Unable to make deprovision request "), any()); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test deprovision with isTokenExpired false and Controller status is provisioned + * Error saving config updates + */ + @Test + public void throwsExceptionWhenUpdatingConfigForDeProvisioning() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("deprovision"), any(), any(), any())).thenThrow(new SSLHandshakeException("Invalid operation")); + String response = fieldAgent.deProvision(false); + assertTrue(response.equals("\nSuccess - tokens, identifiers and keys removed")); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + Mockito.verify(orchestrator).request(eq("deprovision"), eq(RequestType.POST), eq(null), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME),eq("Unable to make deprovision request due to broken certificate "), any()); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test deprovision with isTokenExpired false and Controller status is provisioned + * Orchestrator.request throws SSLHandshakeException + */ + @Test + public void throwsSSLHandshakeExceptionWhenOrchestratorRequestIsCalledForDeProvisioning() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("deprovision"), any(), any(), any())).thenReturn(mock(JsonObject.class)); + PowerMockito.doThrow(new Exception("Error Updating config")).when(Configuration.class); + Configuration.saveConfigUpdates(); + String response = fieldAgent.deProvision(false); + assertTrue(response.equals("\nSuccess - tokens, identifiers and keys removed")); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + Mockito.verify(orchestrator).request(eq("deprovision"), eq(RequestType.POST), eq(null), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME),eq("Error saving config updates"), any()); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test instanceConfigUpdated when controller status is not provisioned + */ + @Test + public void testInstanceConfigUpdatedWhenControllerStatusIsNotProvisioned() { + try { + initiateMockStart(); + when(orchestrator.request(eq("config"), any(), any(), any())).thenReturn(mock(JsonObject.class)); + fieldAgent.instanceConfigUpdated(); + Mockito.verify(orchestrator).update(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("postFogConfig"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Post ioFog config"); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.logInfo(MODULE_NAME, "Finished Post ioFog config"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test instanceConfigUpdated when controller status is provisioned + */ + @Test + public void testInstanceConfigUpdatedWhenControllerStatusIsProvisioned() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("config"), any(), any(), any())).thenReturn(mock(JsonObject.class)); + fieldAgent.instanceConfigUpdated(); + Mockito.verify(orchestrator).update(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("postFogConfig"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Post ioFog config"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished Post ioFog config"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test instanceConfigUpdated when controller status is provisioned + * Orchestrator.request throws SSLHandshakeException + */ + @Test + public void throwsSSLHandshakeExceptionWhenOrchestratorIsCalledToUpdateConfiguration() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("config"), any(), any(), any())).thenThrow(new SSLHandshakeException("Invalid operation")); + fieldAgent.instanceConfigUpdated(); + Mockito.verify(orchestrator).update(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("postFogConfig"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService. logError(eq(MODULE_NAME), eq("Unable to post ioFog config due to broken certificate "), any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished IOFog configuration update"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test instanceConfigUpdated when controller status is provisioned + * Orchestrator.request for config throws Exception + */ + @Test + public void throwsExceptionWhenOrchestratorIsCalledToUpdateConfiguration() { + try { + initiateMockStart(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("config"), any(), any(), any())).thenThrow(new Exception("Invalid operation")); + fieldAgent.instanceConfigUpdated(); + Mockito.verify(orchestrator).update(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("postFogConfig"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService. logError(eq(MODULE_NAME), eq("Unable to post ioFog config "), any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished IOFog configuration update"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Controller status is not provisioned + */ + @Test (timeout = 10000l) + public void testStartWhenControllerStatusIsNotProvisioned() { + try { + initiateMockStart(); + PowerMockito.verifyPrivate(fieldAgent).invoke("ping"); + PowerMockito.verifyPrivate(fieldAgent).invoke("getFogConfig"); + PowerMockito.verifyPrivate(fieldAgent,never()).invoke("isControllerConnected", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, never()).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + PowerMockito.verifyPrivate(fieldAgent, never()).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Start the Field Agent"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Field Agent started"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test start getFogStatus throws Exception + * Controller status is provisioned + * Controller ping is false + * Controller is verified + * Controller connection is broken + */ + @Test + public void testStartWhenControllerConnectionIsBroken() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK, Constants.ControllerStatus.OK, + Constants.ControllerStatus.NOT_PROVISIONED, Constants.ControllerStatus.OK); + when(orchestrator.ping()).thenReturn(false); + when(fieldAgentStatus.isControllerVerified()).thenReturn(true); + initiateMockStart(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("ping"); + PowerMockito.verifyPrivate(fieldAgent).invoke("getFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("isControllerConnected", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, never()).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished Ping : " + false); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logWarning(MODULE_NAME, "Connection to controller has broken"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished handling Bad Controller Status"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME,"Started checking provisioned"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test start getFogStatus throws Exception + * Controller status is provisioned + * Controller ping is false + * Controller is not verified + * Controller connection is broken + */ + @Test + public void testStartWhenControllerConnectionIsBrokenAndNotVerified() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK, Constants.ControllerStatus.OK, + Constants.ControllerStatus.NOT_PROVISIONED, Constants.ControllerStatus.OK); + when(orchestrator.ping()).thenReturn(false); + when(fieldAgentStatus.isControllerVerified()).thenReturn(false); + initiateMockStart(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("ping"); + PowerMockito.verifyPrivate(fieldAgent).invoke("getFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("isControllerConnected", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, never()).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadRegistries", anyBoolean()); + LoggingService.logInfo(MODULE_NAME, "Started Ping"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished Ping : " + false); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logWarning(MODULE_NAME, "controller verification failed: NOT_CONNECTED"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test start getFogStatus throws Exception + * Controller status is provisioned + * Controller ping is true + * Controller is not verified + * Controller connection is good + */ + @Test + public void testStartWhenControllerIsConnectedAndStatusIsProvisioned() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.ping()).thenReturn(true); + initiateMockStart(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("ping"); + PowerMockito.verifyPrivate(fieldAgent).invoke("getFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("isControllerConnected", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadRegistries", anyBoolean()); + LoggingService.logInfo(MODULE_NAME, "Finished Ping : " + true); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "checked is Controller Connected : true "); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.logDebug(MODULE_NAME, "Finished handling Bad Controller Status"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Field Agent started"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test start getFogStatus throws Exception + * Controller status is provisioned + * Controller ping is true + * Controller is not verified + * Controller connection is good + * initialization is false + */ + @Test + public void testStartWhenControllerIsConnectedAndStatusIsProvisionedInitializationIsFalse() { + try { + Field instance = FieldAgent.class.getDeclaredField("initialization"); + instance.setAccessible(true); + instance.set(fieldAgent, false); + JsonObject configObject = jsonObjectBuilder + .add("networkInterface", "interface") + .add("dockerUrl", "http://url") + .add("diskLimit", 40) + .build(); + when(orchestrator.request(eq("config"), any(), any(), any())).thenReturn(configObject); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.ping()).thenReturn(true); + initiateMockStart(); + Mockito.verify(orchestrator).ping(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("ping"); + PowerMockito.verifyPrivate(fieldAgent).invoke("getFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("isControllerConnected", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent, never()).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished Ping : " + true); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "checked is Controller Connected : true "); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Field Agent started"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test getFogStatus + */ + @Test + public void testGetFogStatus() { + try { + method = FieldAgent.class.getDeclaredMethod("getFogStatus"); + method.setAccessible(true); + JsonObject output = (JsonObject) method.invoke(fieldAgent); + assertTrue(output.containsKey("daemonStatus")); + assertTrue(output.getString("ipAddress").equals("ip")); + PowerMockito.verifyPrivate(fieldAgent).invoke("getFogStatus"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "get Fog Status"); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test deleteNode when controller status is not provisioned + * Orchestrator returns success + */ + @Test + public void testDeleteNodeSuccessWhenControllerStatusIsNotProvisioned() { + try { + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("deleteNode"); + method.setAccessible(true); + method.invoke(fieldAgent); + Mockito.verify(orchestrator).request(eq("delete-node"), eq(RequestType.DELETE), eq(null), eq(null)); + Mockito.verify(fieldAgent).deProvision(eq(false)); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "start deleting current fog node from controller and make it deprovision"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished Deprovisioning : Failure - not provisioned"); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test deleteNode when controller status is provisioned + * Orchestrator returns success + */ + @Test + public void testDeleteNodeSuccessWhenControllerStatusIsProvisioned() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("deleteNode"); + method.setAccessible(true); + method.invoke(fieldAgent); + Mockito.verify(orchestrator).request(eq("delete-node"), eq(RequestType.DELETE), eq(null), eq(null)); + Mockito.verify(fieldAgent).deProvision(eq(false)); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished Deprovisioning : Success - tokens, identifiers and keys removed"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finish deleting current fog node from controller and make it deprovision"); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test deleteNode when controller status is provisioned + * Orchestrator delete request throws exception + * Orchestrator deprovision request returns success + */ + @Test + public void throwsExceptionWhenDeleteNode() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("delete-node"), any(), any(), any())).thenThrow(mock(Exception.class)); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("deleteNode"); + method.setAccessible(true); + method.invoke(fieldAgent); + Mockito.verify(orchestrator).request(eq("delete-node"), eq(RequestType.DELETE), eq(null), eq(null)); + Mockito.verify(fieldAgent).deProvision(eq(false)); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("notProvisioned"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("deprovision"), eq(RequestType.POST), eq(null), any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Can't send delete node command"), any()); + PowerMockito.verifyStatic(Configuration.class, atLeastOnce()); + Configuration.setIofogUuid(anyString()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test reboot success + */ + @Test + public void testRebootSuccess() { + try { + List error = new ArrayList<>(); + List value = new ArrayList<>(); + value.add("success"); + CommandShellResultSet, List> resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("reboot"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent).invoke("reboot"); + PowerMockito.verifyStatic(CommandShellExecutor.class, atLeastOnce()); + CommandShellExecutor.executeCommand(any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "start Remote reboot of Linux machine from IOFog controller"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished Remote reboot of Linux machine from IOFog controller"); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test reboot Failure When CommandShellExecutor returns errors + */ + @Test + public void testRebootFailureWhenCommandShellExecutorReturnsError() { + try { + List error = new ArrayList<>(); + List value = new ArrayList<>(); + error.add("Error Rebooting"); + CommandShellResultSet, List> resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("reboot"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent).invoke("reboot"); + PowerMockito.verifyStatic(CommandShellExecutor.class, atLeastOnce()); + CommandShellExecutor.executeCommand(any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logWarning(eq(MODULE_NAME),eq(resultSetWithPath.toString())); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test reboot Failure When CommandShellExecutor returns null + */ + @Test + public void testRebootFailureWhenCommandShellExecutorReturnsNull() { + try { + when(CommandShellExecutor.executeCommand(any())).thenReturn(null); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("reboot"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent).invoke("reboot"); + PowerMockito.verifyStatic(CommandShellExecutor.class, atLeastOnce()); + CommandShellExecutor.executeCommand(any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Error in Remote reboot of Linux machine from IOFog controller"), any()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test changeVersion success + */ + @Test + public void testChangeVersionSuccess() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("changeVersion"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent).invoke("changeVersion"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("version"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyStatic(VersionHandler.class, atLeastOnce()); + VersionHandler.changeVersion(any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Starting change version action"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished change version operation, received from ioFog controller"); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test changeVersion + * Orchestrator request throws CertificateException + */ + @Test + public void throwsCertificateExceptionWhenCalledForChangeVersion() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("version"), any(), any(), any())).thenThrow(mock(CertificateException.class)); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("changeVersion"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent).invoke("changeVersion"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("version"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyPrivate(fieldAgent).invoke("verificationFailed", any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logWarning(MODULE_NAME, "controller verification failed: BROKEN_CERTIFICATE"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get version command due to broken certificate"), any()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test changeVersion + * Orchestrator request throws Exception + */ + @Test + public void throwsExceptionWhenCalledForChangeVersion() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("version"), any(), any(), any())).thenThrow(mock(Exception.class)); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("changeVersion"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent).invoke("changeVersion"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("version"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyPrivate(fieldAgent, never()).invoke("verificationFailed", any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get version command"), any()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test updateDiagnostics success + */ + @Test + public void testUpdateDiagnostics() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("updateDiagnostics"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent, Mockito.atLeastOnce()).invoke("updateDiagnostics"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("strace"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Start update diagnostics"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished update diagnostics"); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test updateDiagnostics + * Orchestrator request throws CertificateException + */ + @Test + public void throwsCertificateExceptionWhenUpdateDiagnostics() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("strace"), any(), any(), any())).thenThrow(mock(CertificateException.class)); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("updateDiagnostics"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent).invoke("updateDiagnostics"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("strace"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyPrivate(fieldAgent).invoke("verificationFailed", any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logWarning(MODULE_NAME, "controller verification failed: BROKEN_CERTIFICATE"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get diagnostics update due to broken certificate"), any()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test updateDiagnostics + * Orchestrator request throws Exception + */ + @Test + public void throwsExceptionWhenUpdateDiagnostics() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("strace"), any(), any(), any())).thenThrow(mock(Exception.class)); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("updateDiagnostics"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent).invoke("updateDiagnostics"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("strace"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyPrivate(fieldAgent, never()).invoke("verificationFailed", any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get diagnostics update"), any()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test getProxyConfig + * Orchestrator request throws Exception + */ + @Test + public void throwsExceptionWhenGetProxyConfigIsCalled() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("tunnel"), any(), any(), any())).thenThrow(mock(Exception.class)); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("getProxyConfig"); + method.setAccessible(true); + method.invoke(fieldAgent); + PowerMockito.verifyPrivate(fieldAgent, Mockito.atLeastOnce()).invoke("getProxyConfig"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("tunnel"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get proxy config "), any()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test getProxyConfig + * Orchestrator request returns success + */ + @Test + public void testGetProxyConfigsuccess() { + try { + JsonObject dummyObject =jsonObjectBuilder + .add("uuid", "response proxy") + .build(); + JsonObject proxyObject = jsonObjectBuilder + .add("tunnel", dummyObject) + .build(); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq("tunnel"), any(), any(), any())).thenReturn(proxyObject); + initiateMockStart(); + method = FieldAgent.class.getDeclaredMethod("getProxyConfig"); + method.setAccessible(true); + JsonObject response = (JsonObject) method.invoke(fieldAgent); + assertTrue(response.containsKey("uuid")); + assertEquals("response proxy", response.getString("uuid")); + PowerMockito.verifyPrivate(fieldAgent, Mockito.atLeastOnce()).invoke("getProxyConfig"); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("tunnel"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get proxy config "), any()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test sendUSBInfoFromHalToController + * Orchestrator request returns success + */ + @Test + public void testSendUSBInfoFromHalToController() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq(COMMAND_USB_INFO), any(), any(), any())).thenReturn(jsonObject); + initiateMockStart(); + fieldAgent.sendUSBInfoFromHalToController(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("getResponse", any()); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("config"), eq(RequestType.PATCH), eq(null), any()); + /*PowerMockito.verifyStatic(StatusReporter.class, atLeastOnce()); + StatusReporter.setResourceManagerStatus();*/ + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Start send USB Info from hal To Controller"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished send USB Info from hal To Controller"); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Test sendUSBInfoFromHalToController + * Orchestrator request throws Exception + */ + @Test + public void throwsExceptionWhenSendUSBInfoFromHalToController() { + try { + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.request(eq(COMMAND_USB_INFO), any(), any(), any())).thenThrow(mock(Exception.class)); + initiateMockStart(); + fieldAgent.sendUSBInfoFromHalToController(); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("getResponse", any()); + Mockito.verify(orchestrator, atLeastOnce()).request(eq("config"), eq(RequestType.PATCH), eq(null), any()); + PowerMockito.verifyStatic(StatusReporter.class, atLeastOnce()); + StatusReporter.setResourceManagerStatus(); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Error while sending USBInfo from hal to controller"), any()); + } catch (Exception e){ + fail("This should never happen"); + } + } + + /** + * Helper method for mocking Configuration + */ + public void mockConfiguration() { + when(Configuration.getIofogUuid()).thenReturn("uuid"); + when(Configuration.getAccessToken()).thenReturn("token"); + when(Configuration.getControllerUrl()).thenReturn("http://controllerurl"); + when(Configuration.getNetworkInterface()).thenReturn("dynamic"); + when(Configuration.getDockerUrl()).thenReturn("getDockerUrl"); + when(Configuration.getDiskLimit()).thenReturn(10f); + when(Configuration.getDiskDirectory()).thenReturn("uuid"); + when(Configuration.getMemoryLimit()).thenReturn(4096f); + when(Configuration.getCpuLimit()).thenReturn(80f); + when(Configuration.getLogDiskLimit()).thenReturn(10f); + when(Configuration.getLogDiskDirectory()).thenReturn("/var/log/iofog-agent/"); + when(Configuration.getLogLevel()).thenReturn("info"); + when(Configuration.getLogFileCount()).thenReturn(10); + when(Configuration.getStatusFrequency()).thenReturn(10); + when(Configuration.getChangeFrequency()).thenReturn(20); + when(Configuration.getDeviceScanFrequency()).thenReturn(60); + when(Configuration.isWatchdogEnabled()).thenReturn(false); + when(Configuration.getReadyToUpgradeScanFrequency()).thenReturn(24); + when(Configuration.getGpsCoordinates()).thenReturn("4000.0, 2000.3"); + when(Configuration.getGetUsageDataFreqSeconds()).thenReturn(1l); + } + + /** + * Helper method for mocking Status classes + */ + public void mockOthers() { + SupervisorStatus supervisorStatus = mock(SupervisorStatus.class); + ProcessManagerStatus processManagerStatus = mock(ProcessManagerStatus.class); + StatusReporterStatus statusReporterStatus = mock(StatusReporterStatus.class); + MessageBusStatus messageBusStatus = mock(MessageBusStatus.class); + SshProxyManagerStatus sshProxyManagerStatus = mock(SshProxyManagerStatus.class); + IOFogNetworkInterfaceManager ioFogNetworkInterfaceManager = mock(IOFogNetworkInterfaceManager.class); + ResourceConsumptionManagerStatus resourceConsumptionManagerStatus = mock(ResourceConsumptionManagerStatus.class); + when(StatusReporter.getSupervisorStatus()).thenReturn(supervisorStatus); + when(StatusReporter.getProcessManagerStatus()).thenReturn(processManagerStatus); + when(processManagerStatus.getRunningMicroservicesCount()).thenReturn(2); + when(StatusReporter.getStatusReporterStatus()).thenReturn(statusReporterStatus); + when(StatusReporter.getMessageBusStatus()).thenReturn(messageBusStatus); + when(StatusReporter.getSshManagerStatus()).thenReturn(sshProxyManagerStatus); + when(StatusReporter.getResourceConsumptionManagerStatus()).thenReturn(resourceConsumptionManagerStatus); + when(IOFogNetworkInterfaceManager.getInstance()).thenReturn(ioFogNetworkInterfaceManager); + when(ioFogNetworkInterfaceManager.getCurrentIpAddress()).thenReturn("ip"); + when(supervisorStatus.getDaemonStatus()).thenReturn(Constants.ModulesStatus.RUNNING); + ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class); + ScheduledFuture future = mock(ScheduledFuture.class); + PowerMockito.doReturn(future).when(scheduler).scheduleAtFixedRate(any(Runnable.class), Mockito.anyLong(), Mockito.anyLong(), any(TimeUnit.class)); + mock(Runnable.class); + + } + + /** + * Test provision when controller status is provisioned & Gps coordinates not null & + * Orchestrator.provision returns success response & + * loadEdgeResources : Orchestrator.request with command microservice throws CertificateException + */ + @Test + public void throwsExceptionWhenOrchestratorEdgeRequestIsCalled() { + try { + initiateMockStart(); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.provision(any())).thenReturn(jsonObject); + when(orchestrator.request(eq("edgeResources"), any(), any(), any())).thenThrow(new CertificateException("Certificate Error")); + fieldAgent.provision("provisonKey"); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to post ioFog config "), any()); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logWarning(MODULE_NAME,"controller verification failed: BROKEN_CERTIFICATE"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to get edgeResources due to broken certificate"), any()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logError(eq(MODULE_NAME), eq("Provisioning failed"), any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test provision when controller status is provisioned & Gps coordinates not null & + * Orchestrator.provision returns success response & + * loadEdgeResources : returns list of edgeResources + */ + @Test + public void testProvisionWhenLoadEdgeResourcesReturnList() { + try { + initiateMockStart(); + JsonObject edgeObject = jsonObjectBuilder + .add("id", 1) + .add("name", "com.orange.smart-door") + .add("description", "Orange Smart Door") + .add("version", "0.0.1") + .add("interfaceProtocol", "https") + .add("interface", "") + .build(); + JsonArray jsonArray = Json.createArrayBuilder().add(edgeObject).build(); + JsonObject edgeResources = jsonObjectBuilder + .add("edgeResources", jsonArray).build(); + when(Configuration.getGpsCoordinates()).thenReturn("40.9006, 174.8860"); + when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + when(orchestrator.provision(any())).thenReturn(jsonObject); + when(orchestrator.request(eq("edgeResources"), any(), any(), any())).thenReturn(edgeResources); + fieldAgent.provision("provisonKey"); + Mockito.verify(orchestrator).provision(eq("provisonKey")); + PowerMockito.verifyPrivate(fieldAgent).invoke("postFogConfig"); + PowerMockito.verifyPrivate(fieldAgent).invoke("sendHWInfoFromHalToController"); + PowerMockito.verifyPrivate(fieldAgent, atLeastOnce()).invoke("loadRegistries", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadMicroservices", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("loadEdgeResources", anyBoolean()); + PowerMockito.verifyPrivate(fieldAgent).invoke("saveFile", any(), eq("/etc/iofog-agent/edge_resources.json")); + Mockito.verify(orchestrator).request(eq("edgeResources"), eq(RequestType.GET), eq(null), eq(null)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(eq(MODULE_NAME), eq("Finished loading edge resources...")); + } catch (AgentSystemException e) { + fail("This should not happen"); + } catch (Exception e) { + fail("This should not happen"); + } + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/VersionHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/VersionHandlerTest.java new file mode 100644 index 00000000..5bf402a6 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/VersionHandlerTest.java @@ -0,0 +1,335 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.field_agent; + +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.field_agent.enums.VersionCommand; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({VersionHandler.class, LoggingService.class, File.class, Runtime.class, + CommandShellExecutor.class}) +public class VersionHandlerTest { + private VersionHandler versionHandler; + private JsonObject jsonObject; + private JsonObjectBuilder jsonObjectBuilder = null; + private String MODULE_NAME; + private File file; + private List error; + private List value; + private CommandShellResultSet, List> resultSetWithPath = null; + private String[] fileList = {"file1", "file2"}; + private Runtime runtime; + @Before + public void setUp() throws Exception { + MODULE_NAME = "Version Handler"; + versionHandler = spy(new VersionHandler()); + mockStatic(LoggingService.class); + mockStatic(Runtime.class); + mockStatic(CommandShellExecutor.class); + file = mock(File.class); + runtime = mock(Runtime.class); + whenNew(File.class).withParameterTypes(String.class).withArguments(any()).thenReturn(file); + when(file.list()).thenReturn(fileList); + jsonObjectBuilder = Json.createObjectBuilder(); + error = new ArrayList<>(); + value = new ArrayList<>(); + when(Runtime.getRuntime()).thenReturn(runtime); + } + + @After + public void tearDown() throws Exception { + MODULE_NAME = null; + jsonObject = null; + error = null; + value = null; + fileList = null; + } + + /** + * Test changeVersion when actionData is null + */ + @Test + public void testChangeVersionCommandWhenNull() { + VersionHandler.changeVersion(null); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Start performing change version operation, received from ioFog controller"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Error performing change version operation : Invalid command"), any()); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished performing change version operation, received from ioFog controller"); + } + + /** + * Test changeVersion when versionCommand is invalid + */ + @Test + public void testChangeVersionCommandWhenNotNull() { + jsonObject = jsonObjectBuilder + .add("versionCommand", "versionCommand") + .add("provisionKey", "provisionKey").build(); + VersionHandler.changeVersion(jsonObject); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Start performing change version operation, received from ioFog controller"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Error performing change version operation : Invalid command"), any()); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished performing change version operation, received from ioFog controller"); + } + + /** + * Test changeVersion when versionCommand is VersionCommand.ROLLBACK + * And is not ready to rollback + */ + @Test + public void testChangeVersionCommandRollbackAndSystemIsNotReady() { + jsonObject = jsonObjectBuilder + .add("versionCommand", VersionCommand.ROLLBACK.toString()) + .add("provisionKey", "provisionKey").build(); + when(file.list()).thenReturn(null); + VersionHandler.changeVersion(jsonObject); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Start performing change version operation, received from ioFog controller"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Checking is ready to rollback"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Is ready to rollback : false"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished performing change version operation, received from ioFog controller"); + } + + /** + * Test changeVersion when versionCommand is VersionCommand.ROLLBACK + * And is ready to rollback + * Runtime script exec throws IOException + */ + @Test + @Ignore + public void throwsIOEXceptionWhenChangeVersionCommandRollback() { + try { + jsonObject = jsonObjectBuilder + .add("versionCommand", VersionCommand.ROLLBACK.toString()) + .add("provisionKey", "provisionKey").build(); + when(runtime.exec(anyString())).thenThrow(mock(IOException.class)); + VersionHandler.changeVersion(jsonObject); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Checking is ready to rollback"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Is ready to rollback : true"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Error executing sh script to change version"), any()); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Finished performing change version operation, received from ioFog controller"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test changeVersion when versionCommand is VersionCommand.ROLLBACK + * And is ready to rollback + * Rollback success + */ + @Test + @Ignore + public void testChangeVersionCommandRollbackSuccess() { + try { + jsonObject = jsonObjectBuilder + .add("versionCommand", VersionCommand.ROLLBACK.toString()) + .add("provisionKey", "provisionKey").build(); + when(runtime.exec(anyString())).thenReturn(mock(Process.class)); + VersionHandler.changeVersion(jsonObject); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Checking is ready to rollback"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, "Is ready to rollback : true"); + verifyStatic(LoggingService.class, never()); + LoggingService.logError(eq(MODULE_NAME), eq("Error executing sh script to change version"), any()); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test changeVersion when versionCommand is VersionCommand.UPGRADE + * And is not readyToUpgrade CommandShellExecutor returns null + */ + @Test + public void testChangeVersionCommandUpgradeWhenCommandShellExecutorReturnsNull() { + jsonObject = jsonObjectBuilder + .add("versionCommand", VersionCommand.UPGRADE.toString()) + .add("provisionKey", "provisionKey").build(); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + VersionHandler.changeVersion(jsonObject); + verifyStatic(CommandShellExecutor.class, atLeastOnce()); + CommandShellExecutor.executeCommand(any()); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Is ready to upgrade : false"); + verifyStatic(LoggingService.class, never()); + LoggingService.logDebug(MODULE_NAME, "Performing change version operation"); + } + + /** + * Test changeVersion when versionCommand is VersionCommand.UPGRADE + * And is ready to upgrade + * CommandShellExecutor returns error for lock files + * And value for fog installed version + */ + @Test + public void testChangeVersionCommandUpgradeWhenCommandShellExecutorReturnsErrorForLockedFile() { + jsonObject = jsonObjectBuilder + .add("versionCommand", VersionCommand.UPGRADE.toString()) + .add("provisionKey", "provisionKey").build(); + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + List anotherError = new ArrayList<>(); + List anotherValue = new ArrayList<>(); + anotherValue.add("1.2.2"); + CommandShellResultSet, List> anotherResultSetWithPath = new CommandShellResultSet<>(anotherValue, anotherError); + List fogError = new ArrayList<>(); + List fogValue = new ArrayList<>(); + fogValue.add("1.2.3"); + CommandShellResultSet, List> fogResultSetWithPath = new CommandShellResultSet<>(fogValue, fogError); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath, resultSetWithPath, anotherResultSetWithPath, fogResultSetWithPath); + VersionHandler.changeVersion(jsonObject); + verifyStatic(CommandShellExecutor.class, atLeastOnce()); + CommandShellExecutor.executeCommand(any()); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Checking is ready to upgrade"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Is ready to upgrade : true"); + } + + /** + * Test changeVersion when versionCommand is VersionCommand.UPGRADE + * And is not ready to upgrade CommandShellExecutor returns locked files + * And value for fog installed version + */ + @Test + public void testChangeVersionCommandUpgradeWhenCommandShellExecutorReturnsValue() { + jsonObject = jsonObjectBuilder + .add("versionCommand", VersionCommand.UPGRADE.toString()) + .add("provisionKey", "provisionKey").build(); + value.add("valueSuccess"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + VersionHandler.changeVersion(jsonObject); + verifyStatic(CommandShellExecutor.class, atLeastOnce()); + CommandShellExecutor.executeCommand(any()); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Is ready to upgrade : false"); + } + + /** + * Test isReadyToUpgrade + * When there are no locked files + * getFogInstalledVersion & getFogCandidateVersion are different + */ + @Test + public void testIsReadyToUpgradeReturnsTrue() { + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + List anotherError = new ArrayList<>(); + List anotherValue = new ArrayList<>(); + anotherValue.add("1.2.2"); + CommandShellResultSet, List> anotherResultSetWithPath = new CommandShellResultSet<>(anotherValue, anotherError); + List fogError = new ArrayList<>(); + List fogValue = new ArrayList<>(); + fogValue.add("1.2.3"); + CommandShellResultSet, List> fogResultSetWithPath = new CommandShellResultSet<>(fogValue, fogError); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath, resultSetWithPath, anotherResultSetWithPath, fogResultSetWithPath); + assertTrue(VersionHandler.isReadyToUpgrade()); + verifyStatic(CommandShellExecutor.class, atLeastOnce()); + CommandShellExecutor.executeCommand(any()); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Checking is ready to upgrade"); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Is ready to upgrade : true"); + } + + /** + * Test isReadyToUpgrade + * When there are no locked files + * getFogInstalledVersion & getFogCandidateVersion are same + */ + @Test + public void testIsReadyToUpgradeReturnsFalse() { + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + List anotherError = new ArrayList<>(); + List anotherValue = new ArrayList<>(); + anotherValue.add("1.2.2"); + CommandShellResultSet, List> anotherResultSetWithPath = new CommandShellResultSet<>(anotherValue, anotherError); + List fogError = new ArrayList<>(); + List fogValue = new ArrayList<>(); + fogValue.add("1.2.2"); + CommandShellResultSet, List> fogResultSetWithPath = new CommandShellResultSet<>(fogValue, fogError); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath, resultSetWithPath, anotherResultSetWithPath, fogResultSetWithPath); + assertFalse(VersionHandler.isReadyToUpgrade()); + verifyStatic(CommandShellExecutor.class, atLeastOnce()); + CommandShellExecutor.executeCommand(any()); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Is ready to upgrade : false"); + } + + /** + * Test isReadyToRollback when there are no backup files + */ + @Test + public void isReadyToRollbackFalse() { + when(file.list()).thenReturn(null); + assertFalse(VersionHandler.isReadyToRollback()); + Mockito.verify(file).list(); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Is ready to rollback : false"); + } + + /** + * Test isReadyToRollback when there are no backup files + */ + @Test + public void isReadyToRollbackTrue() { + assertTrue(VersionHandler.isReadyToRollback()); + Mockito.verify(file).list(); + verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Is ready to rollback : true"); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/enums/VersionCommandTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/enums/VersionCommandTest.java new file mode 100644 index 00000000..0c9fdc23 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/enums/VersionCommandTest.java @@ -0,0 +1,114 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.field_agent.enums; + +import org.eclipse.iofog.field_agent.exceptions.UnknownVersionCommandException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +import static org.junit.Assert.*; +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest(VersionCommand.class) +public class VersionCommandTest { + private String versionCommand; + private JsonObject jsonObject; + private JsonObjectBuilder jsonObjectBuilder = null; + + @Before + public void setUp() throws Exception { + jsonObjectBuilder = Json.createObjectBuilder(); + } + + @After + public void tearDown() throws Exception { + } + + /** + * Test parseCommandString with null command + */ + @Test (expected = UnknownVersionCommandException.class) + public void throwsUnknownVersionCommandExceptionWhenParseCommandStringWithNullCommand() { + VersionCommand.parseCommandString(null); + } + + /** + * Test parseCommandString with invalid command + */ + @Test (expected = UnknownVersionCommandException.class) + public void throwsUnknownVersionCommandExceptionWhenParseCommandStringWithInvalidCommand() { + VersionCommand.parseCommandString("Command"); + } + + /** + * Test parseCommandString with valid command + */ + @Test + public void testParseCommandStringWithValidCommand() { + assertEquals(VersionCommand.ROLLBACK, VersionCommand.parseCommandString(VersionCommand.ROLLBACK.toString())); + assertEquals(VersionCommand.UPGRADE, VersionCommand.parseCommandString(VersionCommand.UPGRADE.toString())); + } + + /** + * Test parseJson with Null versionData + */ + @Test (expected = UnknownVersionCommandException.class) + public void throwsUnknownVersionCommandExceptionWhenParseJsonWithNullVersionData() { + VersionCommand.parseJson(null); + } + + /** + * Test parseJson with invalid versionData + */ + @Test (expected = UnknownVersionCommandException.class) + public void throwsUnknownVersionCommandExceptionWhenParseJsonWithInvalidVersionData() { + jsonObject = jsonObjectBuilder + .add("versionCommandDummy", "versionCommand").build(); + VersionCommand.parseJson(jsonObject); + jsonObject = jsonObjectBuilder + .add("versionCommand", "versionCommand").build(); + VersionCommand.parseJson(jsonObject); + } + + /** + * Test parseJson with valid versionData + */ + @Test + public void testParseJsonWithValidVersionData() { + jsonObject = jsonObjectBuilder + .add("versionCommand", VersionCommand.ROLLBACK.toString()).build(); + assertEquals(VersionCommand.ROLLBACK, VersionCommand.parseJson(jsonObject)); + jsonObject = jsonObjectBuilder + .add("versionCommand", VersionCommand.UPGRADE.toString()).build(); + assertEquals(VersionCommand.UPGRADE, VersionCommand.parseJson(jsonObject)); + } + + /** + * Test getScript + */ + @Test + public void testGetScript() { + assertEquals(VersionCommand.UPGRADE_VERSION_SCRIPT, VersionCommand.UPGRADE.getScript()); + assertEquals(VersionCommand.ROLLBACK_VERSION_SCRIPT, VersionCommand.ROLLBACK.getScript()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ApiHandlerHelpersTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ApiHandlerHelpersTest.java new file mode 100644 index 00000000..4d72e104 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ApiHandlerHelpersTest.java @@ -0,0 +1,321 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.io.Reader; + +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ApiHandlerHelpers.class, HttpRequest.class, HttpHeaders.class, LoggingService.class, ByteBuf.class, + BufferedReader.class, FileReader.class}) +@Ignore +public class ApiHandlerHelpersTest { + private HttpRequest request; + private HttpMethod expectedMethod; + private String contentType; + private String content; + private HttpHeaders httpHeaders; + private ByteBuf byteBuf; + private DefaultFullHttpResponse defaultResponse; + private BufferedReader bufferedReader; + private FileReader fileReader; + + @Before + public void setUp() throws Exception { + mockStatic(ApiHandlerHelpers.class, Mockito.CALLS_REAL_METHODS); + mockStatic(LoggingService.class); + request = PowerMockito.mock(HttpRequest.class); + httpHeaders = PowerMockito.mock(HttpHeaders.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + bufferedReader = PowerMockito.mock(BufferedReader.class); + fileReader = PowerMockito.mock(FileReader.class); + expectedMethod = HttpMethod.POST; + contentType = "Application/json"; + content = "response content"; + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK); + PowerMockito.whenNew(BufferedReader.class) + .withParameterTypes(Reader.class) + .withArguments(Mockito.any(Reader.class)) + .thenReturn(bufferedReader); + PowerMockito.whenNew(FileReader.class) + .withParameterTypes(String.class) + .withArguments(Mockito.anyString()) + .thenReturn(fileReader); + } + + @After + public void tearDown() throws Exception { + Mockito.reset(request, httpHeaders, byteBuf); + + } + + /** + * Test validate method when expectedMethod and request method are same + */ + @Test + public void testValidateMethodWhenEqual() { + try { + PowerMockito.when(request.method()).thenReturn(HttpMethod.POST); + assertTrue(ApiHandlerHelpers.validateMethod(request, expectedMethod)); + } catch (Exception e){ + fail("This should not happen"); + } + } + /** + * Test validate method when expectedMethod and request method are different + */ + @Test + public void testValidateMethodWhenUnEqual() { + try { + PowerMockito.when(request.method()).thenReturn(HttpMethod.GET); + assertFalse(ApiHandlerHelpers.validateMethod(request, expectedMethod)); + } catch (Exception e){ + fail("This should not happen"); + } + } + + /** + * Test validateContentType when contentType is not present in request + */ + @Test + public void testValidateContentType() { + try { + assertEquals("Incorrect content type ", ApiHandlerHelpers.validateContentType(request, contentType)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test validateContentType when contentType is not present in request + */ + @Test + public void testValidateContentTypeAreDifferent() { + try { + PowerMockito.when(request.headers()).thenReturn(httpHeaders); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.CONTENT_TYPE, "")).thenReturn("text/html"); + assertEquals("Incorrect content type text/html", ApiHandlerHelpers.validateContentType(request, contentType)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test validateContentType when contentType in request is same + */ + @Test + public void testValidateContentTypeAreSame() { + try { + PowerMockito.when(request.headers()).thenReturn(httpHeaders); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.CONTENT_TYPE, "")).thenReturn(contentType); + assertNull(ApiHandlerHelpers.validateContentType(request, contentType)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test validateAccessToken when false + */ + @Test + public void testValidateAccessTokenFalse() { + try { + assertFalse(ApiHandlerHelpers.validateAccessToken(request)); + PowerMockito.verifyPrivate(ApiHandlerHelpers.class).invoke("fetchAccessToken"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test validateAccessToken when true + */ + @Test + public void testValidateAccessTokenTrue() { + try { + PowerMockito.when(request.headers()).thenReturn(httpHeaders); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.AUTHORIZATION, "")).thenReturn("token"); + PowerMockito.when(bufferedReader.readLine()).thenReturn("token"); + assertTrue(ApiHandlerHelpers.validateAccessToken(request)); + PowerMockito.verifyPrivate(ApiHandlerHelpers.class).invoke("fetchAccessToken"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test validateAccessToken when true + */ + @Test + public void testValidateAccesswhenFetchTokenThrowsException() { + try { + PowerMockito.when(request.headers()).thenReturn(httpHeaders); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.AUTHORIZATION, "")).thenReturn("token"); + PowerMockito.when(bufferedReader.readLine()).thenThrow(mock(IOException.class)); + assertFalse(ApiHandlerHelpers.validateAccessToken(request)); + PowerMockito.verifyPrivate(ApiHandlerHelpers.class).invoke("fetchAccessToken"); + LoggingService.logError(Mockito.eq("Local API"), Mockito.eq("unable to load api token"), + Mockito.any()); + + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test successResponse when outputBuffer and content is null & + * returns DefaultFullHttpResponse + */ + @Test + public void testSuccessResponseWhenByteBuffAndContentAreNull() { + assertEquals(new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK), + ApiHandlerHelpers.successResponse(null, null)); + } + + /** + * Test successResponse when outputBuffer and content is null + */ + @Test + public void testSuccessResponseWhenContentIsNull() { + assertEquals(defaultResponse, ApiHandlerHelpers.successResponse(byteBuf, null)); + } + + /** + * Test successResponse when outputBuffer and content is null + */ + @Test + public void testSuccessResponseWhenContentNotNull() { + FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, byteBuf); + HttpUtil.setContentLength(res, byteBuf.readableBytes()); + assertEquals(res, ApiHandlerHelpers.successResponse(byteBuf, content)); + } + + /** + * Test methodNotAllowedResponse + */ + @Test + public void testMethodNotAllowedResponse() { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + assertEquals(defaultResponse, ApiHandlerHelpers.methodNotAllowedResponse()); + } + + /** + * Test badRequestResponse when byteBuf & content is null + */ + @Test + public void testBadRequestResponseByteBufAndContentIsNull() { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST); + assertEquals(defaultResponse, ApiHandlerHelpers.badRequestResponse(null, null)); + } + + /** + * Test badRequestResponse when content is null + */ + @Test + public void testBadRequestResponseContentIsNull() { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST); + assertEquals(defaultResponse, ApiHandlerHelpers.badRequestResponse(byteBuf, null)); + } + + /** + * Test badRequestResponse when content is not null + */ + @Test + public void testBadRequestResponseNotNull() { + FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + HttpUtil.setContentLength(res, byteBuf.readableBytes()); + assertEquals(res, ApiHandlerHelpers.badRequestResponse(byteBuf, content)); + } + + /** + * Test unauthorizedResponse when byteBuf & content is null + */ + @Test + public void testUnauthorizedResponseByteBufAndContentIsNull() { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED); + assertEquals(defaultResponse, ApiHandlerHelpers.unauthorizedResponse(null, null)); + + } + /** + * Test unauthorizedResponse when byteBuf & content is not null + */ + @Test + public void testUnauthorizedResponse() { + FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED, byteBuf); + HttpUtil.setContentLength(res, byteBuf.readableBytes()); + assertEquals(res, ApiHandlerHelpers.unauthorizedResponse(byteBuf, content)); + } + + /** + * Test notFoundResponse + */ + @Test + public void testNotFoundResponseByteBufAndContentIsNull() { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND); + assertEquals(defaultResponse, ApiHandlerHelpers.notFoundResponse(null, null)); + } + + /** + * Test notFoundResponse + */ + @Test + public void testNotFoundResponse() { + FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND, byteBuf); + HttpUtil.setContentLength(res, byteBuf.readableBytes()); + assertEquals(res, ApiHandlerHelpers.notFoundResponse(byteBuf, content)); + } + + /** + * Test internalServerErrorResponse + */ + @Test + public void testInternalServerErrorResponseByteBufAndContentIsNull() { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR); + assertEquals(defaultResponse, ApiHandlerHelpers.internalServerErrorResponse(null, null)); + } + /** + * Test internalServerErrorResponse + */ + @Test + public void testInternalServerErrorResponse() { + FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR, byteBuf); + HttpUtil.setContentLength(res, byteBuf.readableBytes()); + assertEquals(res, ApiHandlerHelpers.internalServerErrorResponse(byteBuf, content)); + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/BluetoothApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/BluetoothApiHandlerTest.java new file mode 100644 index 00000000..108ff299 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/BluetoothApiHandlerTest.java @@ -0,0 +1,146 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.handler.codec.http.*; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static io.netty.handler.codec.http.HttpResponseStatus.NOT_FOUND; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({BluetoothApiHandler.class, FullHttpRequest.class, ByteBuf.class, NioEventLoopGroup.class, + Bootstrap.class, ChannelInitializer.class, LoggingService.class, Channel.class, ChannelFuture.class, + HttpHeaders.class, DefaultFullHttpRequest.class}) +@Ignore +public class BluetoothApiHandlerTest { + private BluetoothApiHandler bluetoothApiHandler; + private FullHttpRequest httpRequest; + private HttpHeaders httpHeaders; + private ByteBuf byteBuf; + private String content; + private byte[] bytes; + private NioEventLoopGroup nioEventLoopGroup; + private Bootstrap bootstrap; + private ChannelInitializer channelInitializer; + private Channel channel; + private ChannelFuture channelFuture; + private DefaultFullHttpResponse defaultResponse; + private DefaultFullHttpRequest defaultFullHttpRequest; + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + @Before + public void setUp() throws Exception { + httpRequest = PowerMockito.mock(FullHttpRequest.class); + httpHeaders = PowerMockito.mock(HttpHeaders.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + nioEventLoopGroup = PowerMockito.mock(NioEventLoopGroup.class); + bootstrap = PowerMockito.mock(Bootstrap.class); + channelInitializer = PowerMockito.mock(ChannelInitializer.class); + channel = PowerMockito.mock(Channel.class); + channelFuture = PowerMockito.mock(ChannelFuture.class); + defaultFullHttpRequest = PowerMockito.mock(DefaultFullHttpRequest.class); + mockStatic(LoggingService.class); + content = "content"; + bytes = content.getBytes(); + bluetoothApiHandler = PowerMockito.spy(new BluetoothApiHandler(httpRequest, byteBuf, bytes)); + PowerMockito.whenNew(NioEventLoopGroup.class) + .withArguments(Mockito.anyInt()) + .thenReturn(nioEventLoopGroup); + PowerMockito.whenNew(Bootstrap.class) + .withNoArguments() + .thenReturn(bootstrap); + PowerMockito.whenNew(ChannelInitializer.class) + .withNoArguments() + .thenReturn(channelInitializer); + PowerMockito.whenNew(DefaultFullHttpRequest.class) + .withParameterTypes(HttpVersion.class, HttpMethod.class, String.class, ByteBuf.class) + .withArguments(Mockito.eq(HttpVersion.HTTP_1_1), Mockito.eq(HttpMethod.POST), Mockito.anyString(), Mockito.any(ByteBuf.class)) + .thenReturn(defaultFullHttpRequest); + PowerMockito.when(defaultFullHttpRequest.headers()).thenReturn(httpHeaders); + PowerMockito.when(bootstrap.channel(Mockito.any())).thenReturn(bootstrap); + PowerMockito.when(bootstrap.option(Mockito.any(), Mockito.anyBoolean())).thenReturn(bootstrap); + PowerMockito.when(bootstrap.group(Mockito.any())).thenReturn(bootstrap); + PowerMockito.when(bootstrap.handler(Mockito.any())).thenReturn(bootstrap); + PowerMockito.when(bootstrap.connect(Mockito.anyString(), Mockito.anyInt())).thenReturn(channelFuture); + PowerMockito.when(channelFuture.sync()).thenReturn(channelFuture); + PowerMockito.when(channelFuture.channel()).thenReturn(channel); + PowerMockito.when(httpRequest.uri()).thenReturn("http://0.0.0.0:5000/"); + PowerMockito.when(httpRequest.headers()).thenReturn(httpHeaders); + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.POST); + } + + @After + public void tearDown() throws Exception { + Mockito.reset(bluetoothApiHandler, httpRequest, byteBuf); + + } + + /** + * Test call when response NOT_FOUND + */ + @Test + public void testCallWhenResponseNotFound() { + try { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + defaultResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + assertEquals(defaultResponse, bluetoothApiHandler.call()); + Mockito.verify(bootstrap).connect(Mockito.eq("localhost"), Mockito.eq(10500)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(Mockito.eq("Local Api : Bluetooth API"), Mockito.eq("Error unable to reach RESTblue container!"), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when response NOT_FOUND + */ + @Test + public void testCallWhenResponseNotFoundAndChannelFlush() { + try { + PowerMockito.when(channel.writeAndFlush(Mockito.any())).thenReturn(channelFuture); + PowerMockito.when(channelFuture.addListener(Mockito.any())).thenReturn(channelFuture); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + defaultResponse.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json"); + assertEquals(defaultResponse, bluetoothApiHandler.call()); + Mockito.verify(bootstrap).connect(Mockito.eq("localhost"), Mockito.eq(10500)); + PowerMockito.verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logError(Mockito.eq("Local Api : Bluetooth API"), Mockito.eq("Error unable to reach RESTblue container!"), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/CommandLineApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/CommandLineApiHandlerTest.java new file mode 100644 index 00000000..95ce885a --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/CommandLineApiHandlerTest.java @@ -0,0 +1,306 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; +import org.eclipse.iofog.command_line.CommandLineParser; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.BufferedReader; +import java.io.Reader; +import java.io.StringReader; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({CommandLineApiHandler.class, HttpRequest.class, ByteBuf.class, LoggingService.class, HttpHeaders.class, + ApiHandlerHelpers.class, Json.class, JsonReader.class, JsonObject.class, CommandLineParser.class, BufferedReader.class}) +@Ignore +public class CommandLineApiHandlerTest { + private CommandLineApiHandler commandLineApiHandler; + private HttpRequest httpRequest; + private ByteBuf byteBuf; + private String content; + private byte[] bytes; + private DefaultFullHttpResponse defaultResponse; + private HttpHeaders httpHeaders; + private String contentType; + private BufferedReader bufferedReader; + private JsonReader jsonReader; + private JsonObject jsonObject; + private String commandLineOutput; + private ObjectMapper objectMapper; + private ExecutorService executor; + + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + + @Before + public void setUp() throws Exception { + executor = Executors.newFixedThreadPool(1); + content = "content"; + contentType = "text/html"; + bytes = content.getBytes(); + objectMapper = new ObjectMapper(); + httpRequest = PowerMockito.mock(HttpRequest.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + jsonReader = PowerMockito.mock(JsonReader.class); + httpHeaders = PowerMockito.mock(HttpHeaders.class); + commandLineApiHandler = PowerMockito.spy(new CommandLineApiHandler(httpRequest, byteBuf, bytes)); + bufferedReader = PowerMockito.mock(BufferedReader.class); + jsonObject = PowerMockito.mock(JsonObject.class); + PowerMockito.mockStatic(ApiHandlerHelpers.class); + PowerMockito.mockStatic(Json.class); + PowerMockito.mockStatic(CommandLineParser.class); + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.POST); + PowerMockito.whenNew(BufferedReader.class) + .withParameterTypes(Reader.class) + .withArguments(Mockito.any(Reader.class)) + .thenReturn(bufferedReader); + mockStatic(LoggingService.class); + PowerMockito.when(httpRequest.headers()).thenReturn(httpHeaders); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.CONTENT_TYPE, "")).thenReturn(contentType); + PowerMockito.when(Json.createReader(Mockito.any(StringReader.class))).thenReturn(jsonReader); + PowerMockito.when(jsonReader.readObject()).thenReturn(jsonObject); + PowerMockito.when(byteBuf.writeBytes(Mockito.any(byte[].class))).thenReturn(byteBuf); + commandLineOutput = "success help"; + PowerMockito.when(ApiHandlerHelpers.validateMethod(httpRequest, POST)).thenReturn(true); + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(null); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(true); + } + + @After + public void tearDown() throws Exception { + defaultResponse = null; + objectMapper = null; + jsonObject = null; + httpRequest = null; + byteBuf = null; + commandLineApiHandler = null; + defaultResponse = null; + jsonReader = null; + bytes = null; + content = null; + executor.shutdown(); + } + + /** + * Test ApiHandlerHelpers.validateMethod returns false + */ + @Test + public void testCallWhenMethodIsNotValid() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.GET); + PowerMockito.when(ApiHandlerHelpers.validateMethod(httpRequest, POST)).thenReturn(false); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + PowerMockito.when(ApiHandlerHelpers.methodNotAllowedResponse()).thenReturn(defaultResponse); + assertEquals(defaultResponse, commandLineApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.methodNotAllowedResponse(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ApiHandlerHelpers.validateContentType returns text/html + */ + @Test + public void testCallWhenContentTypeIsNotValid() { + try { + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn("Incorrect content type text/html"); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + assertEquals(defaultResponse, commandLineApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq("Incorrect content type text/html")); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ApiHandlerHelpers.validateContentType returns null + */ + @Test + public void testCallWhenContentTypeIsNull() { + try { + PowerMockito.when(httpHeaders.get(HttpHeaderNames.CONTENT_TYPE, "")).thenReturn(null); + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn("Incorrect content type null"); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + assertEquals(defaultResponse, commandLineApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateContentType(Mockito.eq(httpRequest), Mockito.eq("application/json")); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq("Incorrect content type null")); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ApiHandlerHelpers.validateAccessToken returns false + */ + @Test + public void testCallWhenAccessTokenIsNull() { + try { + String errorMsg = "Incorrect access token"; + contentType = "application/json"; + PowerMockito.when(httpHeaders.get(HttpHeaderNames.CONTENT_TYPE, "")).thenReturn(contentType); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg))).thenReturn(defaultResponse); + assertEquals(defaultResponse, commandLineApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ApiHandlerHelpers.validateAccessToken returns false + */ + @Test + public void testCallWhenAccessTokenIsNotValid() { + try { + String errorMsg = "Incorrect access token"; + contentType = "application/json"; + PowerMockito.when(httpHeaders.get(HttpHeaderNames.CONTENT_TYPE, "")).thenReturn(contentType); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.AUTHORIZATION, "")).thenReturn("access-token"); + PowerMockito.when(bufferedReader.readLine()).thenReturn("token"); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(false); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED, byteBuf); + PowerMockito.when(ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg))).thenReturn(defaultResponse); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + assertEquals(defaultResponse, commandLineApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call success + */ + @Test + public void testCallSuccess() { + try { + contentType = "application/json"; + PowerMockito.when(jsonObject.getString("command")).thenReturn("help"); + Map resultMap = new HashMap<>(); + resultMap.put("response", commandLineOutput); + String jsonResult = objectMapper.writeValueAsString(resultMap); + PowerMockito.when(CommandLineParser.parse(Mockito.anyString())).thenReturn(commandLineOutput); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(jsonResult))).thenReturn(defaultResponse); + commandLineApiHandler.call(); + verifyStatic(CommandLineParser.class); + CommandLineParser.parse(Mockito.eq("help")); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(jsonResult)); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test call when commandLine.parse method throws exception + */ + @Test + public void testCallFailureResponse() { + try { + contentType = "application/json"; + PowerMockito.when(jsonObject.getString("command")).thenReturn("help"); + PowerMockito.when(CommandLineParser.parse(Mockito.anyString())).thenThrow(mock(AgentUserException.class)); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR, byteBuf); + Map resultMap = new HashMap<>(); + resultMap.put("response", null); + resultMap.put("error", "Internal server error"); + String jsonResult = objectMapper.writeValueAsString(resultMap); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(jsonResult))).thenReturn(defaultResponse); + commandLineApiHandler.call(); + verifyStatic(CommandLineParser.class); + CommandLineParser.parse(Mockito.eq("help")); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.internalServerErrorResponse(Mockito.eq(byteBuf), Mockito.eq(jsonResult)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when jsonReader throws exception + */ + @Test + public void testCallWhenJsonReaderThrowsRuntimeException() { + try { + RuntimeException e = new RuntimeException("Error"); + String errorMsg = " Log message parsing error, " + e.getMessage(); + contentType = "application/json"; + PowerMockito.when(jsonObject.getString("command")).thenReturn("help"); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.CONTENT_TYPE, "")).thenReturn(contentType); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.AUTHORIZATION, "")).thenReturn("token"); + PowerMockito.when(bufferedReader.readLine()).thenReturn("token"); + PowerMockito.when(jsonReader.readObject()).thenThrow(e); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg))).thenReturn(defaultResponse); + commandLineApiHandler.call(); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ConfigApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ConfigApiHandlerTest.java new file mode 100644 index 00000000..6c89979f --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ConfigApiHandlerTest.java @@ -0,0 +1,271 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; +import org.eclipse.iofog.command_line.CommandLineParser; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonReader; +import java.io.StringReader; +import java.util.HashMap; + +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ConfigApiHandler.class, HttpRequest.class, ByteBuf.class, LoggingService.class, ApiHandlerHelpers.class, + HttpHeaders.class, Json.class, JsonReader.class, JsonObject.class, CommandLineParser.class, Configuration.class}) +@Ignore +public class ConfigApiHandlerTest { + private HttpRequest httpRequest; + private ByteBuf byteBuf; + private String content; + private byte[] bytes; + private DefaultFullHttpResponse defaultResponse; + private ConfigApiHandler configApiHandler; + private String contentType; + private HttpHeaders httpHeaders; + private JsonReader jsonReader; + private JsonObject jsonObject; + private String commandLineOutput; + private ObjectMapper objectMapper; + private HashMap responseMap; + + @Before + public void setUp() throws Exception { + httpRequest = PowerMockito.mock(HttpRequest.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + httpHeaders = PowerMockito.mock(HttpHeaders.class); + jsonReader = PowerMockito.mock(JsonReader.class); + jsonObject = PowerMockito.mock(JsonObject.class); + objectMapper = new ObjectMapper(); + responseMap = new HashMap<>(); + commandLineOutput = "success help"; + content = "content"; + contentType = "application/json"; + bytes = content.getBytes(); + PowerMockito.mockStatic(ApiHandlerHelpers.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(Json.class); + PowerMockito.mockStatic(CommandLineParser.class); + PowerMockito.mockStatic(Configuration.class); + configApiHandler = PowerMockito.spy(new ConfigApiHandler(httpRequest, byteBuf, bytes)); + PowerMockito.when(ApiHandlerHelpers.validateMethod(httpRequest, POST)).thenReturn(true); + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(null); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(true); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(httpRequest.headers()).thenReturn(httpHeaders); + PowerMockito.when(httpHeaders.get(HttpHeaderNames.CONTENT_TYPE, "")).thenReturn(contentType); + PowerMockito.when(httpRequest.method()).thenReturn(POST); + PowerMockito.when(Json.createReader(Mockito.any(StringReader.class))).thenReturn(jsonReader); + PowerMockito.when(jsonReader.readObject()).thenReturn(jsonObject); + PowerMockito.when(Configuration.setConfig(Mockito.anyMap(), Mockito.anyBoolean())).thenReturn(responseMap); + } + + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + + @After + public void tearDown() throws Exception { + defaultResponse = null; + Mockito.reset(configApiHandler, httpRequest, byteBuf); + } + + /** + * Test call when httpMethod is not valid + */ + @Test + public void testCallWhenMethodTypeIsInvalid() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.GET); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST))).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.methodNotAllowedResponse()).thenReturn(defaultResponse); + assertEquals(defaultResponse, configApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST)); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.methodNotAllowedResponse(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when contentType is not valid + */ + @Test + public void testCallWhenContentTypeIsInvalid() { + try { + String errorMsg = "Incorrect content type text/html"; + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(errorMsg); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, configApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateContentType(Mockito.eq(httpRequest), Mockito.eq("application/json")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ApiHandlerHelpers.validateAccessToken returns false + */ + @Test + public void testCallWhenAccessTokenIsNull() { + try { + String errorMsg = "Incorrect access token"; + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg))).thenReturn(defaultResponse); + assertEquals(defaultResponse, configApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when request is empty + */ + @Test + public void testCallWhenRequestForSetConfigIsEmpty() { + try { + String errMsg = "Request not valid"; + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errMsg))).thenReturn(defaultResponse); + assertEquals(defaultResponse,configApiHandler.call()); + verifyStatic(Configuration.class, Mockito.never()); + Configuration.setConfig(Mockito.anyMap(), Mockito.eq(false)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call Configuration.setConfig throws exception + */ + @Test + public void testCallWhenRequestForSetConfigIsNotEmptyAndSetConfigThrowsException() { + try { + Exception exp = new Exception("Error setting configuration"); + String errMsg = "Error updating new config " + exp.getMessage(); + PowerMockito.when(jsonObject.containsKey("logs-level")).thenReturn(true); + PowerMockito.when(jsonObject.getString("logs-level")).thenReturn("SEVERE"); + PowerMockito.when(Configuration.setConfig(Mockito.anyMap(), Mockito.eq(false))).thenThrow(exp); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errMsg))).thenReturn(defaultResponse); + assertEquals(defaultResponse,configApiHandler.call()); + verifyStatic(Configuration.class); + Configuration.setConfig(Mockito.anyMap(), Mockito.eq(false)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call Configuration.setConfig returns error + */ + @Test + public void testCallWhenRequestForSetConfigReturnsError() { + try { + responseMap.put("ll", "Invalid input"); + PowerMockito.when(jsonObject.containsKey("logs-level")).thenReturn(true); + PowerMockito.when(jsonObject.getString("logs-level")).thenReturn("SEVERE"); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse,configApiHandler.call()); + responseMap.clear(); + responseMap.put("logs-level","Invalid input"); + String result = objectMapper.writeValueAsString(responseMap); + verifyStatic(Configuration.class); + Configuration.setConfig(Mockito.anyMap(), Mockito.eq(false)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call Configuration.setConfig returns empty map + */ + @Test + public void testCallWhenRequestForSetConfigReturnsEmptyMap() { + try { + PowerMockito.when(jsonObject.containsKey("logs-level")).thenReturn(true); + PowerMockito.when(jsonObject.getString("logs-level")).thenReturn("SEVERE"); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse,configApiHandler.call()); + String result = objectMapper.writeValueAsString(responseMap); + verifyStatic(Configuration.class); + Configuration.setConfig(Mockito.anyMap(), Mockito.eq(false)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result)); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test call when jsonReader throws exception + */ + @Test + public void testCallWhenJsonReaderThrowsRuntimeException() { + try { + RuntimeException e = new RuntimeException("Error"); + String errorMsg = "Log message parsing error, " + e.getMessage(); + contentType = "application/json"; + PowerMockito.when(jsonReader.readObject()).thenThrow(e); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg))).thenReturn(defaultResponse); + assertEquals(defaultResponse,configApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlSignalSentInfoTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlSignalSentInfoTest.java new file mode 100644 index 00000000..b5fd1c4c --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlSignalSentInfoTest.java @@ -0,0 +1,81 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ControlSignalSentInfo.class}) +public class ControlSignalSentInfoTest { + private ControlSignalSentInfo controlSignalSentInfo; + private int sendTryCount = 0; + private long timeMillis; + + @Before + public void setUp() throws Exception { + sendTryCount = 5; + timeMillis = System.currentTimeMillis(); + controlSignalSentInfo = PowerMockito.spy(new ControlSignalSentInfo(sendTryCount, timeMillis)); + } + + @After + public void tearDown() throws Exception { + } + + /** + * Test getTimeMillis + */ + @Test + public void testGetTimeMillis() { + assertEquals(timeMillis, controlSignalSentInfo.getTimeMillis()); + } + + /** + * Test setTimeMillis + */ + @Test + public void testSetTimeMillis() { + timeMillis = timeMillis + 100; + controlSignalSentInfo.setTimeMillis(timeMillis); + assertEquals(timeMillis, controlSignalSentInfo.getTimeMillis()); + } + + /** + * Test getSendTryCount + */ + @Test + public void testGetSendTryCount() { + assertEquals(sendTryCount, controlSignalSentInfo.getSendTryCount()); + } + + /** + * Test setSendTryCount + */ + @Test + public void testSetSendTryCount() { + sendTryCount = sendTryCount + 15; + controlSignalSentInfo.setSendTryCount(sendTryCount); + assertEquals(sendTryCount, controlSignalSentInfo.getSendTryCount()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketHandlerTest.java new file mode 100644 index 00000000..f2f4bc98 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketHandlerTest.java @@ -0,0 +1,347 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.HttpHeaders; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.websocketx.*; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.HashMap; +import java.util.Map; + +import static io.netty.handler.codec.http.HttpHeaders.Names.HOST; +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ControlWebsocketHandler.class, StatusReporter.class, LoggingService.class, WebSocketServerHandshakerFactory.class, + WebSocketMap.class, ChannelHandlerContext.class, HttpRequest.class, HttpHeaders.class, Channel.class, WebSocketServerHandshaker00.class, + ChannelFuture.class, LocalApiStatus.class, WebSocketFrame.class, PingWebSocketFrame.class, ByteBuf.class, + WebsocketUtil.class, ByteBufAllocator.class, BinaryWebSocketFrame.class, CloseWebSocketFrame.class}) +@Ignore +public class ControlWebsocketHandlerTest { + private ControlWebsocketHandler controlWebsocketHandler; + private HttpRequest httpRequest; + private HttpHeaders httpHeaders; + private ChannelHandlerContext channelHandlerContext; + private String MODULE_NAME; + private Channel channel; + private WebSocketServerHandshakerFactory webSocketServerHandshakerFactory; + private WebSocketServerHandshaker00 handShaker; + private ChannelFuture channelFuture; + private LocalApiStatus localApiStatus; + private WebSocketFrame webSocketFrame; + private PingWebSocketFrame pingWebSocketFrame; + private ByteBuf byteBuf; + private ByteBufAllocator byteBufAllocator; + private BinaryWebSocketFrame binaryWebSocketFrame; + private CloseWebSocketFrame closeWebSocketFrame; + private Map contextMap; + + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Local API"; + httpRequest = PowerMockito.mock(HttpRequest.class); + httpHeaders = PowerMockito.mock(HttpHeaders.class); + webSocketServerHandshakerFactory = PowerMockito.mock(WebSocketServerHandshakerFactory.class); + channel = PowerMockito.mock(Channel.class); + handShaker = PowerMockito.mock(WebSocketServerHandshaker00.class); + channelFuture = PowerMockito.mock(ChannelFuture.class); + localApiStatus = PowerMockito.mock(LocalApiStatus.class); + webSocketFrame = PowerMockito.mock(WebSocketFrame.class); + pingWebSocketFrame = PowerMockito.mock(PingWebSocketFrame.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + byteBufAllocator = PowerMockito.mock(ByteBufAllocator.class); + binaryWebSocketFrame = PowerMockito.mock(BinaryWebSocketFrame.class); + closeWebSocketFrame = PowerMockito.mock(CloseWebSocketFrame.class); + contextMap = new HashMap<>(); + PowerMockito.mockStatic(StatusReporter.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(WebsocketUtil.class); + PowerMockito.mockStatic(WebSocketServerHandshakerFactory.class); + channelHandlerContext = PowerMockito.mock(ChannelHandlerContext.class); + controlWebsocketHandler = PowerMockito.spy(new ControlWebsocketHandler()); + PowerMockito.when(httpRequest.uri()).thenReturn("http://localhost:54321/token/qwld"); + PowerMockito.when(channelHandlerContext.channel()).thenReturn(channel); + PowerMockito.when(httpRequest.headers()).thenReturn(httpHeaders); + PowerMockito.when(httpHeaders.get(HOST)).thenReturn("host"); + PowerMockito.whenNew(WebSocketServerHandshakerFactory.class) + .withArguments(Mockito.anyString(), Mockito.eq(null), Mockito.anyBoolean(), Mockito.anyInt()) + .thenReturn(webSocketServerHandshakerFactory); + PowerMockito.doReturn(handShaker).when(webSocketServerHandshakerFactory).newHandshaker(Mockito.any(HttpRequest.class)); + PowerMockito.doReturn(channelFuture).when(handShaker).handshake(Mockito.any(), Mockito.any()); + PowerMockito.when(StatusReporter.setLocalApiStatus()).thenReturn(localApiStatus); + PowerMockito.when(WebsocketUtil.hasContextInMap(Mockito.any(), Mockito.any())).thenReturn(true); + PowerMockito.when(channelHandlerContext.alloc()).thenReturn(byteBufAllocator); + PowerMockito.when(byteBufAllocator.buffer()).thenReturn(byteBuf); + } + + @After + public void tearDown() throws Exception { + MODULE_NAME = null; + Mockito.reset(controlWebsocketHandler, httpRequest, byteBuf); + WebSocketMap.controlWebsocketMap.remove(channelHandlerContext); + } + + /** + * Test handle when ChannelHandlerContext and httpsRequest is Null + */ + @Test + public void testHandleWhenReqAndContextAreNull() { + controlWebsocketHandler.handle(null, null); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(Mockito.eq(MODULE_NAME), Mockito.eq("Error in Handler to open the websocket for the real-time control signals"), + Mockito.any()); + } + + /** + * Test handle when ChannelHandlerContext and httpsRequest are not Null + * & token is less than 5 + */ + @Test + public void testHandleWhenReqAndContextAreNotNullAndTokenIsLessThan5() { + PowerMockito.when(httpRequest.uri()).thenReturn("http://localhost:54321/qwld"); + controlWebsocketHandler.handle(channelHandlerContext, httpRequest); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(Mockito.eq(MODULE_NAME), Mockito.eq(" Missing ID or ID value in URL "), + Mockito.any()); + } + + /** + * Test handle when ChannelHandlerContext and httpsRequest are not Null + * & token is greater than 5 & WebSocketServerHandshaker is not null + */ + @Test + public void testHandleWhenReqAndContextAreNotNullAndTokenIsNotLessThan5() { + try { + controlWebsocketHandler.handle(channelHandlerContext, httpRequest); + PowerMockito.verifyNew(WebSocketServerHandshakerFactory.class) + .withArguments(Mockito.eq("ws://host/v2/control/socket"), Mockito.eq(null), Mockito.eq(true), Mockito.eq(Integer.MAX_VALUE)); + Mockito.verify(webSocketServerHandshakerFactory).newHandshaker(Mockito.eq(httpRequest)); + Mockito.verify(handShaker).handshake(Mockito.eq(channel), Mockito.eq(httpRequest)); + PowerMockito.verifyStatic(StatusReporter.class); + StatusReporter.setLocalApiStatus(); + Mockito.verify(localApiStatus).setOpenConfigSocketsCount(Mockito.eq(WebSocketMap.controlWebsocketMap.size())); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test handle when ChannelHandlerContext and httpsRequest are not Null + * & token is greater than 5 & WebSocketServerHandshaker is null + */ + @Test + public void testHandleWhenReqAndContextAreNotNullAndWebSocketServerHandShakerIsNull() { + try { + PowerMockito.doReturn(null).when(webSocketServerHandshakerFactory).newHandshaker(Mockito.any(HttpRequest.class)); + controlWebsocketHandler.handle(channelHandlerContext, httpRequest); + PowerMockito.verifyNew(WebSocketServerHandshakerFactory.class) + .withArguments(Mockito.eq("ws://host/v2/control/socket"), Mockito.eq(null), Mockito.eq(true), Mockito.eq(Integer.MAX_VALUE)); + Mockito.verify(webSocketServerHandshakerFactory).newHandshaker(Mockito.eq(httpRequest)); + PowerMockito.verifyStatic(WebSocketServerHandshakerFactory.class); + WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channel); + PowerMockito.verifyStatic(StatusReporter.class); + StatusReporter.setLocalApiStatus(); + Mockito.verify(localApiStatus).setOpenConfigSocketsCount(Mockito.eq(WebSocketMap.controlWebsocketMap.size())); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test handleWebSocketFrame when WebSocketFrame is null + */ + @Test + public void testHandleWebSocketFrameWhenWebSocketFrameIsNull() { + controlWebsocketHandler.handleWebSocketFrame(channelHandlerContext, null); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Send control signals to container on configuration change"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Finished handling the websocket frame"); + } + + /** + * Test handleWebSocketFrame when WebSocketFrame is not null + */ + @Test + public void testHandleWebSocketFrameWhenWebSocketFrameIsNotNull() { + controlWebsocketHandler.handleWebSocketFrame(channelHandlerContext, webSocketFrame); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Send control signals to container on configuration change"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Finished handling the websocket frame"); + } + + /** + * Test handleWebSocketFrame when WebSocketFrame is pingWebSocketFrame & readableBytes ==1 + * has open real-time websocket + */ + @Test + public void testHandleWebSocketFrameWhenWebSocketFrameIsInstanceOfPingWebSocketFrame() { + PowerMockito.when(pingWebSocketFrame.content()).thenReturn(byteBuf); + PowerMockito.when(byteBuf.readableBytes()).thenReturn(1); + PowerMockito.when(byteBuf.readByte()).thenReturn((byte)9); + controlWebsocketHandler.handleWebSocketFrame(channelHandlerContext, pingWebSocketFrame); + Mockito.verify(pingWebSocketFrame).content(); + Mockito.verify(byteBuf).readableBytes(); + Mockito.verify(channelHandlerContext).alloc(); + PowerMockito.verifyStatic(WebsocketUtil.class); + WebsocketUtil.hasContextInMap(Mockito.eq(channelHandlerContext), Mockito.eq(WebSocketMap.controlWebsocketMap)); + } + + /** + * Test handleWebSocketFrame when WebSocketFrame is pingWebSocketFrame & readableBytes == 0 + * has open real-time websocket + */ + @Test + public void testHandleWebSocketFrameWhenWebSocketFrameIsInstanceOfPingWebSocketFrameAndReadableBytesIs0() { + PowerMockito.when(pingWebSocketFrame.content()).thenReturn(byteBuf); + PowerMockito.when(byteBuf.readableBytes()).thenReturn(0); + PowerMockito.when(byteBuf.readByte()).thenReturn((byte)9); + controlWebsocketHandler.handleWebSocketFrame(channelHandlerContext, pingWebSocketFrame); + Mockito.verify(pingWebSocketFrame).content(); + PowerMockito.verifyStatic(WebsocketUtil.class, Mockito.never()); + WebsocketUtil.hasContextInMap(Mockito.eq(channelHandlerContext), Mockito.eq(WebSocketMap.controlWebsocketMap)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Ping opcode not found"); + } + + /** + * Test handleWebSocketFrame when binaryWebSocketFrame & + * readableBytes 9 + */ + @Test + public void testHandleWebSocketFrameWhenWebSocketFrameIsInstanceOfbinaryWebSocketFrame() { + PowerMockito.when(binaryWebSocketFrame.content()).thenReturn(byteBuf); + PowerMockito.when(byteBuf.readableBytes()).thenReturn(1); + PowerMockito.when(byteBuf.readByte()).thenReturn((byte)9); + controlWebsocketHandler.handleWebSocketFrame(channelHandlerContext, binaryWebSocketFrame); + Mockito.verify(binaryWebSocketFrame).content(); + Mockito.verify(byteBuf).readableBytes(); + Mockito.verify(byteBuf).readByte(); + } + + /** + * Test handleWebSocketFrame when binaryWebSocketFrame & + * readableBytes 11 + */ + @Test + public void testHandleWebSocketFrameWhenWebSocketFrameIsInstanceOfCloseWebSocketFrame() { + PowerMockito.when(binaryWebSocketFrame.content()).thenReturn(byteBuf); + PowerMockito.when(byteBuf.readableBytes()).thenReturn(1); + PowerMockito.when(byteBuf.readByte()).thenReturn((byte)11); + controlWebsocketHandler.handleWebSocketFrame(channelHandlerContext, binaryWebSocketFrame); + Mockito.verify(binaryWebSocketFrame).content(); + Mockito.verify(byteBuf).readableBytes(); + Mockito.verify(byteBuf).readByte(); + } + + /** + * Test handleWebSocketFrame when binaryWebSocketFrame & + * readableBytes 9 + */ + @Test + public void testHandleWebSocketFrameWhenWebSocketFrameIsInstanceOfbinaryWebSocketFrameAndByteIs11() { + try { + PowerMockito.when(closeWebSocketFrame.content()).thenReturn(byteBuf); + PowerMockito.when(channelHandlerContext.channel()).thenReturn(channel); + PowerMockito.doNothing().when(WebsocketUtil.class, "removeWebsocketContextFromMap", Mockito.any(), Mockito.any()); + controlWebsocketHandler.handleWebSocketFrame(channelHandlerContext, closeWebSocketFrame); + PowerMockito.verifyStatic(WebsocketUtil.class); + WebsocketUtil.removeWebsocketContextFromMap(Mockito.eq(channelHandlerContext), Mockito.eq(WebSocketMap.controlWebsocketMap)); + PowerMockito.verifyStatic(StatusReporter.class); + StatusReporter.setLocalApiStatus(); + } catch (Exception e){ + fail("This should not happen"); + } + } + + /** + * Test initiateControlSignal when map is null + */ + @Test + public void testInitiateControlSignalWhenOldAndNewConfigMapIsNull() { + controlWebsocketHandler.initiateControlSignal(null, null); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Start Helper method to compare the configuration map control signals"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Finished Helper method to compare the configuration map control signals"); + } + + /** + * Test initiateControlSignal when controlWebsocketMap has different value than changedConfigElmtsList + */ + @Test + public void testInitiateControlSignalWhenControlWebsocketMapHasDifferentValue() { + Map newConfigMap = new HashMap<>(); + newConfigMap.put("log-level", "INFO"); + Map oldConfigMap = new HashMap<>(); + oldConfigMap.put("log-level", "SEVERE"); + WebSocketMap.addWebsocket('C', "log-directory", channelHandlerContext); + controlWebsocketHandler.initiateControlSignal(oldConfigMap, newConfigMap); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Start Helper method to compare the configuration map control signals"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Finished Helper method to compare the configuration map control signals"); + } + + /** + * Test initiateControlSignal when controlWebsocketMap has different value than changedConfigElmtsList + */ + @Test + public void testInitiateControlSignalWhenOldAndNewConfigMapHasDifferentValue() { + Map newConfigMap = new HashMap<>(); + newConfigMap.put("log-level", "INFO"); + Map oldConfigMap = new HashMap<>(); + oldConfigMap.put("log-directory", "SEVERE"); + WebSocketMap.addWebsocket('C', "log-directory", channelHandlerContext); + controlWebsocketHandler.initiateControlSignal(oldConfigMap, newConfigMap); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Start Helper method to compare the configuration map control signals"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Finished Helper method to compare the configuration map control signals"); + } + + @Test + public void testInitiateControlSignalWhenControlWebsocketMapHasSameValue() { + Map newConfigMap = new HashMap<>(); + newConfigMap.put("log-level", "INFO"); + Map oldConfigMap = new HashMap<>(); + newConfigMap.put("log-level", "SEVERE"); + WebSocketMap.addWebsocket('C', "log-level", channelHandlerContext); + controlWebsocketHandler.initiateControlSignal(oldConfigMap, newConfigMap); + Mockito.verify(channelHandlerContext).alloc(); + Mockito.verify(byteBuf).writeByte(Mockito.eq(0xC)); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketWorkerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketWorkerTest.java new file mode 100644 index 00000000..a2b15ff7 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketWorkerTest.java @@ -0,0 +1,126 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufAllocator; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ControlWebsocketWorker.class, LoggingService.class, ChannelHandlerContext.class, ControlSignalSentInfo.class, + ByteBufAllocator.class, ByteBuf.class, Channel.class, WebsocketUtil.class, StatusReporter.class, LocalApiStatus.class}) +@Ignore +public class ControlWebsocketWorkerTest { + private String MODULE_NAME; + private ControlWebsocketWorker controlWebsocketWorker; + private ChannelHandlerContext context; + private ControlSignalSentInfo controlSignalSentInfo; + private ByteBufAllocator byteBufAllocator; + private ByteBuf byteBuf; + private Channel channel; + private LocalApiStatus localApiStatus; + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Local API"; + context = PowerMockito.mock(ChannelHandlerContext.class); + channel = PowerMockito.mock(Channel.class); + byteBufAllocator = PowerMockito.mock(ByteBufAllocator.class); + controlSignalSentInfo = PowerMockito.mock(ControlSignalSentInfo.class); + localApiStatus = PowerMockito.mock(LocalApiStatus.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(StatusReporter.class); + PowerMockito.mockStatic(WebsocketUtil.class); + controlWebsocketWorker = PowerMockito.spy(new ControlWebsocketWorker()); + byteBuf = PowerMockito.mock(ByteBuf.class); + PowerMockito.when(context.alloc()).thenReturn(byteBufAllocator); + PowerMockito.when(byteBufAllocator.buffer()).thenReturn(byteBuf); + PowerMockito.when(context.channel()).thenReturn(channel); + PowerMockito.when(StatusReporter.setLocalApiStatus()).thenReturn(localApiStatus); + + } + + @After + public void tearDown() throws Exception { + Mockito.reset(controlWebsocketWorker, controlSignalSentInfo); + WebSocketMap.unackControlSignalsMap.remove(context); + } + + /** + * Test run when WebSocketMap.unackControlSignalsMap is empty + */ + @Test + public void testRunWhenUnackControlSignalsMapIsEmpty() { + controlWebsocketWorker.run(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME,"Initiating control signals for unacknowledged signals"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME,"Finished Initiating control signals for unacknowledged signals"); + } + + /** + * Test run when WebSocketMap.unackControlSignalsMap is not empty + * controlSignalSentInfo.getSendTryCount() < 10 + */ + @Test + public void testRunWhenUnackControlSignalsMapIsNotEmpty() { + try { + WebSocketMap.unackControlSignalsMap.put(context, controlSignalSentInfo); + controlWebsocketWorker.run(); + Mockito.verify(controlSignalSentInfo, Mockito.atLeastOnce()).getSendTryCount(); + PowerMockito.verifyPrivate(controlWebsocketWorker).invoke("initiateControlSignal", Mockito.eq(context)); + Mockito.verify(context).alloc(); + Mockito.verify(context).channel(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test run when WebSocketMap.unackControlSignalsMap is not empty + * controlSignalSentInfo.getSendTryCount() > 10 + */ + @Test + public void testRunWhenUnackControlSignalsMapIsNotEmptyAndSendTryCountIsGreaterThan10() { + try { + WebSocketMap.unackControlSignalsMap.put(context, controlSignalSentInfo); + PowerMockito.when(controlSignalSentInfo.getSendTryCount()).thenReturn(11); + controlWebsocketWorker.run(); + Mockito.verify(controlSignalSentInfo, Mockito.atLeastOnce()).getSendTryCount(); + PowerMockito.verifyPrivate(controlWebsocketWorker, Mockito.never()).invoke("initiateControlSignal", Mockito.eq(context)); + PowerMockito.verifyStatic(WebsocketUtil.class); + WebsocketUtil.removeWebsocketContextFromMap(Mockito.eq(context), Mockito.eq(WebSocketMap.controlWebsocketMap)); + Mockito.verify(localApiStatus).setOpenConfigSocketsCount(Mockito.eq(WebSocketMap.controlWebsocketMap.size())); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/DeprovisionApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/DeprovisionApiHandlerTest.java new file mode 100644 index 00000000..e0dea7a1 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/DeprovisionApiHandlerTest.java @@ -0,0 +1,191 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpUtil; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static io.netty.handler.codec.http.HttpMethod.DELETE; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({DeprovisionApiHandler.class, ApiHandlerHelpers.class, LoggingService.class, FieldAgent.class}) +@Ignore +public class DeprovisionApiHandlerTest { + private DeprovisionApiHandler deprovisionApiHandler; + private HttpRequest httpRequest; + private ByteBuf byteBuf; + private String content; + private byte[] bytes; + private DefaultFullHttpResponse defaultResponse; + private FieldAgent fieldAgent; + private ExecutorService executor; + + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + + @Before + public void setUp() throws Exception { + executor = Executors.newFixedThreadPool(1); + PowerMockito.mockStatic(ApiHandlerHelpers.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(FieldAgent.class); + httpRequest = PowerMockito.mock(HttpRequest.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + fieldAgent = PowerMockito.mock(FieldAgent.class); + content = "content"; + bytes = content.getBytes(); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + deprovisionApiHandler = PowerMockito.spy(new DeprovisionApiHandler(httpRequest, byteBuf, bytes)); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(DELETE))).thenReturn(true); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(true); + PowerMockito.when(FieldAgent.getInstance()).thenReturn(fieldAgent); + } + + @After + public void tearDown() throws Exception { + executor.shutdown(); + deprovisionApiHandler = null; + httpRequest = null; + byteBuf = null; + defaultResponse = null; + bytes = null; + content = null; + } + + /** + * Test call when httpMethod is not valid + */ + @Test + public void testCallWhenMethodTypeIsInvalid() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.GET); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(DELETE))).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.methodNotAllowedResponse()).thenReturn(defaultResponse); + assertEquals(defaultResponse, deprovisionApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(DELETE)); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.methodNotAllowedResponse(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ApiHandlerHelpers.validateAccessToken returns false + */ + @Test + public void testCallWhenAccessTokenIsNull() { + try { + String errorMsg = "Incorrect access token"; + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg))).thenReturn(defaultResponse); + assertEquals(defaultResponse, deprovisionApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test deprovisionApiHandler.call when FieldAgent deprovision response is failure + */ + @Test + public void testCallWhenFieldAgentDeprovisionReturnsFailureStatus() { + try { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR, byteBuf); + PowerMockito.when(ApiHandlerHelpers.internalServerErrorResponse(Mockito.eq(byteBuf), Mockito.anyString())).thenReturn(defaultResponse); + PowerMockito.when(fieldAgent.deProvision(false)).thenReturn("\nFailure - not provisioned"); + assertEquals(defaultResponse, deprovisionApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.internalServerErrorResponse(Mockito.eq(byteBuf), Mockito.anyString()); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test deprovisionApiHandler.call when FieldAgent deprovision throws exception + */ + @Test + public void testCallWhenFieldAgentDeprovisionThrowsException() { + try { + RuntimeException e = new RuntimeException("Error while deprovisioning"); + String errorMsg = "Log message parsing error, " + e.getMessage(); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.anyString())).thenReturn(defaultResponse); + PowerMockito.when(fieldAgent.deProvision(false)).thenThrow(e); + assertEquals(defaultResponse, deprovisionApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test deprovisionApiHandler.call when FieldAgent deprovision response is success + */ + @Test + public void testCallWhenFieldAgentDeprovisionReturnsFailureStatusIsNull() { + try { + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.anyString())).thenReturn(defaultResponse); + PowerMockito.when(fieldAgent.deProvision(false)).thenReturn("\nSuccess - tokens, identifiers and keys removed"); + assertEquals(defaultResponse, deprovisionApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(DELETE)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.anyString()); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GetConfigurationHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GetConfigurationHandlerTest.java new file mode 100644 index 00000000..4134c82c --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GetConfigurationHandlerTest.java @@ -0,0 +1,212 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpUtil; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.*; + +import java.io.StringReader; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static io.netty.handler.codec.http.HttpMethod.DELETE; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({GetConfigurationHandler.class, LoggingService.class, HttpRequest.class, ByteBuf.class, ApiHandlerHelpers.class, + Json.class, JsonReader.class, JsonObject.class, JsonBuilderFactory.class, JsonObjectBuilder.class}) +@Ignore +public class GetConfigurationHandlerTest { + private GetConfigurationHandler getConfigurationHandler; + private HttpRequest httpRequest; + private ByteBuf byteBuf; + private String content; + private byte[] bytes; + private DefaultFullHttpResponse defaultResponse; + private JsonReader jsonReader; + private JsonObject jsonObject; + private JsonBuilderFactory jsonBuilderFactory; + private JsonObjectBuilder jsonObjectBuilder; + private String result; + private ExecutorService executor; + + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + + @Before + public void setUp() throws Exception { + executor = Executors.newFixedThreadPool(1); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(ApiHandlerHelpers.class); + PowerMockito.mockStatic(Json.class); + httpRequest = PowerMockito.mock(HttpRequest.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + jsonReader = PowerMockito.mock(JsonReader.class); + jsonObject = PowerMockito.mock(JsonObject.class); + jsonBuilderFactory = PowerMockito.mock(JsonBuilderFactory.class); + jsonObjectBuilder = PowerMockito.mock(JsonObjectBuilder.class); + content = "content"; + result = "result"; + bytes = content.getBytes(); + getConfigurationHandler = PowerMockito.spy(new GetConfigurationHandler(httpRequest, byteBuf, bytes)); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST))).thenReturn(true); + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(null); + PowerMockito.when(Json.createReader(Mockito.any(StringReader.class))).thenReturn(jsonReader); + PowerMockito.when(Json.createBuilderFactory(Mockito.eq(null))).thenReturn(jsonBuilderFactory); + PowerMockito.when(jsonBuilderFactory.createObjectBuilder()).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonObjectBuilder.build()).thenReturn(jsonObject); + PowerMockito.when(jsonObjectBuilder.add(Mockito.anyString(), Mockito.anyString())).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonReader.readObject()).thenReturn(jsonObject); + PowerMockito.when(jsonObject.toString()).thenReturn(result); + } + + @After + public void tearDown() throws Exception { + executor.shutdown(); + getConfigurationHandler = null; + jsonObject = null; + httpRequest = null; + byteBuf = null; + result = null; + defaultResponse = null; + jsonReader = null; + bytes = null; + content = null; + jsonBuilderFactory = null; + jsonObjectBuilder = null; + } + + /** + * Test call when httpMethod is not valid + */ + @Test + public void testCallWhenMethodTypeIsInvalid() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.GET); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST))).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.methodNotAllowedResponse()).thenReturn(defaultResponse); + assertEquals(defaultResponse, getConfigurationHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST)); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.methodNotAllowedResponse(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + + /** + * Test call when contentType is not valid + */ + @Test + public void testCallWhenContentTypeIsInvalid() { + try { + String errorMsg = "Incorrect content type text/html"; + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(errorMsg); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, getConfigurationHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateContentType(Mockito.eq(httpRequest), Mockito.eq("application/json")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when request is not valid jsonObject doesn't contain id + */ + @Test + public void testCallWhenRequestIsNotValid() { + try { + String errorMsg = "Incorrect content/data, Id value not found "; + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, getConfigurationHandler.call()); + PowerMockito.verifyPrivate(getConfigurationHandler, Mockito.atLeastOnce()).invoke("validateRequest", Mockito.eq(jsonObject)); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when request is valid jsonObjectcontain id & No configuration found + */ + @Test + public void testCallWhenRequestIsValid() { + try { + PowerMockito.when(jsonObject.containsKey("id")).thenReturn(true); + PowerMockito.when(jsonObject.isNull("id")).thenReturn(false); + PowerMockito.when(jsonObject.getString("id")).thenReturn("id"); + String errorMsg = "No configuration found for the id id"; + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, getConfigurationHandler.call()); + PowerMockito.verifyPrivate(getConfigurationHandler, Mockito.atLeastOnce()).invoke("validateRequest", Mockito.eq(jsonObject)); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test call when request is valid jsonObjectcontain id & No configuration found + */ + @Test + public void testCallWhenRequestIsValidAndIsPresentInConfigurationMap() { + try { + PowerMockito.when(jsonObject.containsKey("id")).thenReturn(true); + PowerMockito.when(jsonObject.isNull("id")).thenReturn(false); + PowerMockito.when(jsonObject.getString("id")).thenReturn("id"); + ConfigurationMap.containerConfigMap.put("id", "value"); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.any(), Mockito.any())).thenReturn(defaultResponse); + assertEquals(defaultResponse, getConfigurationHandler.call()); + PowerMockito.verifyPrivate(getConfigurationHandler, Mockito.atLeastOnce()).invoke("validateRequest", Mockito.eq(jsonObject)); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result)); + ConfigurationMap.containerConfigMap.remove("id"); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GpsApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GpsApiHandlerTest.java new file mode 100644 index 00000000..58c77c33 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GpsApiHandlerTest.java @@ -0,0 +1,221 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.*; + +import java.io.StringReader; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({GpsApiHandler.class, HttpRequest.class, ByteBuf.class, ApiHandlerHelpers.class, LoggingService.class, + Json.class, JsonReader.class, JsonObject.class, Configuration.class, JsonBuilderFactory.class, JsonObjectBuilder.class}) +@Ignore +public class GpsApiHandlerTest { + private GpsApiHandler gpsApiHandler; + private HttpRequest httpRequest; + private ByteBuf byteBuf; + private String content; + private byte[] bytes; + private JsonReader jsonReader; + private JsonObject jsonObject; + private DefaultFullHttpResponse defaultResponse; + private JsonBuilderFactory jsonBuilderFactory; + private JsonObjectBuilder jsonObjectBuilder; + private String result; + private ExecutorService executor; + + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + + @Before + public void setUp() throws Exception { + executor = Executors.newFixedThreadPool(1); + PowerMockito.mockStatic(ApiHandlerHelpers.class); + PowerMockito.mockStatic(Configuration.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(Json.class); + httpRequest = PowerMockito.mock(HttpRequest.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + content = "content"; + bytes = content.getBytes(); + result = "result"; + jsonReader = PowerMockito.mock(JsonReader.class); + jsonObject = PowerMockito.mock(JsonObject.class); + jsonBuilderFactory = PowerMockito.mock(JsonBuilderFactory.class); + jsonObjectBuilder = PowerMockito.mock(JsonObjectBuilder.class); + gpsApiHandler = PowerMockito.spy(new GpsApiHandler(httpRequest, byteBuf, bytes)); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(null); + PowerMockito.when(Json.createReader(Mockito.any(StringReader.class))).thenReturn(jsonReader); + PowerMockito.when(jsonReader.readObject()).thenReturn(jsonObject); + PowerMockito.doNothing().when(Configuration.class, "setGpsDataIfValid", Mockito.any(), Mockito.anyString()); + PowerMockito.doNothing().when(Configuration.class, "writeGpsToConfigFile"); + PowerMockito.doNothing().when(Configuration.class, "saveConfigUpdates"); + PowerMockito.when(Json.createBuilderFactory(Mockito.eq(null))).thenReturn(jsonBuilderFactory); + PowerMockito.when(jsonBuilderFactory.createObjectBuilder()).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonObjectBuilder.build()).thenReturn(jsonObject); + PowerMockito.when(jsonObjectBuilder.add(Mockito.anyString(), Mockito.anyString())).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonObject.toString()).thenReturn(result); + + } + + @After + public void tearDown() throws Exception { + executor.shutdown(); + gpsApiHandler = null; + jsonObject = null; + httpRequest = null; + byteBuf = null; + result = null; + defaultResponse = null; + jsonReader = null; + bytes = null; + content = null; + jsonBuilderFactory = null; + jsonObjectBuilder = null; + } + + /** + * Test call when contentType is not valid + */ + @Test + public void testCallWhenContentTypeIsInvalid() { + try { + String errorMsg = "Incorrect content type text/html"; + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(errorMsg); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, gpsApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateContentType(Mockito.eq(httpRequest), Mockito.eq("application/json")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when contentType is valid + * Request type is DELETE which is not supported + */ + @Test + public void testCallWhenRequestTypeIsDelete() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.DELETE); + String errorMsg = "Not supported method: " + httpRequest.method(); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, gpsApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when contentType is valid + * Request type is POST & there is Error with setting GPS + */ + @Test + public void testCallWhenRequestTypeIsPostAndSaveConfigurationThrowsException() { + try { + Exception exp = new Exception("Error"); + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.POST); + PowerMockito.doThrow(exp).when(Configuration.class, "saveConfigUpdates"); + String errorMsg = " Error with setting GPS, " + exp.getMessage(); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, gpsApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + PowerMockito.verifyStatic(Configuration.class); + Configuration.writeGpsToConfigFile(); + PowerMockito.verifyStatic(Configuration.class); + Configuration.saveConfigUpdates(); + PowerMockito.verifyPrivate(gpsApiHandler).invoke("setAgentGpsCoordinates"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when contentType is valid + * Request type is POST & successfully updates configuration + */ + @Test + public void testCallWhenRequestTypeIsPost() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.POST); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, gpsApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result)); + PowerMockito.verifyStatic(Configuration.class); + Configuration.writeGpsToConfigFile(); + PowerMockito.verifyStatic(Configuration.class); + Configuration.saveConfigUpdates(); + PowerMockito.verifyPrivate(gpsApiHandler).invoke("setAgentGpsCoordinates"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when contentType is valid + * Request type is GET + */ + @Test + public void testCallWhenRequestTypeIsGET() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(HttpMethod.GET); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + PowerMockito.when(Configuration.getGpsCoordinates()).thenReturn("10.20.30,120.90.80"); + assertEquals(defaultResponse, gpsApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result)); + PowerMockito.verifyStatic(Configuration.class); + Configuration.getGpsCoordinates(); + PowerMockito.verifyPrivate(gpsApiHandler).invoke("getAgentGpsCoordinates"); + } catch (Exception e) { + fail("This should not happen"); + } + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/InfoApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/InfoApiHandlerTest.java new file mode 100644 index 00000000..3adee003 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/InfoApiHandlerTest.java @@ -0,0 +1,180 @@ +package org.eclipse.iofog.local_api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.*; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.*; +import org.junit.rules.Timeout; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.*; + +import java.io.StringReader; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.verifyStatic; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({InfoApiHandler.class, HttpRequest.class, ByteBuf.class, ApiHandlerHelpers.class, LoggingService.class, + Json.class, JsonReader.class, JsonObject.class, Configuration.class, + ObjectMapper.class}) +@Ignore +public class InfoApiHandlerTest { + private InfoApiHandler infoApiHandler; + private HttpRequest httpRequest; + private ByteBuf byteBuf; + private String content; + private byte[] bytes; + private JsonReader jsonReader; + private JsonObject jsonObject; + private DefaultFullHttpResponse defaultResponse; + private String result; + private ObjectMapper objectMapper; + private ExecutorService executor; + + + //global timeout rule + @Rule + public Timeout globalTimeout = Timeout.millis(100000l); + + @Before + public void setUp() throws Exception { + executor = Executors.newFixedThreadPool(1); + PowerMockito.mockStatic(ApiHandlerHelpers.class); + PowerMockito.mockStatic(Configuration.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(Json.class); + httpRequest = PowerMockito.mock(HttpRequest.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + content = "content"; + bytes = content.getBytes(); + result = "result"; + jsonReader = PowerMockito.mock(JsonReader.class); + objectMapper = PowerMockito.mock(ObjectMapper.class); + jsonObject = PowerMockito.mock(JsonObject.class); + infoApiHandler = PowerMockito.spy(new InfoApiHandler(httpRequest, byteBuf, bytes)); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(GET))).thenReturn(true); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(true); + PowerMockito.when(Json.createReader(Mockito.any(StringReader.class))).thenReturn(jsonReader); + PowerMockito.when(jsonReader.readObject()).thenReturn(jsonObject); + PowerMockito.when(jsonObject.toString()).thenReturn(result); + PowerMockito.whenNew(ObjectMapper.class).withNoArguments().thenReturn(objectMapper); + PowerMockito.when(objectMapper.writeValueAsString(Mockito.any())).thenReturn(result); + } + + @After + public void tearDown() throws Exception { + infoApiHandler = null; + objectMapper = null; + jsonObject = null; + httpRequest = null; + byteBuf = null; + result = null; + defaultResponse = null; + jsonReader = null; + bytes = null; + content = null; + executor.shutdown(); + } + + /** + * Test call when httpMethod is not valid + */ + @Test + public void testCallWhenMethodTypeIsInvalid() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(POST); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(GET))).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.methodNotAllowedResponse()).thenReturn(defaultResponse); + assertEquals(defaultResponse, infoApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(GET)); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.methodNotAllowedResponse(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ApiHandlerHelpers.validateAccessToken returns false + */ + @Test + public void testCallWhenAccessTokenIsNull() { + try { + String errorMsg = "Incorrect access token"; + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, UNAUTHORIZED, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg))).thenReturn(defaultResponse); + assertEquals(defaultResponse, infoApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.unauthorizedResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when method & access token is valid + */ + @Test + public void testCallWhenMethodAndAccessTokenAreValid() { + try { + PowerMockito.when(Configuration.getConfigReport()).thenReturn("gps-coordinates(lat,lon) : 10.20.10.90,100.30.50"); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + HttpUtil.setContentLength(defaultResponse, byteBuf.readableBytes()); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result))).thenReturn(defaultResponse); + assertEquals(defaultResponse, infoApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when method & access token is valid + */ + @Test + public void testCallWhenMethodAndAccessTokenAreValidObjectMapperThrowsException() { + try { + String errorMsg = "Log message parsing error, null"; + PowerMockito.when(Configuration.getConfigReport()).thenReturn("developer's-mode : true"); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.doThrow(PowerMockito.mock(JsonProcessingException.class)).when(objectMapper).writeValueAsString(Mockito.any()); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg))).thenReturn(defaultResponse); + assertEquals(defaultResponse, infoApiHandler.call()); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateAccessToken(Mockito.eq(httpRequest)); + verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerHandlerTest.java new file mode 100644 index 00000000..e3fd2493 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerHandlerTest.java @@ -0,0 +1,30 @@ +package org.eclipse.iofog.local_api; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class LocalApiServerHandlerTest { + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void channelRead0() { + } + + @Test + public void channelReadComplete() { + } + + @Test + public void exceptionCaught() { + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactoryTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactoryTest.java new file mode 100644 index 00000000..0e084baa --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactoryTest.java @@ -0,0 +1,110 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpServerCodec; +import io.netty.handler.ssl.SslContext; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({LocalApiServerPipelineFactory.class, SslContext.class, SocketChannel.class, ChannelPipeline.class, + LoggingService.class, HttpServerCodec.class, HttpObjectAggregator.class, LocalApiServerHandler.class, DefaultEventExecutorGroup.class}) +@Ignore +public class LocalApiServerPipelineFactoryTest { + private LocalApiServerPipelineFactory localApiServerPipelineFactory; + private SslContext sslContext; + private SocketChannel channel; + private ChannelPipeline pipeline; + private LocalApiServerHandler serverHandler; + private HttpObjectAggregator httpObjectAggregator; + private HttpServerCodec httpServerCodec; + private DefaultEventExecutorGroup defaultEventExecutorGroup; + private ExecutorService executor; + + + @Before + public void setUp() throws Exception { + executor = Executors.newFixedThreadPool(1); + httpServerCodec = PowerMockito.mock(HttpServerCodec.class); + httpObjectAggregator = PowerMockito.mock(HttpObjectAggregator.class); + serverHandler = PowerMockito.mock(LocalApiServerHandler.class); + sslContext = PowerMockito.mock(SslContext.class); + channel = PowerMockito.mock(SocketChannel.class); + pipeline = PowerMockito.mock(ChannelPipeline.class); + defaultEventExecutorGroup = PowerMockito.mock(DefaultEventExecutorGroup.class); + PowerMockito.mockStatic(LoggingService.class); + localApiServerPipelineFactory = PowerMockito.spy(new LocalApiServerPipelineFactory(sslContext)); + PowerMockito.when(channel.pipeline()).thenReturn(pipeline); + PowerMockito.whenNew(HttpServerCodec.class).withNoArguments().thenReturn(httpServerCodec); + PowerMockito.whenNew(LocalApiServerHandler.class) + .withArguments(Mockito.any(EventExecutorGroup.class)) + .thenReturn(serverHandler); + PowerMockito.whenNew(HttpObjectAggregator.class) + .withArguments(Mockito.eq(Integer.MAX_VALUE)) + .thenReturn(httpObjectAggregator); + PowerMockito.whenNew(DefaultEventExecutorGroup.class) + .withArguments(Mockito.eq(10)) + .thenReturn(defaultEventExecutorGroup); + } + + @After + public void tearDown() throws Exception { + localApiServerPipelineFactory = null; + sslContext = null; + httpObjectAggregator = null; + serverHandler = null; + httpServerCodec = null; + defaultEventExecutorGroup = null; + pipeline = null; + channel = null; + executor.shutdown(); + } + + /** + * Test initChannel + */ + @Test + public void testInitChannel() { + try { + localApiServerPipelineFactory.initChannel(channel); + Mockito.verify(pipeline).addLast(Mockito.eq(httpObjectAggregator)); + Mockito.verify(pipeline).addLast(Mockito.eq(serverHandler)); + Mockito.verify(pipeline).addLast(Mockito.eq(httpServerCodec)); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerTest.java new file mode 100644 index 00000000..ef53ee0b --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerTest.java @@ -0,0 +1,138 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.handler.ssl.util.SelfSignedCertificate; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({LocalApiServer.class, NioEventLoopGroup.class, SelfSignedCertificate.class, ServerBootstrap.class, LocalApiServerPipelineFactory.class, LoggingService.class, + ChannelFuture.class, ControlWebsocketWorker.class, MessageWebsocketWorker.class}) +@Ignore +public class LocalApiServerTest { + private LocalApiServer localApiServer; + private NioEventLoopGroup nioEventLoopGroup; + private SelfSignedCertificate selfSignedCertificate; + private ServerBootstrap serverBootstrap; + private LocalApiServerPipelineFactory localApiServerPipelineFactory; + private ChannelFuture channelFuture; + private Channel channel; + private ControlWebsocketWorker controlWebsocketWorker; + private MessageWebsocketWorker messageWebsocketWorker; + private String MODULE_NAME; + + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Local API"; + PowerMockito.mockStatic(LoggingService.class); + nioEventLoopGroup = PowerMockito.mock(NioEventLoopGroup.class); + channel = PowerMockito.mock(Channel.class); + controlWebsocketWorker = PowerMockito.mock(ControlWebsocketWorker.class); + messageWebsocketWorker = PowerMockito.mock(MessageWebsocketWorker.class); + channelFuture = PowerMockito.mock(ChannelFuture.class); + selfSignedCertificate = PowerMockito.mock(SelfSignedCertificate.class); + serverBootstrap = PowerMockito.mock(ServerBootstrap.class); + localApiServerPipelineFactory = PowerMockito.mock(LocalApiServerPipelineFactory.class); + localApiServer = PowerMockito.spy(new LocalApiServer()); + PowerMockito.whenNew(NioEventLoopGroup.class).withArguments(Mockito.eq(1)) + .thenReturn(nioEventLoopGroup); + PowerMockito.whenNew(NioEventLoopGroup.class).withArguments(Mockito.eq(10)) + .thenReturn(nioEventLoopGroup); + PowerMockito.whenNew(SelfSignedCertificate.class).withNoArguments() + .thenReturn(selfSignedCertificate); + PowerMockito.whenNew(LocalApiServerPipelineFactory.class).withArguments(Mockito.any()) + .thenReturn(localApiServerPipelineFactory); + PowerMockito.whenNew(ServerBootstrap.class).withNoArguments() + .thenReturn(serverBootstrap); + PowerMockito.whenNew(MessageWebsocketWorker.class).withNoArguments() + .thenReturn(messageWebsocketWorker); + PowerMockito.whenNew(ControlWebsocketWorker.class).withNoArguments() + .thenReturn(controlWebsocketWorker); + PowerMockito.when(serverBootstrap.group(Mockito.any(NioEventLoopGroup.class), Mockito.any(NioEventLoopGroup.class))).thenReturn(serverBootstrap); + PowerMockito.when(serverBootstrap.channel(Mockito.any())).thenReturn(serverBootstrap); + PowerMockito.when(serverBootstrap.childHandler(Mockito.any())).thenReturn(serverBootstrap); + PowerMockito.when(serverBootstrap.bind(Mockito.eq(54321))).thenReturn(channelFuture); + PowerMockito.when(channelFuture.sync()).thenReturn(channelFuture); + PowerMockito.when(channelFuture.channel()).thenReturn(channel); + PowerMockito.when(channel.closeFuture()).thenReturn(channelFuture); + } + + @After + public void tearDown() throws Exception { + localApiServer.stop(); + localApiServer = null; + MODULE_NAME = null; + nioEventLoopGroup = null; + channel = null; + controlWebsocketWorker = null; + messageWebsocketWorker = null; + channelFuture = null; + selfSignedCertificate = null; + serverBootstrap = null; + localApiServerPipelineFactory = null; + } + + /** + * Test start + */ + @Test + public void testStart() { + try { + localApiServer.start(); + Mockito.verify(serverBootstrap).childHandler(Mockito.eq(localApiServerPipelineFactory)); + Mockito.verify(serverBootstrap).channel(Mockito.eq(NioServerSocketChannel.class)); + Mockito.verify(serverBootstrap).bind(Mockito.eq(54321)); + Mockito.verify(channel).closeFuture(); + Mockito.verify(channelFuture).channel(); + Mockito.verify(channelFuture, Mockito.atLeastOnce()).sync(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test stop + */ + @Test + public void testStop() { + try { + localApiServer.stop(); + PowerMockito.mockStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Start stopping Local api server\n"); + PowerMockito.mockStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Local api server stopped\n"); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiStatusTest.java new file mode 100644 index 00000000..acb0ae54 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiStatusTest.java @@ -0,0 +1,63 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({LocalApiStatus.class}) +public class LocalApiStatusTest { + private LocalApiStatus localApiStatus; + + @Before + public void setUp() throws Exception { + localApiStatus = PowerMockito.spy(new LocalApiStatus()); + } + + @After + public void tearDown() throws Exception { + localApiStatus = null; + } + + /** + * Test getter and setter of OpenConfigSocketsCount + */ + @Test + public void testGetAndSetOfOpenConfigSocketsCount() { + assertEquals(0, localApiStatus.getOpenConfigSocketsCount()); + localApiStatus.setOpenConfigSocketsCount(10); + assertEquals(10, localApiStatus.getOpenConfigSocketsCount()); + } + + /** + * Test getter and setter of penMessageSocketsCount + */ + @Test + public void testGetAndSetOpenMessageSocketsCount() { + assertEquals(0, localApiStatus.getOpenMessageSocketsCount()); + localApiStatus.setOpenMessageSocketsCount(10); + assertEquals(10, localApiStatus.getOpenMessageSocketsCount()); + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LogApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LogApiHandlerTest.java new file mode 100644 index 00000000..33e415cd --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LogApiHandlerTest.java @@ -0,0 +1,230 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.HttpRequest; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.*; + +import java.io.StringReader; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import static io.netty.handler.codec.http.HttpMethod.GET; +import static io.netty.handler.codec.http.HttpMethod.POST; +import static io.netty.handler.codec.http.HttpResponseStatus.*; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({LogApiHandler.class, LoggingService.class, HttpRequest.class, HttpRequest.class, ByteBuf.class, JsonReader.class, + JsonObject.class, ApiHandlerHelpers.class, Configuration.class, Json.class, JsonBuilderFactory.class, JsonObjectBuilder.class}) +@Ignore +public class LogApiHandlerTest { + private LogApiHandler logApiHandler; + private HttpRequest httpRequest; + private ByteBuf byteBuf; + private String content; + private byte[] bytes; + private JsonReader jsonReader; + private JsonObject jsonObject; + private DefaultFullHttpResponse defaultResponse; + private String result; + private JsonBuilderFactory jsonBuilderFactory; + private JsonObjectBuilder jsonObjectBuilder; + private ExecutorService executor; + + @Before + public void setUp() throws Exception { + executor = Executors.newFixedThreadPool(1); + PowerMockito.mockStatic(ApiHandlerHelpers.class); + PowerMockito.mockStatic(Configuration.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(Json.class); + httpRequest = PowerMockito.mock(HttpRequest.class); + byteBuf = PowerMockito.mock(ByteBuf.class); + content = "content"; + bytes = content.getBytes(); + result = "result"; + jsonReader = PowerMockito.mock(JsonReader.class); + jsonObject = PowerMockito.mock(JsonObject.class); + jsonBuilderFactory = PowerMockito.mock(JsonBuilderFactory.class); + jsonObjectBuilder = PowerMockito.mock(JsonObjectBuilder.class); + logApiHandler = PowerMockito.spy(new LogApiHandler(httpRequest, byteBuf, bytes)); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(GET))).thenReturn(true); + PowerMockito.when(ApiHandlerHelpers.validateAccessToken(Mockito.any())).thenReturn(true); + PowerMockito.when(Json.createReader(Mockito.any(StringReader.class))).thenReturn(jsonReader); + PowerMockito.when(jsonReader.readObject()).thenReturn(jsonObject); + PowerMockito.when(httpRequest.method()).thenReturn(POST); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST))).thenReturn(true); + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(null); + PowerMockito.when(Json.createBuilderFactory(Mockito.eq(null))).thenReturn(jsonBuilderFactory); + PowerMockito.when(jsonBuilderFactory.createObjectBuilder()).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonObjectBuilder.build()).thenReturn(jsonObject); + PowerMockito.when(jsonObjectBuilder.add(Mockito.anyString(), Mockito.anyString())).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonObject.toString()).thenReturn(result); + PowerMockito.when(LoggingService.microserviceLogInfo(Mockito.anyString(), Mockito.anyString())).thenReturn(true); + PowerMockito.when(LoggingService.microserviceLogWarning(Mockito.anyString(), Mockito.anyString())).thenReturn(true); + } + + @After + public void tearDown() throws Exception { + logApiHandler = null; + jsonObject = null; + httpRequest = null; + byteBuf = null; + result = null; + defaultResponse = null; + jsonReader = null; + bytes = null; + content = null; + jsonBuilderFactory = null; + jsonObjectBuilder = null; + executor.shutdown(); + } + + /** + * Test call when httpMethod is not valid + */ + @Test + public void testCallWhenMethodTypeIsInvalid() { + try { + PowerMockito.when(httpRequest.method()).thenReturn(GET); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, METHOD_NOT_ALLOWED); + PowerMockito.when(ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST))).thenReturn(false); + PowerMockito.when(ApiHandlerHelpers.methodNotAllowedResponse()).thenReturn(defaultResponse); + assertEquals(defaultResponse, logApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateMethod(Mockito.eq(httpRequest), Mockito.eq(POST)); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.methodNotAllowedResponse(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when contentType is not valid + */ + @Test + public void testCallWhenContentTypeIsInvalid() { + try { + String errorMsg = "Incorrect content type text/html"; + PowerMockito.when(ApiHandlerHelpers.validateContentType(Mockito.any(), Mockito.anyString())).thenReturn(errorMsg); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, logApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateContentType(Mockito.eq(httpRequest), Mockito.eq("application/json")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when content doesn't has message, logType and id + */ + @Test + public void testCallWhenRequestDoesnotContainMessage() { + try { + String errorMsg = "Log message parsing error, " + "Logger initialized null"; + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, byteBuf); + PowerMockito.when(ApiHandlerHelpers.badRequestResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, logApiHandler.call()); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateContentType(Mockito.eq(httpRequest), Mockito.eq("application/json")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.badRequestResponse(Mockito.eq(byteBuf), Mockito.eq(errorMsg)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test call when content has message, logType and id + * logType is info + */ + @Test + public void testCallWhenRequestContainMessage() { + try { + PowerMockito.when(jsonObject.containsKey(Mockito.eq("message"))).thenReturn(true); + PowerMockito.when(jsonObject.containsKey(Mockito.eq("type"))).thenReturn(true); + PowerMockito.when(jsonObject.containsKey(Mockito.eq("id"))).thenReturn(true); + PowerMockito.when(jsonObject.getString(Mockito.eq("id"))).thenReturn("id"); + PowerMockito.when(jsonObject.getString(Mockito.eq("message"))).thenReturn("message"); + PowerMockito.when(jsonObject.getString(Mockito.eq("type"))).thenReturn("info"); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, logApiHandler.call()); + Mockito.verify(jsonObject).containsKey(Mockito.eq("message")); + Mockito.verify(jsonObject).containsKey(Mockito.eq("id")); + Mockito.verify(jsonObject).containsKey(Mockito.eq("type")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateContentType(Mockito.eq(httpRequest), Mockito.eq("application/json")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.microserviceLogInfo(Mockito.eq("id"), Mockito.eq("message")); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test call when content has message, logType and id + * logType is info + */ + @Test + public void testCallWhenRequestContainLogTypeSevere() { + try { + PowerMockito.when(jsonObject.containsKey(Mockito.eq("message"))).thenReturn(true); + PowerMockito.when(jsonObject.containsKey(Mockito.eq("type"))).thenReturn(true); + PowerMockito.when(jsonObject.containsKey(Mockito.eq("id"))).thenReturn(true); + PowerMockito.when(jsonObject.getString(Mockito.eq("id"))).thenReturn("id"); + PowerMockito.when(jsonObject.getString(Mockito.eq("message"))).thenReturn("message"); + PowerMockito.when(jsonObject.getString(Mockito.eq("type"))).thenReturn("severe"); + defaultResponse = new DefaultFullHttpResponse(HTTP_1_1, OK, byteBuf); + PowerMockito.when(ApiHandlerHelpers.successResponse(Mockito.any(), Mockito.anyString())).thenReturn(defaultResponse); + assertEquals(defaultResponse, logApiHandler.call()); + Mockito.verify(jsonObject).containsKey(Mockito.eq("message")); + Mockito.verify(jsonObject).containsKey(Mockito.eq("id")); + Mockito.verify(jsonObject).containsKey(Mockito.eq("type")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.validateContentType(Mockito.eq(httpRequest), Mockito.eq("application/json")); + PowerMockito.verifyStatic(ApiHandlerHelpers.class); + ApiHandlerHelpers.successResponse(Mockito.eq(byteBuf), Mockito.eq(result)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.microserviceLogWarning(Mockito.eq("id"), Mockito.eq("message")); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/MessageCallbackTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/MessageCallbackTest.java new file mode 100644 index 00000000..865971d0 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/MessageCallbackTest.java @@ -0,0 +1,63 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.local_api; + +import org.eclipse.iofog.message_bus.Message; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessageCallback.class, MessageWebsocketHandler.class, Message.class}) +public class MessageCallbackTest { + private MessageCallback messageCallback; + private String name; + private MessageWebsocketHandler messageWebsocketHandler; + private Message message; + + @Before + public void setUp() throws Exception { + name = "message"; + message = PowerMockito.mock(Message.class); + messageWebsocketHandler = PowerMockito.mock(MessageWebsocketHandler.class); + messageCallback = PowerMockito.spy(new MessageCallback(name)); + PowerMockito.whenNew(MessageWebsocketHandler.class).withNoArguments().thenReturn(messageWebsocketHandler); + } + + @After + public void tearDown() throws Exception { + name = null; + messageCallback = null; + message = null; + messageWebsocketHandler = null; + } + + /** + * Test sendRealtimeMessage + */ + @Test + public void testSendRealtimeMessage() { + messageCallback.sendRealtimeMessage(message); + Mockito.verify(messageWebsocketHandler).sendRealTimeMessage(Mockito.eq(name), Mockito.eq(message)); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/IOMessageListenerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/IOMessageListenerTest.java new file mode 100644 index 00000000..4ed8363d --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/IOMessageListenerTest.java @@ -0,0 +1,98 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.local_api.MessageCallback; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.jms.JMSException; +import javax.jms.TextMessage; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({IOMessageListener.class, MessageCallback.class, TextMessage.class, Message.class, LoggingService.class}) +public class IOMessageListenerTest { + private IOMessageListener ioMessageListener; + private MessageCallback messageCallback; + private TextMessage textMessage; + private Message message; + private String MODULE_NAME = "MessageListener"; + + @Before + public void setUp() throws Exception { + MODULE_NAME = "MessageListener"; + messageCallback = mock(MessageCallback.class); + textMessage = mock(TextMessage.class); + message = mock(Message.class); + mockStatic(LoggingService.class); + ioMessageListener = spy(new IOMessageListener(messageCallback)); + doNothing().when(textMessage).acknowledge(); + PowerMockito.when(textMessage.getText()).thenReturn("{}"); + PowerMockito.whenNew(Message.class).withArguments(anyString()).thenReturn(message); + PowerMockito.doNothing().when(messageCallback).sendRealtimeMessage(any(Message.class)); + } + + @After + public void tearDown() throws Exception { + reset(messageCallback); + MODULE_NAME = null; + } + + /** + * Test onMessage success scenario + */ + @Test + public void testOnMessage() { + try { + ioMessageListener.onMessage(textMessage); + verify(textMessage).acknowledge(); + verify(messageCallback).sendRealtimeMessage(any()); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Start acknowledging message onMessage"); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Finish acknowledging message onMessage"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test onMessage error scenario + */ + @Test + public void throwsExceptionOnMessage() { + try { + PowerMockito.doThrow(mock(JMSException.class)).when(textMessage).acknowledge(); + ioMessageListener.onMessage(textMessage); + LoggingService.logError(eq(MODULE_NAME), eq("Error acknowledging message"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageArchiveTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageArchiveTest.java new file mode 100644 index 00000000..52a04ee9 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageArchiveTest.java @@ -0,0 +1,188 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.*; +import java.lang.reflect.Method; +import java.nio.file.*; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.List; + +import static java.lang.System.currentTimeMillis; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.reset; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + * + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessageArchive.class, Configuration.class, LoggingService.class, File.class, + RandomAccessFile.class, Runtime.class}) +public class MessageArchiveTest { + private String MODULE_NAME; + private MessageArchive messageArchive; + private long timestamp; + private String message; + private File file; + private RandomAccessFile randomAccessFile; + private Runtime runtime; + private File[] files; + + @Before + public void setUp() throws Exception { + MODULE_NAME = "MessageArchive"; + timestamp = currentTimeMillis(); + message = "message"; + mockStatic(Configuration.class); + mockStatic(LoggingService.class); + mockStatic(Runtime.class); + when(Configuration.getDiskDirectory()).thenReturn("dir/"); + file = mock(File.class); + randomAccessFile = mock(RandomAccessFile.class); + runtime = mock(Runtime.class); + files = new File[1]; + files[0] = spy(new File("message1234545.idx")); + when(file.listFiles(any(FilenameFilter.class))).thenReturn(files); + when(files[0].isFile()).thenReturn(true); + when(file.getName()).thenReturn("message.idx"); + PowerMockito.whenNew(File.class).withParameterTypes(String.class).withArguments(any()).thenReturn(file); + PowerMockito.whenNew(RandomAccessFile.class).withParameterTypes(File.class, String.class) + .withArguments(any(), anyString()).thenReturn(randomAccessFile); + PowerMockito.when(Runtime.getRuntime()).thenReturn(runtime); + PowerMockito.when(runtime.maxMemory()).thenReturn(1048576460l * 32); + PowerMockito.when(runtime.totalMemory()).thenReturn(1l); + PowerMockito.when(runtime.freeMemory()).thenReturn(1l); + messageArchive = spy(new MessageArchive("message.idx")); + } + + @After + public void tearDown() throws Exception { + MODULE_NAME = null; + files = null; + reset(messageArchive, randomAccessFile); + deleteDirectory("dir/messages/archive"); + } + + void deleteDirectory(String directoryFilePath) throws IOException { + Path directory = Paths.get(directoryFilePath); + + if (Files.exists(directory)) + { + Files.walkFileTree(directory, new SimpleFileVisitor() + { + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes basicFileAttributes) throws IOException + { + Files.delete(path); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path directory, IOException ioException) throws IOException + { + Files.delete(directory); + return FileVisitResult.CONTINUE; + } + }); + } + } + /** + * Test save + */ + @Test + public void testSave() { + try { + messageArchive.save(message.getBytes(UTF_8),timestamp); + PowerMockito.verifyPrivate(messageArchive).invoke("openFiles", anyLong()); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).seek(anyLong()); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).length(); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).getFilePointer(); + } catch (Exception e) { + fail("This shall never happen"); + } + } + + /** + * Test close + */ + @Test + public void testClose() { + try { + messageArchive.save(message.getBytes(UTF_8),timestamp); + messageArchive.close(); + PowerMockito.verifyPrivate(messageArchive).invoke("openFiles", anyLong()); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).seek(anyLong()); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).length(); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).getFilePointer(); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).close(); + } catch (Exception e) { + fail("This shall never happen"); + } + } + + /** + * Test messageQuery + */ + @Test + public void testMessageQueryWithMessages() { + try{ + when(files[0].isFile()).thenReturn(true); + when(files[0].getName()).thenReturn("message1234545.idx"); + when(randomAccessFile.getFilePointer()).thenReturn(1l); + when(randomAccessFile.length()).thenReturn(10l); + when(randomAccessFile.read(any(byte[].class), anyInt(), anyInt())).thenReturn(1); + when(randomAccessFile.readLong()).thenReturn(1l); + whenNew(File.class).withParameterTypes(String.class).withArguments(any()).thenReturn(file); + whenNew(RandomAccessFile.class).withParameterTypes(File.class, String.class) + .withArguments(any(), anyString()).thenReturn(randomAccessFile); + messageArchive.messageQuery(1, 50); + Mockito.verify(file, Mockito.atLeastOnce()).listFiles(any(FilenameFilter.class)); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).getFilePointer(); + Mockito.verify(randomAccessFile, Mockito.atLeastOnce()).read(any(byte[].class), anyInt(), anyInt()); + } catch (Exception e){ + fail("This shall never happen"); + } + } + + /** + * Test getDataSize + */ + @Test + public void testGetDataSize() { + try { + byte[] bytes = new byte[33]; + Method method = MessageArchive.class.getDeclaredMethod("getDataSize", byte[].class); + method.setAccessible(true); + assertEquals(0, (int) method.invoke(messageArchive, bytes)); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusServerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusServerTest.java new file mode 100644 index 00000000..774075cd --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusServerTest.java @@ -0,0 +1,305 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.apache.qpid.jms.JmsConnectionFactory; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.jms.*; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + * + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessageBusServer.class, Configuration.class, LoggingService.class,}) +public class MessageBusServerTest { + private final List receivers = new ArrayList() { { add("ABCD"); add("EFGH"); } }; + private MessageBusServer messageBusServer; + private String MODULE_NAME; + private Session session; + private Connection connection; + private ConnectionFactory connectionFactory; + private MessageProducer messageProducer; + private MessageConsumer messageConsumer; + private TextMessage textMessage; + private Queue queue; + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Message Bus Server"; + messageBusServer = spy(new MessageBusServer()); + session = mock(Session.class); + connection = mock(Connection.class); + messageProducer = mock(MessageProducer.class); + messageConsumer = mock(MessageConsumer.class); + textMessage = mock(TextMessage.class); + queue = mock(Queue.class); + + mockStatic(Configuration.class); + mockStatic(LoggingService.class); + + connectionFactory = mock(JmsConnectionFactory.class); + PowerMockito.when(connectionFactory.createConnection()).thenReturn(connection); + PowerMockito.whenNew(JmsConnectionFactory.class).withArguments(anyString()).thenReturn((JmsConnectionFactory) connectionFactory); + PowerMockito.when(Configuration.getMemoryLimit()).thenReturn(1.0f); + PowerMockito.when(Configuration.getDiskDirectory()).thenReturn("dir/"); + PowerMockito.when(connection.createSession(anyBoolean(), anyInt())).thenReturn(session); + PowerMockito.when(session.createTextMessage(any())).thenReturn(textMessage); + PowerMockito.when(session.createQueue(any())).thenReturn(queue); + PowerMockito.when(session.createConsumer(any())).thenReturn(messageConsumer); + PowerMockito.when(session.createProducer(any())).thenReturn(messageProducer); + } + + @After + public void tearDown() throws Exception { + messageBusServer.stopServer(); + reset(messageBusServer); + reset(connection); + reset(connectionFactory); + reset(queue); + reset(session); + MODULE_NAME = null; + } + + /** + * Test start server + */ + @Test + public void testStartServer() { + try { + messageBusServer.startServer("localhost", 5672); + Mockito.verify(connectionFactory, Mockito.atLeastOnce()).createConnection(); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Starting server"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished starting server"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test initialize + */ + @Test + public void testInitialize() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + Mockito.verify(connection, Mockito.atLeastOnce()).createSession(false, Session.CLIENT_ACKNOWLEDGE); + Mockito.verify(connection, Mockito.atLeastOnce()).start(); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Starting initialization"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Finished initialization"); + } catch (Exception e) { + fail("This should not happen"); + } + + } + + + /** + * Test stop server when all consumers and producers are not running + */ + @Test + public void testStopServerWhenNothingIsRunning() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + messageBusServer.stopServer(); + Mockito.verify(session, Mockito.atLeastOnce()).close(); + Mockito.verify(connection, Mockito.atLeastOnce()).close(); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "stopping server started"); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "stopped server"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test stop server when consumers and producers present + */ + @Test + public void testStopServerWhenProducerAndConsumerAreRunning() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + messageBusServer.createConsumer("consumer1"); + messageBusServer.createConsumer("consumer2"); + messageBusServer.createProducer("producer", receivers); + messageBusServer.stopServer(); + Mockito.verify(session, Mockito.atLeastOnce()).close(); + Mockito.verify(connection, Mockito.atLeastOnce()).close(); + Mockito.verify(messageConsumer, Mockito.atLeast(2)).close(); + Mockito.verify(messageProducer, Mockito.atLeast(2)).close(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test stop server when server is running + * consumer and producer throws Exception when closing + */ + @Test + public void throwsExceptionWhenStoppingProducerAndConsumer() { + try { + PowerMockito.doThrow(mock(JMSException.class)).when(messageProducer).close(); + PowerMockito.doThrow(mock(JMSException.class)).when(messageConsumer).close(); + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + messageBusServer.createConsumer("consumer"); + messageBusServer.createProducer("producer", receivers); + messageBusServer.stopServer(); + Mockito.verify(messageConsumer, Mockito.atLeastOnce()).close(); + Mockito.verify(messageProducer, Mockito.atLeast(2)).close(); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Error closing consumer"), any()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logError(eq(MODULE_NAME), eq("Error closing producer"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test createConsumer and getConsumer + */ + @Test + public void testCreateConsumerAndGetConsumer() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + messageBusServer.createConsumer("consumer"); + assertEquals(messageConsumer, messageBusServer.getConsumer("consumer")); + PowerMockito.verifyStatic(LoggingService.class, times(1)); + LoggingService.logDebug(MODULE_NAME, "Starting create consumer"); + PowerMockito.verifyStatic(LoggingService.class, times(1)); + LoggingService.logDebug(MODULE_NAME, "Finished create consumer"); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test removeConsumer when consumer is present + * When getConsumer the removed consumer, MessageBusServer creates the new consumer + */ + @Test + public void testRemoveConsumerWhenConsumerIsPresent() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + messageBusServer.createConsumer("consumer"); + assertEquals(messageConsumer, messageBusServer.getConsumer("consumer")); + messageBusServer.removeConsumer("consumer"); + assertEquals(messageConsumer, messageBusServer.getConsumer("consumer")); + Mockito.verify(messageBusServer, times(2)).createConsumer(anyString()); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test removeConsumer is called with random Consumer. + * GetConsumer creates a new consumer in the map if not present + */ + @Test + public void testRemoveConsumerWhenConsumerIsNotPresent() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + messageBusServer.createConsumer("consumer"); + assertEquals(messageConsumer, messageBusServer.getConsumer("consumer")); + messageBusServer.removeConsumer("randomConsumer"); + assertEquals(messageConsumer, messageBusServer.getConsumer("randomConsumer")); + Mockito.verify(messageBusServer, times(2)).createConsumer(anyString()); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * test CreateProducer and getProducer + * the same publisher + */ + @Test + public void testCreateProducerAndGetProducer() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + messageBusServer.createProducer("producer", receivers); + Mockito.verify(messageBusServer).createProducer(anyString(), any()); + Mockito.verify(session, atLeastOnce()).createProducer(any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * test remove and getProducer + * the different publisher. GetProducer creates a new publisher if not present + */ + @Test + public void testRemoveProducerAndThenRemoveProducerTheSamePublisher() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + messageBusServer.createProducer("producer", receivers); + Mockito.verify(messageBusServer).createProducer(anyString(), any()); + Mockito.verify(session, atLeastOnce()).createProducer(any()); + messageBusServer.removeProducer("producer"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test create message is equal to mock session + */ + @Test + public void getSession() { + try { + messageBusServer.startServer("localhost", 5672); + messageBusServer.initialize(); + assertEquals(textMessage, MessageBusServer.createMessage(anyString())); + Mockito.verify(session, atLeastOnce()).createTextMessage(anyString()); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusStatusTest.java new file mode 100644 index 00000000..4e00f990 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusStatusTest.java @@ -0,0 +1,124 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.reset; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + * + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessageBusStatus.class}) +public class MessageBusStatusTest { + private MessageBusStatus messageBusStatus; + private long processedMessages; + private float averageSpeed; + + @Before + public void setUp() throws Exception { + messageBusStatus = spy(new MessageBusStatus()); + processedMessages = 1000L; + averageSpeed = 1000f; + + } + + @After + public void tearDown() throws Exception { + reset(messageBusStatus); + processedMessages = 0; + averageSpeed = 0; + } + + /** + * Test getProcessedMessages + */ + @Test + public void testGetProcessedMessages() { + assertEquals(0, messageBusStatus.getProcessedMessages()); + } + + /** + * Test increasePublishedMessagesPerMicroservice + */ + @Test + public void testIncreasePublishedMessagesPerMicroservice() { + assertEquals(1, messageBusStatus.increasePublishedMessagesPerMicroservice(null).getProcessedMessages()); + assertEquals(2, messageBusStatus.increasePublishedMessagesPerMicroservice("microservice").getProcessedMessages()); + } + + /** + * Test getPublishedMessagesPerMicroservice + */ + @Test + public void testGetPublishedMessagesPerMicroservice() { + assertEquals(0, messageBusStatus.getPublishedMessagesPerMicroservice().size()); + assertEquals(1, messageBusStatus.increasePublishedMessagesPerMicroservice("microservice") + .getPublishedMessagesPerMicroservice().size()); + } + + /** + * Test getPublishedMessagesPerMicroservice of specific microservice + */ + @Test + public void testGetPublishedMessagesPerMicroserviceOfSpecificMicroservice() { + assertEquals(1, messageBusStatus. + increasePublishedMessagesPerMicroservice(null) + .getPublishedMessagesPerMicroservice(null), 0); + assertEquals(1, messageBusStatus + .increasePublishedMessagesPerMicroservice("microservice"). + getPublishedMessagesPerMicroservice("microservice"), 0); + } + + /** + * Test get and set averageSpeed + */ + @Test + public void testGetAndSetAverageSpeed() { + assertEquals(0, messageBusStatus.getAverageSpeed(), 0); + assertEquals(averageSpeed, messageBusStatus.setAverageSpeed(averageSpeed).getAverageSpeed(), 0); + } + + /** + * Test removePublishedMessagesPerMicroservice + */ + @Test + public void testRemovePublishedMessagesPerMicroservice() { + messageBusStatus.removePublishedMessagesPerMicroservice(null); + messageBusStatus.increasePublishedMessagesPerMicroservice("microservice"); + assertEquals(1, messageBusStatus.getPublishedMessagesPerMicroservice().size()); + messageBusStatus.removePublishedMessagesPerMicroservice("microservice"); + assertEquals(0, messageBusStatus.getPublishedMessagesPerMicroservice().size()); + } + + /** + * Test GetJsonPublishedMessagesPerMicroservice when microservices are published + * and not published + */ + @Test + public void testGetJsonPublishedMessagesPerMicroservice() { + assertFalse(messageBusStatus.getJsonPublishedMessagesPerMicroservice().contains("id")); + messageBusStatus.increasePublishedMessagesPerMicroservice("microservice"); + assertTrue(messageBusStatus.getJsonPublishedMessagesPerMicroservice().contains("id")); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusTest.java new file mode 100644 index 00000000..16cc7d40 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusTest.java @@ -0,0 +1,355 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.microservice.Route; +import org.eclipse.iofog.resource_consumption_manager.ResourceConsumptionManager; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.supervisor.SupervisorStatus; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.Orchestrator; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.whenNew; + +/** + * @author nehanaithani + * + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessageBus.class, MicroserviceManager.class, MessageBusServer.class, MessageProducer.class, + LoggingService.class, MessageReceiver.class, MessageConsumer.class ,MessagePublisher.class, + StatusReporter.class, MessageBusStatus.class, SupervisorStatus.class, Thread.class, Orchestrator.class}) +public class MessageBusTest { + private MessageBus messageBus; + private MicroserviceManager microserviceManager; + private MessageBusServer messageBusServer; + private Thread speedThread; + private Route route; + private String MODULE_NAME; + private String receiverValue; + private MessageReceiver messageReceiver; + private MessageConsumer messageConsumer; + private MessagePublisher messagePublisher; + private MessageBusStatus messageBusStatus; + private SupervisorStatus supervisorStatus; + private Map publishedMessagesPerMicroservice; + private Orchestrator orchestrator = null; + Map mapRoutes; + List receivers; + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Message Bus"; + messageBus = spy(MessageBus.class); + setMock(messageBus); + PowerMockito.mockStatic(MicroserviceManager.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(StatusReporter.class); + microserviceManager = mock(MicroserviceManager.class); + messageBusServer = mock(MessageBusServer.class); + messageReceiver = mock(MessageReceiver.class); + messageConsumer = mock(MessageConsumer.class); + messagePublisher = mock(MessagePublisher.class); + messageBusStatus = mock(MessageBusStatus.class); + supervisorStatus = mock(SupervisorStatus.class); + PowerMockito.when(MicroserviceManager.getInstance()).thenReturn(microserviceManager); + PowerMockito.whenNew(MessageBusServer.class).withNoArguments().thenReturn(messageBusServer); + PowerMockito.whenNew(MessageReceiver.class).withArguments(anyString(), any(MessageConsumer.class)) + .thenReturn(messageReceiver); + PowerMockito.whenNew(MessagePublisher.class).withArguments(anyString(), any(Route.class), any(MessageProducer.class)) + .thenReturn(messagePublisher); + route = new Route(); + receivers = new ArrayList<>(); + receiverValue = "1"; + receivers.add(receiverValue); + receivers.add("2"); + receivers.add("3"); + route.setReceivers(receivers); + mapRoutes = new HashMap<>(); + mapRoutes.put("1", route); + publishedMessagesPerMicroservice = new HashMap<>(); + publishedMessagesPerMicroservice.put("1", 100l); + PowerMockito.when(microserviceManager.getRoutes()).thenReturn(mapRoutes); + PowerMockito.when(messageBusStatus.getPublishedMessagesPerMicroservice()).thenReturn(publishedMessagesPerMicroservice); + PowerMockito.when(messageBusServer.getConsumer(any())).thenReturn(mock(MessageConsumer.class)); + PowerMockito.when(messageBusServer.getProducer(any(), any())).thenReturn(mock(List.class)); + PowerMockito.when(messageBusServer.isConnected()).thenReturn(true); + PowerMockito.doNothing().when(messageReceiver).enableRealTimeReceiving(); + PowerMockito.doNothing().when(messageReceiver).disableRealTimeReceiving(); + PowerMockito.when(StatusReporter.getMessageBusStatus()).thenReturn(messageBusStatus); + PowerMockito.when(StatusReporter.setMessageBusStatus()).thenReturn(messageBusStatus); + PowerMockito.when(StatusReporter.setSupervisorStatus()).thenReturn(supervisorStatus); + orchestrator = mock(Orchestrator.class); + whenNew(Orchestrator.class).withNoArguments().thenReturn(orchestrator); + + MessagePublisher messagePublisher = mock(MessagePublisher.class); + whenNew(MessagePublisher.class).withAnyArguments().thenReturn(messagePublisher); + } + + @After + public void tearDown() throws Exception { + MODULE_NAME = null; + receiverValue = null; + Field instance = ResourceConsumptionManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(null, null); + mapRoutes = null; + publishedMessagesPerMicroservice = null; + receivers = null; + reset(messageBus); + reset(messageBusServer); + reset(messagePublisher); + reset(messageConsumer); + reset(messageReceiver); + reset(microserviceManager); + } + /** + * Set a mock to the {@link MessageBus} instance + * Throws {@link RuntimeException} in case if reflection failed, see a {@link Field#set(Object, Object)} method description. + * @param mock the mock to be inserted to a class + */ + private void setMock(MessageBus mock) { + try { + Field instance = MessageBus.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(instance, mock); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Asserts module index of MessageBus is equal to constant value + */ + @Test + public void testGetModuleIndex() { + assertEquals(Constants.MESSAGE_BUS, messageBus.getModuleIndex()); + } + + /** + * Asserts module name of messageBus is equal to constant value + */ + @Test + public void testGetModuleName() { + assertEquals("Message Bus", messageBus.getModuleName()); + } + + /** + * Assert mock is same as MessageBus.getInstance() + */ + @Test + public void testGetInstance() { + assertSame(messageBus, MessageBus.getInstance()); + } + + /** + * Test enableRealTimeReceiving when receiver passed is null + */ + @Test (timeout = 100000L) + public void testEnableRealTimeReceivingWhenReceiverPassedIsNull() { + initiateMockStart(); + messageBus.enableRealTimeReceiving(null); + Mockito.verify(messageReceiver, never()).enableRealTimeReceiving(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME,"Starting enable real time receiving"); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.logDebug(MODULE_NAME,"Finishing enable real time receiving"); + } + + /** + * Test enableRealTimeReceiving when receiver is not found + */ + @Test (timeout = 100000L) + public void testEnableRealTimeReceivingWhenReceiverIsNotFound() { + initiateMockStart(); + messageBus.enableRealTimeReceiving("receiver"); + Mockito.verify(messageReceiver, never()).enableRealTimeReceiving(); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.logInfo(MODULE_NAME,"Finishing enable real time receiving"); + } + + /** + * Test disableRealTimeReceiving when receiver passed is null + */ + @Test (timeout = 100000L) + public void testDisableRealTimeReceivingWhenReceiverPassedIsNull() { + initiateMockStart(); + messageBus.disableRealTimeReceiving(null); + Mockito.verify(messageReceiver, never()).disableRealTimeReceiving(); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.logInfo(MODULE_NAME,"Finishing disable real time receiving"); + } + + /** + * Test disableRealTimeReceiving when receiver is not null + */ + @Test (timeout = 100000L) + public void testDisableRealTimeReceivingWhenReceiverPassedIsNotFound() { + initiateMockStart(); + messageBus.disableRealTimeReceiving("receiver"); + Mockito.verify(messageReceiver, never()).disableRealTimeReceiving(); + } + + /** + * Test update + */ + @Test + public void testUpdate() { + initiateMockStart(); + try { + messageBus.update(); + Mockito.verify(microserviceManager, atLeastOnce()).getLatestMicroservices(); + } catch (Exception e) { + fail("Shouldn't have happened"); + } + } + + @Test (timeout = 100000L) + public void testInstanceConfigUpdated() { + initiateMockStart(); + messageBus.instanceConfigUpdated(); + } + + /** + * Test start + */ + @Test (timeout = 100000L) + public void testStart() { + try { + initiateMockStart(); + Mockito.verify(messageBusServer, atLeastOnce()).startServer("localhost", 5672); + Mockito.verify(messageBusServer, atLeastOnce()).initialize(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME,"STARTING MESSAGE BUS SERVER"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME,"MESSAGE BUS SERVER STARTED"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test stop + */ + @Test (timeout = 100000L) + public void testStop() { + try { + initiateMockStart(); + messageBus.stop(); + Mockito.verify(messageBusServer, atLeastOnce()).stopServer(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME,"Start closing receivers and publishers and stops ActiveMQ server"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME,"Finished closing receivers and publishers and stops ActiveMQ server"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test stop when messageBusServer.stopServer() throws exception + */ + @Test (timeout = 100000L) + public void throwsExceptionWhenMessageServerStopIsCalled() { + try { + PowerMockito.doThrow(mock(Exception.class)).when(messageBusServer).stopServer(); + initiateMockStart(); + messageBus.stop(); + Mockito.verify(messageBusServer, atLeastOnce()).stopServer(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error closing receivers and publishers and stops ActiveMQ server"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test get receiver + */ + @Test (timeout = 100000L) + public void testGetReceiver() { + initiateMockStart(); + assertNull(messageBus.getReceiver(receiverValue)); + } + + /** + * Test getNextId + */ + @Test + public void testGetNextId() { + initiateMockStart(); + assertNotNull(messageBus.getNextId()); + } + + /** + * Test getRoutes + */ + @Test (timeout = 100000L) + public void testGetRoutes() { + initiateMockStart(); + assertNotNull(messageBus.getRoutes()); + assertEquals(mapRoutes, messageBus.getRoutes()); + } + + /** + * Helper method + */ + public void initiateMockStart() { + speedThread = mock(Thread.class); + Thread startThread = mock(Thread.class); + try { + PowerMockito.whenNew(Thread.class).withParameterTypes(Runnable.class).withArguments(Mockito.any(Runnable.class)).thenReturn(startThread); + PowerMockito.whenNew(Thread.class).withParameterTypes(Runnable.class, String.class).withArguments(Mockito.any(Runnable.class), + anyString()).thenReturn(speedThread); + PowerMockito.doNothing().when(speedThread).start(); + PowerMockito.doNothing().when(startThread).start(); + messageBus.start(); + + JsonObjectBuilder jsonObjectBuilder = Json.createObjectBuilder(); + JsonObject jsonObject = jsonObjectBuilder + .add("routerHost", "localhost") + .add("routerPort", 5672).build(); + when(orchestrator.request(any(), any(), any(), any())).thenReturn(jsonObject); + + messageBus.startServer(); + } catch (Exception e) { + fail("this should not happen"); + } + + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusUtilTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusUtilTest.java new file mode 100644 index 00000000..3bf031c8 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusUtilTest.java @@ -0,0 +1,291 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.microservice.Route; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static java.lang.System.currentTimeMillis; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; + +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessageBusUtil.class, MessageBus.class, StatusReporter.class, MessageBusStatus.class, Message.class, LoggingService.class, MessagePublisher.class, MessageReceiver.class, Route.class}) +public class MessageBusUtilTest { + private MessageBusUtil messageBusUtil; + private MessageBus messageBus; + private MessageBusStatus messageBusStatus; + private Message message; + private String MODULE_NAME; + private MessagePublisher messagePublisher; + private MessageReceiver messageReceiver; + private Route route; + private List messages; + private List receivers; + private Map routes; + + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Message Bus Util"; + messageBus = mock(MessageBus.class); + message = mock(Message.class); + messagePublisher = mock(MessagePublisher.class); + messageReceiver = mock(MessageReceiver.class); + route = mock(Route.class); + mockStatic(LoggingService.class); + mockStatic(MessageBus.class); + mockStatic(StatusReporter.class); + messages = mock(ArrayList.class); + receivers = mock(ArrayList.class); + routes = mock(HashMap.class); + messageBusStatus = mock(MessageBusStatus.class); + PowerMockito.when(MessageBus.getInstance()).thenReturn(messageBus); + PowerMockito.when(messageBus.getReceiver(any())).thenReturn(messageReceiver); + PowerMockito.when(messageBus.getPublisher(any())).thenReturn(messagePublisher); + PowerMockito.when(messageBus.getRoutes()).thenReturn(routes); + PowerMockito.when(routes.get(any())).thenReturn(route); + PowerMockito.when(route.getReceivers()).thenReturn(receivers); + PowerMockito.when(messageReceiver.getMessages()).thenReturn(messages); + PowerMockito.when(StatusReporter.setMessageBusStatus()).thenReturn(messageBusStatus); + PowerMockito.when(receivers.contains(eq("receiver"))).thenReturn(true); + messageBusUtil = spy(new MessageBusUtil()); + } + + @After + public void tearDown() throws Exception { + MODULE_NAME = null; + Mockito.reset(messageBusUtil); + Mockito.reset(messageBusStatus); + Mockito.reset(message); + Mockito.reset(messageBus); + Mockito.reset(messagePublisher); + Mockito.reset(messageReceiver); + Mockito.reset(route); + } + + /** + * Test publishMessage when messageBus.getPublisher is null + */ + @Test + public void testPublishMessageWhenPublisherIsNull() { + try { + PowerMockito.when(messageBus.getPublisher(message.getPublisher())).thenReturn(null); + messageBusUtil.publishMessage(message); + Mockito.verify(messageBus).getPublisher(any()); + Mockito.verify(messagePublisher, Mockito.never()).publish(any(Message.class)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Start publish message"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Finishing publish message"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test publishMessage when messageBus.getPublisher is not null + */ + @Test + public void testPublishMessageWhenPublisherIsNotNull() { + try { + PowerMockito.when(messageBus.getPublisher(message.getPublisher())).thenReturn(messagePublisher); + messageBusUtil.publishMessage(message); + Mockito.verify(messageBus).getPublisher(any()); + Mockito.verify(messagePublisher).publish(any(Message.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test publishMessage + * publisher throws Exception + */ + @Test + public void throwsExceptionWhenPublisherIsCalled() { + PowerMockito.when(messageBus.getPublisher(message.getPublisher())).thenReturn(messagePublisher); + PowerMockito.when(messagePublisher.getName()).thenReturn("MP"); + try { + PowerMockito.doThrow(mock(Exception.class)).when(messagePublisher).publish(any()); + messageBusUtil.publishMessage(message); + Mockito.verify(messageBus).getPublisher(any()); + Mockito.verify(messagePublisher).publish(any(Message.class)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Unable to send message : Message Publisher (MP)"), any()); + } catch (Exception e) { + fail("This Should not happen"); + } + } + + /** + * Test getMessages MessageReceiver is null + */ + @Test + public void testGetMessagesWhenMessageReceiverIsNull() { + try { + PowerMockito.when(messageBus.getReceiver(any())).thenReturn(null); + assertEquals(0, messageBusUtil.getMessages("receiver").size()); + Mockito.verify(messageBus).getReceiver(any()); + Mockito.verify(messageReceiver, Mockito.never()).getMessages(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Starting get message"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Finishing get message"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getMessages MessageReceiver is not null + */ + @Test + public void testGetMessagesWhenMessageReceiverIsNotNull() { + try { + assertEquals(messages, messageBusUtil.getMessages("receiver")); + Mockito.verify(messageBus).getReceiver(any()); + Mockito.verify(messageReceiver).getMessages(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getMessages when MessagePublisher throws exception + */ + @Test + public void throwsExceptionWhenMessagePublisherIsCalledInGetMessages() { + try { + PowerMockito.doThrow(mock(Exception.class)).when(messageReceiver).getMessages(); + assertEquals(0, messageBusUtil.getMessages("receiver").size()); + Mockito.verify(messageBus).getReceiver(any()); + Mockito.verify(messageReceiver).getMessages(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("unable to receive messages : Message Receiver (receiver)"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + + } + + /** + * Test MessageQuery when route is null + */ + @Test + public void testMessageQueryWhenRouteIsNull() { + try { + PowerMockito.when(routes.get(any())).thenReturn(null); + assertNull(messageBusUtil.messageQuery("publisher", "receiver", currentTimeMillis(), 100l)); + Mockito.verify(messageBus).getRoutes(); + Mockito.verify(messageBus, Mockito.never()).getPublisher(any()); + Mockito.verify(messagePublisher, Mockito.never()).messageQuery(anyLong(), anyLong()); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Starting message query"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logDebug(MODULE_NAME, "Finishing message query"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test MessageQuery when input parameter to is less than from + */ + @Test + public void testMessageQueryWhenInputParamToIsLessThanFrom() { + try { + assertNull(messageBusUtil.messageQuery("publisher", "receiver", currentTimeMillis(), 100l)); + Mockito.verify(messageBus).getRoutes(); + Mockito.verify(messageBus, Mockito.never()).getPublisher(any()); + Mockito.verify(messagePublisher, Mockito.never()).messageQuery(anyLong(), anyLong()); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Starting message query"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logDebug(MODULE_NAME, "Finishing message query"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test MessageQuery when route doesn't have receiver + */ + @Test + public void testMessageQueryWhenRouteDoseNotHaveReceiver() { + try { + PowerMockito.when(receivers.contains(eq("receiver"))).thenReturn(false); + assertNull(messageBusUtil.messageQuery("publisher", "receiver", 100l, currentTimeMillis())); + Mockito.verify(messageBus).getRoutes(); + Mockito.verify(route).getReceivers(); + Mockito.verify(messageBus, Mockito.never()).getPublisher(any()); + Mockito.verify(messagePublisher, Mockito.never()).messageQuery(anyLong(), anyLong()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test MessageQuery when route have receiver + * messageBus doesn't have publisher + */ + @Test + public void testMessageQueryWhenRouteHaveReceiverButNotPublisher() { + try { + PowerMockito.when(messageBus.getPublisher(any())).thenReturn(null); + assertNull(messageBusUtil.messageQuery("publisher", "receiver", 100l, currentTimeMillis())); + Mockito.verify(messageBus).getRoutes(); + Mockito.verify(route).getReceivers(); + Mockito.verify(messageBus).getPublisher(any()); + Mockito.verify(messagePublisher, Mockito.never()).messageQuery(anyLong(), anyLong()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test MessageQuery when route have receiver + * messageBus have publisher + */ + @Test + public void testMessageQueryWhenRouteHaveReceiverAndPublisher() { + try { + PowerMockito.when(messagePublisher.messageQuery(anyLong(), anyLong())).thenReturn(messages); + assertEquals(messages, messageBusUtil.messageQuery("publisher", "receiver", 100l, currentTimeMillis())); + Mockito.verify(messageBus).getRoutes(); + Mockito.verify(messageBus).getPublisher(any()); + Mockito.verify(messagePublisher).messageQuery(anyLong(), anyLong()); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageIdGeneratorTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageIdGeneratorTest.java new file mode 100644 index 00000000..87197816 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageIdGeneratorTest.java @@ -0,0 +1,71 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static java.lang.System.currentTimeMillis; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessageIdGenerator.class}) +public class MessageIdGeneratorTest { + private MessageIdGenerator messageIdGenerator; + + @Before + public void setUp() throws Exception { + messageIdGenerator = spy(new MessageIdGenerator()); + } + + @After + public void tearDown() throws Exception { + reset(messageIdGenerator); + } + + /** + * Test generate + */ + @Test + public void testGenerate() { + try { + assertNotNull("Message Id not null", + messageIdGenerator.generate(currentTimeMillis())); + PowerMockito.verifyPrivate(messageIdGenerator, times(2)) + .invoke("toBase58", anyLong()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getNextId + */ + @Test + public void testGetNextId() { + assertNotNull("Next Id", messageIdGenerator.getNextId()); + assertFalse(messageIdGenerator.getNextId().contains("?")); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessagePublisherTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessagePublisherTest.java new file mode 100644 index 00000000..8879ac56 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessagePublisherTest.java @@ -0,0 +1,193 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.microservice.Route; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TextMessage; +import java.util.ArrayList; +import java.util.List; + +import static org.eclipse.iofog.message_bus.MessageBus.MODULE_NAME; +import static org.eclipse.iofog.utils.logging.LoggingService.logError; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.reset; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessagePublisher.class, Route.class, MessageProducer.class, Message.class, + MessageArchive.class, LoggingService.class, MessageBusServer.class, Session.class, + TextMessage.class}) +public class MessagePublisherTest { + private final List messageProducers = new ArrayList<>(); + private MessagePublisher messagePublisher; + private String name; + private Route route; + private Message message; + private MessageArchive messageArchive; + private MessageBusServer messageBusServer; + private TextMessage textMessage; + private byte[] bytes; + private List receivers; + private List messageList; + + @Before + public void setUp() throws Exception { + name = "name"; + bytes = new byte[20]; + route = mock(Route.class); + message = mock(Message.class); + textMessage = mock(TextMessage.class); + messageArchive = mock(MessageArchive.class); + messageBusServer = mock(MessageBusServer.class); + receivers = new ArrayList<>(); + receivers.add("receivers"); + messageList = mock(ArrayList.class); + mockStatic(LoggingService.class); + mockStatic(MessageBusServer.class); + PowerMockito.when(message.getBytes()).thenReturn(bytes); + PowerMockito.when(message.getTimestamp()).thenReturn(System.currentTimeMillis()); + PowerMockito.when(MessageBusServer.createMessage(anyString())).thenReturn(textMessage); + PowerMockito.when(route.getReceivers()).thenReturn(receivers); + PowerMockito.whenNew(MessageArchive.class).withArguments(anyString()).thenReturn(messageArchive); + messagePublisher = spy(new MessagePublisher(name, route, messageProducers)); + PowerMockito.doNothing().when(messageArchive).save(Mockito.any(byte[].class), anyLong()); + PowerMockito.doNothing().when(messageArchive).close(); + PowerMockito.when(messageArchive.messageQuery(anyLong(), anyLong())).thenReturn(messageList); + } + + @After + public void tearDown() throws Exception { + reset(messagePublisher); + reset(route); + } + + /** + * Test getName + */ + @Test + public void TestGetName() { + assertEquals(name, messagePublisher.getName()); + } + + /** + * Test Publish + */ + @Test + public void testPublishWhenMessageIsArchived() { + try { + messagePublisher.publish(message); + Mockito.verify(messageArchive, atLeastOnce()).save(any(byte[].class), anyLong()); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Start publish message :name"); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Finished publish message : name"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test Publish throws exception when messageArchive save is called + */ + @Test + public void throwExceptionWhenArchiveMessageSaveIsCalled() { + try { + PowerMockito.doThrow(mock(Exception.class)).when(messageArchive).save(Mockito.any(byte[].class), anyLong()); + messagePublisher.publish(message); + Mockito.verify(messageArchive, atLeastOnce()).save(any(byte[].class), anyLong()); + verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Message Publisher (name)unable to archive message"), + any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test updateRoute + */ + @Test + public void testUpdateRoute() { + try { + messagePublisher.updateRoute(route, messageProducers); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Updating route"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test close + */ + @Test + public void testClose() { + try { + messagePublisher.close(); + Mockito.verify(messageArchive, atLeastOnce()).close(); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Start closing publish"); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Finished closing publish"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test close throws Exception + */ + @Test + public void throwsExceptionWhenCloseIsCalled() { + try { + PowerMockito.doThrow(mock(RuntimeException.class)).when(messageArchive).close(); + messagePublisher.close(); + Mockito.verify(messageArchive, atLeastOnce()).close(); + verifyStatic(LoggingService.class); + logError(eq(MODULE_NAME), eq("Error closing message archive"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test messageQuery + */ + @Test + public void messageQuery() { + try { + assertEquals(messageList, messagePublisher.messageQuery(System.currentTimeMillis()-1, System.currentTimeMillis())); + Mockito.verify(messageArchive, atLeastOnce()).messageQuery(anyLong(), anyLong()); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageReceiverTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageReceiverTest.java new file mode 100644 index 00000000..404996e7 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageReceiverTest.java @@ -0,0 +1,284 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.local_api.MessageCallback; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.jms.JMSException; +import javax.jms.MessageConsumer; +import javax.jms.TextMessage; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.times; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({MessageReceiver.class, MessageConsumer.class, IOMessageListener.class, TextMessage.class, + LoggingService.class, Message.class, IOMessageListener.class}) +public class MessageReceiverTest { + private MessageReceiver messageReceiver; + private MessageConsumer messageConsumer; + private IOMessageListener ioMessageListener; + private TextMessage textMessage; + private Message message; + private String name; + private String MODULE_NAME; + + @Before + public void setUp() throws Exception { + name = "receiver"; + MODULE_NAME = "MessageReceiver"; + mockStatic(LoggingService.class); + messageConsumer = mock(MessageConsumer.class); + ioMessageListener = mock(IOMessageListener.class); + textMessage = mock(TextMessage.class); + message = mock(Message.class); + PowerMockito.whenNew(IOMessageListener.class).withArguments(any(MessageCallback.class)).thenReturn(ioMessageListener); + PowerMockito.whenNew(Message.class).withParameterTypes(byte[].class).withArguments(any()).thenReturn(message); + PowerMockito.when(messageConsumer.receiveNoWait()).thenReturn(textMessage).thenReturn(null); + PowerMockito.when(messageConsumer.getMessageListener()).thenReturn(ioMessageListener); + PowerMockito.when(textMessage.getText()).thenReturn("{}"); + messageReceiver = spy(new MessageReceiver(name, messageConsumer)); + } + + @After + public void tearDown() throws Exception { + reset(messageConsumer, messageReceiver, ioMessageListener); + MODULE_NAME = null; + } + + /** + * Test getMessages When clientConsumer receive immediate message as null + */ + @Test + public void testGetMessagesWhenClientConsumerReceivesNull() { + try { + PowerMockito.when(messageConsumer.receiveNoWait()).thenReturn(null); + assertEquals(0, messageReceiver.getMessages().size()); + Mockito.verify(messageConsumer, times(1)).receiveNoWait(); + Mockito.verify(textMessage, Mockito.never()).acknowledge(); + Mockito.verify(messageConsumer, Mockito.never()).setMessageListener(any(IOMessageListener.class)); + PowerMockito.verifyPrivate(messageReceiver, times(1)) + .invoke("getMessage"); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, String.format("Finished getting message \"%s\"", name)); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test getMessages When Listener is null + */ + @Test + public void testGetMessagesWhenListenerIsNull() { + try { + assertEquals(message, messageReceiver.getMessages().get(0)); + Mockito.verify(messageConsumer, times(2)).receiveNoWait(); + Mockito.verify(textMessage).acknowledge(); + Mockito.verify(messageConsumer, Mockito.never()).setMessageListener(any(IOMessageListener.class)); + PowerMockito.verifyPrivate(messageReceiver, times(2)) + .invoke("getMessage"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + + /** + * Test getMessages When Listener is not null + */ + @Test + public void testGetMessagesWhenListenerIsNotNull() { + try { + messageReceiver.enableRealTimeReceiving(); + assertEquals(0, messageReceiver.getMessages().size()); + Mockito.verify(messageConsumer, Mockito.never()).receiveNoWait(); + Mockito.verify(textMessage, Mockito.never()).acknowledge(); + Mockito.verify(messageConsumer).setMessageListener(any(IOMessageListener.class)); + PowerMockito.verifyPrivate(messageReceiver).invoke("getMessage"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getName + */ + @Test + public void testGetName() { + assertEquals(name, messageReceiver.getName()); + } + + /** + * Test enableRealTimeReceiving when Consumer is Null + */ + @Test + public void testEnableRealTimeReceivingWhenConsumerIsNull() { + try { + messageReceiver = spy(new MessageReceiver(name, null)); + messageReceiver.enableRealTimeReceiving(); + Mockito.verify(messageConsumer, Mockito.never()).setMessageListener(any(IOMessageListener.class)); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Start enable real time receiving"); + verifyStatic(LoggingService.class); + LoggingService.logError(anyString(), anyString(), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test enableRealTimeReceiving when Consumer is not Null + */ + @Test + public void testEnableRealTimeReceivingWhenConsumerIsNotNull() { + try { + messageReceiver.enableRealTimeReceiving(); + Mockito.verify(messageConsumer).setMessageListener(any(IOMessageListener.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test enableRealTimeReceiving clientConsumer throws ActiveMQException + */ + @Test + public void throwsExceptionWhenSetHandlerIsCalledWhileEnableRealTimeReceiving() { + try { + PowerMockito.doThrow(mock(JMSException.class)).when(messageConsumer).setMessageListener(any()); + messageReceiver.enableRealTimeReceiving(); + Mockito.verify(messageConsumer).setMessageListener(any(IOMessageListener.class)); + verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error in enabling real time listener"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test disableRealTimeReceiving clientConsumer throws ActiveMQException + * Listener is not null + */ + @Test + public void throwsActiveMqExceptionWhenSetHandlerIsCalledWhileDisablingRealTimeReceiving() { + try { + messageReceiver.enableRealTimeReceiving(); + PowerMockito.doThrow(mock(JMSException.class)).when(messageConsumer).setMessageListener(any()); + messageReceiver.disableRealTimeReceiving(); + Mockito.verify(messageConsumer).setMessageListener(any(IOMessageListener.class)); + verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error in disabling real time listener"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test disableRealTimeReceiving clientConsumer is null + */ + @Test + public void testDisablingRealTimeReceivingWhenClientConsumerIsNull() { + try { + messageReceiver = spy(new MessageReceiver(name, null)); + messageReceiver.disableRealTimeReceiving(); + Mockito.verify(messageConsumer, Mockito.never()).setMessageListener(any(IOMessageListener.class)); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Start disable real time receiving"); + verifyStatic(LoggingService.class, Mockito.never()); + LoggingService.logDebug(MODULE_NAME, "Finished disable real time receiving"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test disableRealTimeReceiving clientConsumer is not null + * Listener is not null + */ + @Test + public void testDisableRealTimeReceiving() { + try { + messageReceiver.enableRealTimeReceiving(); + messageReceiver.disableRealTimeReceiving(); + Mockito.verify(messageConsumer).setMessageListener(any(IOMessageListener.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test close MessageReceiver when clientConsumer is not null + */ + @Test + public void testCloseReceiverWhenConsumerIsNotNull() { + try { + messageReceiver.close(); + Mockito.verify(messageReceiver).disableRealTimeReceiving(); + Mockito.verify(messageConsumer).close(); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Start closing receiver"); + verifyStatic(LoggingService.class); + LoggingService.logDebug(MODULE_NAME, "Finished closing receiver"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test close MessageReceiver when clientConsumer is null + */ + @Test + public void testCloseReceiverWhenConsumerIsNull() { + try { + messageReceiver = spy(new MessageReceiver(name, null)); + messageReceiver.close(); + Mockito.verify(messageReceiver, Mockito.never()).disableRealTimeReceiving(); + Mockito.verify(messageConsumer, Mockito.never()).close(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test close MessageReceiver when clientConsumer throws ActiveMQException + */ + @Test + public void throwsExceptionWhenCloseIsCalled() { + try { + PowerMockito.doThrow(mock(JMSException.class)).when(messageConsumer).close(); + messageReceiver.close(); + Mockito.verify(messageReceiver).disableRealTimeReceiving(); + Mockito.verify(messageConsumer).close(); + verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error in closing receiver"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageTest.java new file mode 100644 index 00000000..bd9a6187 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageTest.java @@ -0,0 +1,484 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.message_bus; + +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; + +import static java.lang.System.currentTimeMillis; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Message.class, Base64.class, LoggingService.class, ByteArrayOutputStream.class}) +public class MessageTest { + private short VERSION; + private String MODULE_NAME; + private Message message; + private String id; + private String tag; + private String messageGroupId; + private int sequenceNumber; + private int sequenceTotal; + private byte priority; + private long timestamp; + private String publisher; + private String authIdentifier; + private String authGroup; + private short version; + private long chainPosition; + private String hash; + private String previousHash; + private String nonce; + private int difficultyTarget; + private String infoType; + private String infoFormat; + private byte[] contextData; + private byte[] contentData; + private JsonObject jsonObject; + private JsonObjectBuilder jsonObjectBuilder = null; + + + @Before + public void setUp() throws Exception { + mockStatic(LoggingService.class); + MODULE_NAME = "Message"; + VERSION = 4; + byte[] byteArray = new byte[] { (byte)0xe0, (byte)0xf4 }; + version = 0; + id = "id"; + tag = "tag"; + messageGroupId = "messageGroupId"; + sequenceNumber = 1; + sequenceTotal = 10; + priority = 1; + timestamp = currentTimeMillis(); + publisher = "publisher"; + authIdentifier = "authIdentifier"; + authGroup = "authGroup"; + chainPosition = 5; + hash = "hash"; + previousHash = "previousHash"; + nonce = "nonce"; + difficultyTarget = 2; + infoType = "infoType"; + infoFormat = "infoFormat"; + message = spy(new Message()); + String content = "contentData"; + String context = "contextData"; + contentData = Base64.getDecoder().decode(content.getBytes(UTF_8)); + contextData = Base64.getDecoder().decode(context.getBytes(UTF_8)); + jsonObjectBuilder = Json.createObjectBuilder(); + jsonObject = jsonObjectBuilder.add("id", id) + .add("tag",tag ) + .add("groupid", messageGroupId) + .add("sequencenumber", sequenceNumber) + .add("sequencetotal", sequenceTotal) + .add("priority", priority) + .add("timestamp", timestamp) + .add("publisher", publisher) + .add("authid", authIdentifier) + .add("authgroup", authGroup) + .add("chainposition", chainPosition) + .add("hash", hash) + .add("previoushash", previousHash) + .add("nonce", nonce) + .add("difficultytarget", difficultyTarget) + .add("infotype", infoType) + .add("infoformat", infoFormat) + .add("contentdata", content) + .add("contextdata", context).build(); + + } + + @After + public void tearDown() throws Exception { + MODULE_NAME = null; + VERSION = 0; + id = null; + tag = null; + messageGroupId = null; + sequenceNumber = 0; + sequenceTotal = 0; + priority = 0; + timestamp = 0; + publisher = null; + authIdentifier = null; + authGroup = null; + chainPosition = 0; + hash = null; + previousHash = null; + nonce = null; + difficultyTarget = 0; + infoType = null; + infoFormat = null; + contentData = null; + contextData = null; + } + + /** + * Test Message constructor with Json parameter + */ + @Test + public void constructorWithJsonArgument() { + message = spy(new Message(jsonObject)); + assertEquals(id, message.getId()); + assertEquals(tag, message.getTag()); + assertEquals(messageGroupId, message.getMessageGroupId()); + assertEquals(sequenceNumber, message.getSequenceNumber()); + assertEquals(sequenceTotal, message.getSequenceTotal()); + assertEquals(priority, message.getPriority()); + assertEquals(timestamp, message.getTimestamp()); + assertEquals(publisher, message.getPublisher()); + assertEquals(authIdentifier, message.getAuthIdentifier()); + assertEquals(authGroup, message.getAuthGroup()); + assertEquals(chainPosition, message.getChainPosition()); + assertEquals(hash, message.getHash()); + assertEquals(previousHash, message.getPreviousHash()); + assertEquals(nonce, message.getNonce()); + assertEquals(difficultyTarget, message.getDifficultyTarget()); + assertEquals(infoType, message.getInfoType()); + assertEquals(infoFormat, message.getInfoFormat()); + } + + /** + * Test getter And Setter of Id + */ + @Test + public void testGetterAndSetterId() { + message.setId(id); + assertEquals(id, message.getId()); + } + + /** + * Test getter And Setter of tag + */ + @Test + public void testGetterAndSetterTag() { + message.setTag(tag); + assertEquals(tag, message.getTag()); + } + + /** + * Test getter And Setter of MessageGroupId + */ + @Test + public void testGetterAndSetterMessageGroupId() { + message.setMessageGroupId(messageGroupId); + assertEquals(messageGroupId, message.getMessageGroupId()); + } + + /** + * Test getter and setter of sequenceNumber + */ + @Test + public void testGetterAndSetterSequenceNumber() { + message.setSequenceNumber(sequenceNumber); + assertEquals(sequenceNumber, message.getSequenceNumber()); + } + + /** + * Test getter and setter of sequenceTotal + */ + @Test + public void testGetterAndSetterSequenceTotal() { + message.setSequenceTotal(sequenceTotal); + assertEquals(sequenceTotal, message.getSequenceTotal()); + } + + /** + * Test getter and setter of priority + */ + @Test + public void testGetterAndSetterPriority() { + message.setPriority(priority); + assertEquals(priority, message.getPriority()); + } + + /** + * Test getter and setter of timeStamp + */ + @Test + public void testGetterAndSetterTimestamp() { + message.setTimestamp(timestamp); + assertEquals(timestamp, message.getTimestamp()); + } + + /** + * Test getter and setter of Publisher + */ + @Test + public void testGetterAndSetterPublisher() { + message.setPublisher(publisher); + assertEquals(publisher, message.getPublisher()); + } + + /** + * Test getter and setter of authIdentifier + */ + @Test + public void testGetterAndSetterAuthIdentifier() { + message.setAuthIdentifier(authIdentifier); + assertEquals(authIdentifier, message.getAuthIdentifier()); + } + + /** + * Test getter and setter of AuthGroup + */ + @Test + public void testGetterAndSetterAuthGroup() { + message.setAuthGroup(authGroup); + assertEquals(authGroup, message.getAuthGroup()); + } + + /** + * Test getter and setter of version + */ + @Test + public void testGetterAndSetterVersion() { + assertEquals(VERSION, message.getVersion()); + } + + /** + * Test getter and setter of chainPosition + */ + @Test + public void testGetterAndSetterChainPosition() { + message.setChainPosition(chainPosition); + assertEquals(chainPosition, message.getChainPosition()); + } + + /** + * Test getter and setter of hash + */ + @Test + public void testGetterAndSetterHash() { + message.setHash(hash); + assertEquals(hash, message.getHash()); + } + + /** + * Test getter and setter of previous hash + */ + @Test + public void testGetterAndSetterPreviousHash() { + message.setPreviousHash(previousHash); + assertEquals(previousHash, message.getPreviousHash()); + } + + /** + * Test getter and setter of nonce + */ + @Test + public void testGetterAndSetterNonce() { + message.setNonce(nonce); + assertEquals(nonce, message.getNonce()); + } + + /** + * Test getter and setter of difficultyTarget + */ + @Test + public void testGetterAndSetterDifficultyTarget() { + message.setDifficultyTarget(difficultyTarget); + assertEquals(difficultyTarget, message.getDifficultyTarget()); + } + + /** + * Test getter and setter of infoType + */ + @Test + public void testGetterAndSetterInfoType() { + message.setInfoType(infoType); + assertEquals(infoType, message.getInfoType()); + } + + /** + * Test getter and setter of InfoFormat + */ + @Test + public void testGetterAndSetterInfoFormat() { + message.setInfoFormat(infoFormat); + assertEquals(infoFormat, message.getInfoFormat()); + } + + /** + * Test getter and setter of ContextData + */ + @Test + public void testGetterAndSetterContextData() { + message.setContextData(contextData); + assertEquals(contextData, message.getContextData()); + } + + /** + * Test getter and setter of ContentData + */ + @Test + public void testGetterAndSetterContentData() { + message.setContentData(contentData); + assertEquals(contentData, message.getContentData()); + } + + /** + * Test getter and setter of bytes + */ + @Test + public void testBytes() { + message = spy(new Message(jsonObject)); + byte[] rawByte = message.getBytes(); + message = spy(new Message(rawByte)); + assertEquals(id, message.getId()); + assertEquals(tag, message.getTag()); + assertEquals(messageGroupId, message.getMessageGroupId()); + assertEquals(sequenceNumber, message.getSequenceNumber()); + assertEquals(sequenceTotal, message.getSequenceTotal()); + assertEquals(priority, message.getPriority()); + assertEquals(timestamp, message.getTimestamp()); + assertEquals(publisher, message.getPublisher()); + assertEquals(authIdentifier, message.getAuthIdentifier()); + assertEquals(authGroup, message.getAuthGroup()); + assertEquals(chainPosition, message.getChainPosition()); + assertEquals(hash, message.getHash()); + assertEquals(previousHash, message.getPreviousHash()); + assertEquals(nonce, message.getNonce()); + assertEquals(difficultyTarget, message.getDifficultyTarget()); + assertEquals(infoType, message.getInfoType()); + assertEquals(infoFormat, message.getInfoFormat()); + + } + + /** + * Test throws exception when ByteArrayOutputStream object is created + */ + @Test + public void throwsExceptionWhenByteArrayOutputStreamIsCreatedInBytes() { + try { + whenNew(ByteArrayOutputStream.class).withNoArguments().thenThrow(mock(IOException.class) ); + message = spy(new Message(jsonObject)); + byte[] rawByte = message.getBytes(); + assertTrue(rawByte.length == 0); + verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error in getBytes"), any()); + } catch (Exception e) { + fail("This should never happen"); + } + } + + /** + * Test toJson when Message constructor is without argument + */ + @Test + public void testJsonWithMessageConstructorWithNoArguments() { + assertTrue(message.toJson().containsKey("version")); + assertTrue(message.toJson().containsKey("publisher")); + assertTrue(message.toJson().containsKey("groupid")); + assertEquals("", message.toJson().getString("groupid")); + assertEquals(VERSION, message.toJson().getInt("version")); + assertEquals("", message.toJson().getString("publisher")); + assertEquals("", message.toJson().getString("infotype")); + } + + /** + * Test toJson when Message constructor is without argument JsonObject + */ + @Test + public void testJsonWithMessageConstructorWithJsonArgument() { + message = spy(new Message(jsonObject)); + assertTrue(message.toJson().containsKey("version")); + assertTrue(message.toJson().containsKey("publisher")); + assertTrue(message.toJson().containsKey("groupid")); + assertEquals(messageGroupId, message.toJson().getString("groupid")); + assertEquals(version, message.toJson().getInt("version")); + assertEquals(publisher, message.toJson().getString("publisher")); + assertEquals(infoFormat, message.toJson().getString("infoformat")); + assertEquals(previousHash, message.toJson().getString("previoushash")); + assertEquals(hash, message.toJson().getString("hash")); + } + + /** + * Test toString + */ + @Test + public void testToString() { + assertFalse(message.toString().contains("@")); + message = spy(new Message(jsonObject)); + assertFalse(message.toString().contains("@")); + } + + /** + * Test decodeBase64 + */ + @Test + public void testDecodeBase64() { + message.decodeBase64(message.encodeBase64()); + assertNotEquals(hash, message.getHash()); + } + + /** + * Test decodeBase64 throws Exception + */ + @Test + public void throwsExceptionWhenDecodeBase64() { + mockStatic(Base64.class); + Base64.Decoder decoder = mock(Base64.Decoder.class); + when(Base64.getDecoder()).thenReturn(decoder); + PowerMockito.doThrow(new RuntimeException()).when(decoder).decode( any(byte[].class)); + message.decodeBase64(message.encodeBase64()); + verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error in decodeBase64"), any()); + } + + /** + * Test encodeBase64 + */ + @Test + public void testEncodeBase64() { + assertNotNull(message.encodeBase64()); + } + + /** + * Test encodeBase64 throws Exception + */ + @Test + public void throwsExceptionWhenEncodeBase64() { + mockStatic(Base64.class); + Base64.Encoder encoder = mock(Base64.Encoder.class); + when(Base64.getEncoder()).thenReturn(encoder); + PowerMockito.doThrow(new RuntimeException()).when(encoder).encode( any(byte[].class)); + message.encodeBase64(); + verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error in encodeBase64"), any()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerManagerTest.java new file mode 100644 index 00000000..16ecc772 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerManagerTest.java @@ -0,0 +1,564 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.exception.ConflictException; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.exception.NotModifiedException; +import com.github.dockerjava.api.model.Container; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.microservice.MicroserviceState; +import org.eclipse.iofog.microservice.Registry; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.Optional; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ContainerManager.class, MicroserviceManager.class, ContainerTask.class, LoggingService.class, + DockerUtil.class, Microservice.class, Container.class, StatusReporter.class, ProcessManagerStatus.class, + Registry.class, IOFogNetworkInterfaceManager.class}) +public class ContainerManagerTest { + private ContainerManager containerManager; + private MicroserviceManager microserviceManager; + private ContainerTask containerTask; + private ProcessManagerStatus processManagerStatus; + private DockerUtil dockerUtil; + private String MODULE_NAME; + private Microservice microservice; + private Container container; + private Registry registry; + private IOFogNetworkInterfaceManager ioFogNetworkInterfaceManager; + private Optional optionalContainer; + private Optional optionalMicroservice; + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Container Manager"; + microserviceManager = mock(MicroserviceManager.class); + containerTask = mock(ContainerTask.class); + dockerUtil = mock(DockerUtil.class); + microservice = mock(Microservice.class); + container = mock(Container.class); + registry = mock(Registry.class); + ioFogNetworkInterfaceManager = mock(IOFogNetworkInterfaceManager.class); + processManagerStatus = mock(ProcessManagerStatus.class); + optionalContainer = Optional.of(container); + optionalMicroservice = Optional.of(microservice); + PowerMockito.mockStatic(MicroserviceManager.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(DockerUtil.class); + PowerMockito.mockStatic(StatusReporter.class); + PowerMockito.mockStatic(IOFogNetworkInterfaceManager.class); + PowerMockito.when(MicroserviceManager.getInstance()).thenReturn(microserviceManager); + PowerMockito.when(DockerUtil.getInstance()).thenReturn(dockerUtil); + PowerMockito.when(StatusReporter.setProcessManagerStatus()).thenReturn(processManagerStatus); + PowerMockito.when(IOFogNetworkInterfaceManager.getInstance()).thenReturn(ioFogNetworkInterfaceManager); + PowerMockito.when(ioFogNetworkInterfaceManager.getCurrentIpAddress()).thenReturn("url"); + PowerMockito.when(processManagerStatus.setMicroservicesState(any(), any())).thenReturn(processManagerStatus); + containerManager = PowerMockito.spy(new ContainerManager()); + } + + @After + public void tearDown() throws Exception { + reset(containerManager, containerTask, dockerUtil, microserviceManager); + MODULE_NAME = null; + } + + /** + * Test execute when containerTask is null + */ + @Test + public void testExecuteWhenContainerTaskIsNull() { + try { + containerManager.execute(null); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + Mockito.verify(microserviceManager, never()).findLatestMicroserviceByUuid(any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is remove And microservice is Empty + */ + @Test + public void testExecuteWhenContainerTaskIsNotNullAndMicroserviceIsEmpty() { + try { + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.REMOVE); + containerManager.execute(containerTask); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + Mockito.verify(microserviceManager).findLatestMicroserviceByUuid(eq(containerTask.getMicroserviceUuid())); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq(containerTask.getMicroserviceUuid()), eq(false)); + PowerMockito.verifyPrivate(containerManager, Mockito.never()).invoke("removeContainer", anyString(), anyString(), anyBoolean()); + PowerMockito.verifyPrivate(containerManager, Mockito.never()).invoke("setMicroserviceStatus", anyString(), any(MicroserviceState.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is remove And microservice is not empty + * Task contains microserviceId which is not valid or already removed + */ + @Test + public void testExecuteWhenContainerTaskRemoveMicroserviceIdNotValidAndMicroserviceIsNotEmpty() { + try { + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.REMOVE); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + containerManager.execute(containerTask); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + verify(dockerUtil).getContainer(any()); + Mockito.verify(microserviceManager).findLatestMicroserviceByUuid(eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq("uuid"), eq(false)); + PowerMockito.verifyPrivate(containerManager, Mockito.never()).invoke("removeContainer", anyString(), anyString(), anyBoolean()); + PowerMockito.verifyPrivate(containerManager, Mockito.never()).invoke("setMicroserviceStatus", anyString(), any(MicroserviceState.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is remove And microservice is not empty + * Task contains microserviceId which is valid and not already removed + */ + @Test + public void testExecuteWhenContainerTaskRemoveMicroserviceIdIsValid() { + try { + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.REMOVE); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + containerManager.execute(containerTask); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + verify(dockerUtil).stopContainer(any()); + verify(dockerUtil, times(2)).getContainer(any()); + Mockito.verify(microserviceManager).findLatestMicroserviceByUuid(eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq("uuid"), eq(false)); + PowerMockito.verifyPrivate(containerManager).invoke("stopContainer", eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainer", eq(null), eq(null), eq(false)); + PowerMockito.verifyPrivate(containerManager, Mockito.times(4)).invoke("setMicroserviceStatus", any(), any(MicroserviceState.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is remove And microservice is not empty + * Task contains microserviceId which is valid and not already removed + * docker.stopContainer throws Exception + */ + @Test + public void throwsExceptionWhenDockerStopContainerIsCalledInExecuteWhenContainerTaskRemoveMicroserviceIdISIsValid() { + try { + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.REMOVE); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.when(container.getId()).thenReturn("containerID"); + PowerMockito.doThrow(mock(NotModifiedException.class)).when(dockerUtil).stopContainer(any()); + containerManager.execute(containerTask); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + verify(dockerUtil).stopContainer(any()); + verify(dockerUtil, times(2)).getContainer(any()); + Mockito.verify(microserviceManager).findLatestMicroserviceByUuid(eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq("uuid"), eq(false)); + PowerMockito.verifyPrivate(containerManager).invoke("stopContainer", eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainer", eq("containerID"), eq(null), eq(false)); + PowerMockito.verifyPrivate(containerManager, Mockito.times(4)) + .invoke("setMicroserviceStatus", any(), any(MicroserviceState.class)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error stopping container \"containerID\""), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is remove And microservice is not empty + * Task contains microserviceId which is valid and not already removed + * docker.removeContainer throws Exception + */ + @Test (expected = AgentSystemException.class) + public void throwsExceptionWhenDockerRemoveContainerIsCalledInExecuteWhenContainerTaskRemove() throws Exception{ + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.REMOVE); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.when(container.getId()).thenReturn("containerID"); + PowerMockito.doThrow(mock(NotModifiedException.class)).when(dockerUtil).removeContainer(any(), anyBoolean()); + containerManager.execute(containerTask); + } + + /** + * Test execute when containerTask is not null + * TasK is REMOVE_WITH_CLEAN_UP + */ + @Test + public void testExecuteWhenContainerTaskRemoveWithCleanup() { + try { + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.REMOVE_WITH_CLEAN_UP); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + containerManager.execute(containerTask); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + verify(dockerUtil).stopContainer(any()); + verify(dockerUtil, times(2)).getContainer(any()); + verify(dockerUtil).removeImageById(any()); + Mockito.verify(microserviceManager).findLatestMicroserviceByUuid(eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq("uuid"), eq(true)); + PowerMockito.verifyPrivate(containerManager).invoke("stopContainer", eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainer", eq(null), eq(null), eq(true)); + PowerMockito.verifyPrivate(containerManager, Mockito.times(4)) + .invoke("setMicroserviceStatus", any(), any(MicroserviceState.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is REMOVE_WITH_CLEAN_UP + * docker.removeImageById throws NotFoundException + */ + @Test + public void throwsExceptionWhenDockerRemoveImageByIdWhenContainerTaskRemoveWithCleanup() { + try { + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.REMOVE_WITH_CLEAN_UP); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(container.getId()).thenReturn("containerID"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.doThrow(mock(NotFoundException.class)).when(dockerUtil).removeImageById(any()); + containerManager.execute(containerTask); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + verify(dockerUtil).stopContainer(any()); + verify(dockerUtil, times(2)).getContainer(any()); + verify(dockerUtil).removeImageById(any()); + Mockito.verify(microserviceManager).findLatestMicroserviceByUuid(eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq("uuid"), eq(true)); + PowerMockito.verifyPrivate(containerManager).invoke("stopContainer", eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainer", eq("containerID"), eq(null), eq(true)); + PowerMockito.verifyPrivate(containerManager, Mockito.times(4)) + .invoke("setMicroserviceStatus", any(), any(MicroserviceState.class)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Image for container \"containerID\" cannot be removed"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is REMOVE_WITH_CLEAN_UP + * docker.removeImageById throws ConflictException + */ + @Test + public void throwsConflictExceptionWhenDockerRemoveImageByIdWhenContainerTaskRemoveWithCleanup() { + try { + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.REMOVE_WITH_CLEAN_UP); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(container.getId()).thenReturn("containerID"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.doThrow(mock(ConflictException.class)).when(dockerUtil).removeImageById(any()); + containerManager.execute(containerTask); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + verify(dockerUtil).stopContainer(any()); + verify(dockerUtil, times(2)).getContainer(any()); + verify(dockerUtil).removeImageById(any()); + Mockito.verify(microserviceManager).findLatestMicroserviceByUuid(eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq("uuid"), eq(true)); + PowerMockito.verifyPrivate(containerManager).invoke("stopContainer", eq("uuid")); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainer", eq("containerID"), eq(null), eq(true)); + PowerMockito.verifyPrivate(containerManager, Mockito.times(4)) + .invoke("setMicroserviceStatus", any(), any(MicroserviceState.class)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Image for container \"containerID\" cannot be removed"), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is ADD + * Microservice is Empty + */ + @Test + public void testExecuteWhenContainerTaskAddAndMicroserviceIsEmpty() { + try { + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.ADD); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + containerManager.execute(containerTask); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + PowerMockito.verifyPrivate(containerManager, never()).invoke("addContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager, never()).invoke("createContainer", any()); + PowerMockito.verifyPrivate(containerManager, never()).invoke("getRegistry", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is ADD + * Microservice is not Empty + * getRegistries throws AgentSystemException + * registries from microserviceManager is null + */ + @Test (expected = AgentSystemException.class) + public void throwsAgentSystemExceptionWhenRegistriesIsNullExecuteWhenContainerTaskAdd() throws Exception{ + PowerMockito.when(microserviceManager.findLatestMicroserviceByUuid(anyString())) + .thenReturn(optionalMicroservice); + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.ADD); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + containerManager.execute(containerTask); + } + + /** + * Test execute when containerTask is not null + * TasK is ADD + * Microservice is not Empty + * getRegistries returns registry with url from_cache + */ + @Test + public void testExecuteWhenContainerTaskAddAndRegistriesArePresentWithURLFromCache() { + try { + PowerMockito.when(microserviceManager.findLatestMicroserviceByUuid(anyString())) + .thenReturn(optionalMicroservice); + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.ADD); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.when(microserviceManager.getRegistry(anyInt())).thenReturn(registry); + PowerMockito.when(registry.getUrl()).thenReturn("from_cache"); + containerManager.execute(containerTask); + verify(dockerUtil, never()).pullImage(any(), any(), any()); + verify(dockerUtil).createContainer(any(), any()); + verify(microservice).setRebuild(anyBoolean()); + Mockito.verify(dockerUtil).getContainer(eq(microservice.getMicroserviceUuid())); + PowerMockito.verifyPrivate(containerManager).invoke("addContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("createContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("getRegistry", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("startContainer", eq(microservice)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is ADD + * Microservice is not Empty + * getRegistries returns registry with url url + */ + @Test + public void testExecuteWhenContainerTaskAddAndRegistriesArePresentWithURL() { + try { + PowerMockito.when(microserviceManager.findLatestMicroserviceByUuid(anyString())) + .thenReturn(optionalMicroservice); + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.ADD); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.when(microserviceManager.getRegistry(anyInt())).thenReturn(registry); + PowerMockito.when(registry.getUrl()).thenReturn("url"); + containerManager.execute(containerTask); + verify(dockerUtil).pullImage(any(), any(), any()); + verify(dockerUtil).createContainer(any(), any()); + verify(microservice).setRebuild(anyBoolean()); + Mockito.verify(dockerUtil).getContainer(eq(microservice.getMicroserviceUuid())); + PowerMockito.verifyPrivate(containerManager).invoke("addContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("createContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("getRegistry", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("startContainer", eq(microservice)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is ADD + * Microservice is not Empty + * getRegistries returns registry with url + * Docker.pullImage throws Exception + * docker.findLocalImage returns false + */ + @Test (expected = NotFoundException.class) + public void throwsExceptionWhenDockerImagePullIsCalledExecuteWhenContainerTaskAdd() throws Exception { + PowerMockito.when(microserviceManager.findLatestMicroserviceByUuid(anyString())) + .thenReturn(optionalMicroservice); + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.ADD); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.when(dockerUtil.findLocalImage(anyString())).thenReturn(false); + PowerMockito.doThrow(mock(AgentSystemException.class)).when(dockerUtil).pullImage(any(), any(), any()); + PowerMockito.when(microserviceManager.getRegistry(anyInt())).thenReturn(registry); + PowerMockito.when(registry.getUrl()).thenReturn("url"); + containerManager.execute(containerTask); + } + + /** + * Test execute when containerTask is not null + * TasK is ADD + * Microservice is not Empty + * getRegistries returns registry with url + * Docker.pullImage throws Exception + * docker.findLocalImage returns true + */ + @Test + public void testWhenDockerImagePullIsCalledExecuteWhenContainerTaskAdd() { + try { + PowerMockito.when(microserviceManager.findLatestMicroserviceByUuid(anyString())) + .thenReturn(optionalMicroservice); + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.ADD); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.when(dockerUtil.findLocalImage(anyString())).thenReturn(true); + PowerMockito.doThrow(mock(AgentSystemException.class)).when(dockerUtil).pullImage(any(), any(), any()); + PowerMockito.when(microserviceManager.getRegistry(anyInt())).thenReturn(registry); + PowerMockito.when(registry.getUrl()).thenReturn("url"); + containerManager.execute(containerTask); + verify(dockerUtil).pullImage(any(), any(), any()); + verify(dockerUtil).createContainer(any(), any()); + verify(microservice).setRebuild(anyBoolean()); + PowerMockito.verifyPrivate(containerManager).invoke("addContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("createContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("createContainer", eq(microservice), eq(true)); + PowerMockito.verifyPrivate(containerManager).invoke("createContainer", eq(microservice), eq(false)); + PowerMockito.verifyPrivate(containerManager, times(2)).invoke("getRegistry", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("startContainer", any()); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), + eq("unable to pull \"microserviceName\" from registry. trying local cache"), + any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is UPDATE + * Microservice is not Empty + * getRegistries returns registry with url + * Docker.pullImage throws Exception + * docker.findLocalImage returns true + * Microservice isRebuild is false + * withCleanUp is false + */ + @Test + public void testExecuteWhenContainerTaskUpdate() { + try { + PowerMockito.when(microserviceManager.findLatestMicroserviceByUuid(anyString())) + .thenReturn(optionalMicroservice); + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.UPDATE); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(microservice.isRebuild()).thenReturn(false); + PowerMockito.when(microservice.getRegistryId()).thenReturn(2); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.when(microserviceManager.getRegistry(anyInt())).thenReturn(registry); + PowerMockito.when(registry.getUrl()).thenReturn("url"); + containerManager.execute(containerTask); + verify(dockerUtil).pullImage(any(), any(), any()); + verify(dockerUtil).createContainer(any(), any()); + verify(microservice).setRebuild(anyBoolean()); + PowerMockito.verifyPrivate(containerManager).invoke("updateContainer", eq(microservice), eq(false)); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq("uuid"), eq(false)); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainer", eq(container.getId()), eq(null), eq(false)); + PowerMockito.verifyPrivate(containerManager).invoke("createContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("startContainer", eq(microservice)); + } catch (Exception e) { + System.out.println(e); + fail("This should not happen"); + } + } + + /** + * Test execute when containerTask is not null + * TasK is UPDATE + * Microservice is not Empty + * getRegistries returns registry with url + * Docker.pullImage throws Exception + * docker.findLocalImage returns true + * Microservice isRebuild is false + * withCleanUp is false + * docker.startContainer throws Exception + */ + @Test + public void throwsNotFoundExceptionWhenStartContainerIsCalledInExecuteWhenContainerTaskUpdate() { + try { + PowerMockito.when(microserviceManager.findLatestMicroserviceByUuid(anyString())) + .thenReturn(optionalMicroservice); + PowerMockito.when(containerTask.getAction()).thenReturn(ContainerTask.Tasks.UPDATE); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(microservice.isRebuild()).thenReturn(false); + PowerMockito.when(microservice.getRegistryId()).thenReturn(2); + PowerMockito.when(containerTask.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(dockerUtil.getContainer(anyString())).thenReturn(optionalContainer); + PowerMockito.doThrow(mock(NotFoundException.class)).when(dockerUtil).startContainer(any()); + PowerMockito.when(microserviceManager.getRegistry(anyInt())).thenReturn(registry); + PowerMockito.when(registry.getUrl()).thenReturn("url"); + containerManager.execute(containerTask); + verify(dockerUtil).pullImage(any(), any(), any()); + verify(dockerUtil).createContainer(any(), any()); + verify(microservice).setRebuild(anyBoolean()); + PowerMockito.verifyPrivate(containerManager).invoke("updateContainer", eq(microservice), eq(false)); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainerByMicroserviceUuid", eq("uuid"), eq(false)); + PowerMockito.verifyPrivate(containerManager).invoke("removeContainer", eq(container.getId()), eq(null), eq(false)); + PowerMockito.verifyPrivate(containerManager).invoke("createContainer", eq(microservice)); + PowerMockito.verifyPrivate(containerManager).invoke("startContainer", eq(microservice)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), + eq("Container \"microserviceName\" not found"), + any()); + } catch (Exception e) { + System.out.println(e); + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerTaskTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerTaskTest.java new file mode 100644 index 00000000..a58cef89 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerTaskTest.java @@ -0,0 +1,107 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.reset; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ContainerTask.class}) +public class ContainerTaskTest { + private ContainerTask containerTask; + private ContainerTask.Tasks task; + private String microserviceId; + + @Before + public void setUp() throws Exception { + task = ContainerTask.Tasks.ADD; + microserviceId = "microserviceId"; + containerTask = new ContainerTask(task, microserviceId); + } + + @After + public void tearDown() throws Exception { + microserviceId = null; + } + + /** + * Test getAction + */ + @Test + public void testGetAction() { + assertEquals(task, containerTask.getAction()); + } + + /** + * Test getRetries And incrementRetries + */ + @Test + public void testGetRetries() { + assertEquals(0, containerTask.getRetries()); + containerTask.incrementRetries(); + assertEquals(1, containerTask.getRetries()); + } + + /** + * Test getMicroserviceUuid + */ + @Test + public void testGetMicroserviceUuid() { + assertEquals(microserviceId, containerTask.getMicroserviceUuid()); + } + + /** + * Test equals + */ + @Test + public void testEquals() { + ContainerTask newContainerTask = new ContainerTask(task, microserviceId); + assertTrue(containerTask.equals(newContainerTask)); + ContainerTask anotherTask = new ContainerTask(ContainerTask.Tasks.REMOVE_WITH_CLEAN_UP, microserviceId); + assertFalse(containerTask.equals(anotherTask)); + } + + /** + * Test hasCode when object are equal + */ + @Test + public void testHashCodeWhenObjectAreEqual() { + ContainerTask newContainerTask = new ContainerTask(task, microserviceId); + assertTrue(containerTask.equals(newContainerTask)); + assertEquals("When Objects are equal they have equal hashcode", + containerTask.hashCode(), newContainerTask.hashCode()); + } + + /** + * Test hasCode when object are not equal + */ + @Test + public void testHashCodeWhenObjectAreNotEqual() { + ContainerTask anotherTask = new ContainerTask(ContainerTask.Tasks.REMOVE_WITH_CLEAN_UP, microserviceId); + assertFalse(containerTask.equals(anotherTask)); + assertNotEquals("When Objects are not equal then they have different hashcode", + containerTask.hashCode(), anotherTask.hashCode()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/DockerUtilTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/DockerUtilTest.java new file mode 100644 index 00000000..c5270620 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/DockerUtilTest.java @@ -0,0 +1,1139 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.DockerClient; +import com.github.dockerjava.api.command.*; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.exception.NotModifiedException; +import com.github.dockerjava.api.model.*; +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientBuilder; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.api.command.PullImageResultCallback; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.microservice.*; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.reset; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({DockerUtil.class, DefaultDockerClientConfig.class, Configuration.class, DockerClient.class, DockerClientBuilder.class, + StatusReporter.class, ProcessManagerStatus.class, EventsCmd.class, LoggingService.class, ListNetworksCmd.class, Network.class, + Microservice.class, StartContainerCmd.class, InspectContainerResponse.class, InspectContainerCmd.class, StopContainerCmd.class, + RemoveContainerCmd.class, NetworkSettings.class, Container.class, ListContainersCmd.class, MicroserviceStatus.class, RestartStuckChecker.class, + StatsCmd.class, CountDownLatch.class, StatsCallback.class, Statistics.class, HostConfig.class, RemoveImageCmd.class, Registry.class, + PullImageCmd.class, PullImageResultCallback.class, InspectImageCmd.class, CreateContainerCmd.class, CreateContainerResponse.class, LogConfig.class, + PortMapping.class, VolumeMapping.class +}) +public class DockerUtilTest { + private DockerUtil dockerUtil; + private DefaultDockerClientConfig.Builder dockerClientConfig; + private DefaultDockerClientConfig defaultDockerClientConfig; + private DockerClient dockerClient; + private DockerClientBuilder dockerClientBuilder; + private ProcessManagerStatus processManagerStatus; + private EventsCmd eventsCmd; + private ListNetworksCmd listNetworksCmd; + private PullImageCmd pullImageCmd; + private PullImageResultCallback pullImageResultCallback; + private Network network; + private InspectContainerResponse inspectContainerResponse; + private InspectContainerResponse.ContainerState containerState; + private InspectContainerCmd inspectContainerCmd; + private RemoveContainerCmd removeContainerCmd; + private RemoveImageCmd removeImageCmd; + private InspectImageCmd inspectImageCmd; + private CreateContainerCmd createContainerCmd; + private CreateContainerResponse createContainerResponse; + private NetworkSettings networkSettings; + private StatsCmd statsCmd; + private HostConfig hostConfig; + private CountDownLatch countDownLatch; + private Statistics statistics; + private StatsCallback statsCallback; + private Container container; + private Registry registry; + private PortMapping portMapping; + private VolumeMapping volumeMapping; + private LogConfig logConfig; + private List networkList; + private List portMappingList; + private List volumeMappingList; + private Map dockerBridgeMap; + private String bridgeName; + private Microservice microservice; + private StartContainerCmd startContainerCmd; + private StopContainerCmd stopContainerCmd; + private ListContainersCmd listContainersCmd; + private MicroserviceStatus microserviceStatus; + private String containerID; + private String imageID; + private String ipAddress; + private String[] containerNames = {".iofog_containerName1",".iofog_containerName2"}; + private String microserviceUuid = "microserviceUuid"; + private List containerList; + private String MODULE_NAME = "Docker Util"; + private String[] extraHost = {"extraHost1", "extraHost2"}; + private Method method = null; + + @Before + public void setUp() throws Exception { + dockerClientConfig = mock(DefaultDockerClientConfig.Builder.class); + defaultDockerClientConfig = mock(DefaultDockerClientConfig.class); + processManagerStatus = mock(ProcessManagerStatus.class); + dockerClientBuilder = mock(DockerClientBuilder.class); + dockerClient = mock(DockerClient.class); + eventsCmd = mock(EventsCmd.class); + registry = mock(Registry.class); + listNetworksCmd = mock(ListNetworksCmd.class); + microservice = mock(Microservice.class); + startContainerCmd = mock(StartContainerCmd.class); + stopContainerCmd = mock(StopContainerCmd.class); + removeContainerCmd = mock(RemoveContainerCmd.class); + removeImageCmd = mock(RemoveImageCmd.class); + pullImageCmd = mock(PullImageCmd.class); + pullImageResultCallback = mock(PullImageResultCallback.class); + network = mock(Network.class); + inspectImageCmd = mock(InspectImageCmd.class); + hostConfig = mock(HostConfig.class); + inspectContainerResponse = mock(InspectContainerResponse.class); + createContainerResponse = mock(CreateContainerResponse.class); + containerState = mock(InspectContainerResponse.ContainerState.class); + inspectContainerCmd = mock(InspectContainerCmd.class); + networkSettings = mock(NetworkSettings.class); + listContainersCmd = mock(ListContainersCmd.class); + createContainerCmd = mock(CreateContainerCmd.class); + logConfig = mock(LogConfig.class); + container = mock(Container.class); + statsCmd = mock(StatsCmd.class); + countDownLatch = mock(CountDownLatch.class); + statistics = mock(Statistics.class); + statsCallback = mock(StatsCallback.class); + microserviceStatus = mock(MicroserviceStatus.class); + portMapping = mock(PortMapping.class); + volumeMapping = mock(VolumeMapping.class); + networkList = new ArrayList<>(); + containerList = new ArrayList<>(); + networkList.add(network); + containerList.add(container); + dockerBridgeMap = mock(HashMap.class); + portMappingList = new ArrayList<>(); + portMappingList.add(portMapping); + volumeMappingList = new ArrayList<>(); + volumeMappingList.add(volumeMapping); + bridgeName = "default_bridge"; + containerID = "containerID"; + imageID = "imageID"; + ipAddress = "ipAddress"; + dockerBridgeMap.put("com.docker.network.bridge.default_bridge", bridgeName); + mockStatic(DefaultDockerClientConfig.class); + mockStatic(Configuration.class); + mockStatic(DockerClient.class); + mockStatic(DockerClientBuilder.class); + mockStatic(StatusReporter.class); + mockStatic(LoggingService.class); + mockStatic(RestartStuckChecker.class); + PowerMockito.when(DefaultDockerClientConfig.createDefaultConfigBuilder()).thenReturn(dockerClientConfig); + PowerMockito.when(dockerClientConfig.withDockerHost(any())).thenReturn(dockerClientConfig); + PowerMockito.when(dockerClientConfig.withApiVersion(anyString())).thenReturn(dockerClientConfig); + PowerMockito.when(dockerClientConfig.build()).thenReturn(defaultDockerClientConfig); + PowerMockito.when(Configuration.getDockerUrl()).thenReturn("url"); + PowerMockito.when(Configuration.getDockerApiVersion()).thenReturn("1.2"); + PowerMockito.when(DockerClientBuilder.getInstance(any(DockerClientConfig.class))).thenReturn(dockerClientBuilder); + PowerMockito.when(dockerClientBuilder.build()).thenReturn(dockerClient); + PowerMockito.when(dockerClient.eventsCmd()).thenReturn(eventsCmd); + PowerMockito.doAnswer((Answer) invocation -> null).when(eventsCmd).exec(any()); + PowerMockito.when(dockerClient.listNetworksCmd()).thenReturn(listNetworksCmd); + PowerMockito.when(listNetworksCmd.exec()).thenReturn(networkList); + PowerMockito.when(dockerClient.startContainerCmd(anyString())).thenReturn(startContainerCmd); + PowerMockito.doNothing().when(startContainerCmd).exec(); + PowerMockito.when(dockerClient.removeContainerCmd(anyString())).thenReturn(removeContainerCmd); + PowerMockito.when(removeContainerCmd.withForce(anyBoolean())).thenReturn(removeContainerCmd); + PowerMockito.when(removeContainerCmd.withRemoveVolumes(anyBoolean())).thenReturn(removeContainerCmd); + PowerMockito.doNothing().when(removeContainerCmd).exec(); + PowerMockito.when(dockerClient.removeImageCmd(anyString())).thenReturn(removeImageCmd); + PowerMockito.when(removeImageCmd.withForce(anyBoolean())).thenReturn(removeImageCmd); + PowerMockito.doNothing().when(removeImageCmd).exec(); + PowerMockito.when(dockerClient.stopContainerCmd(anyString())).thenReturn(stopContainerCmd); + PowerMockito.doNothing().when(stopContainerCmd).exec(); + PowerMockito.when(dockerClient.pullImageCmd(anyString())).thenReturn(pullImageCmd); + PowerMockito.when(dockerClient.inspectImageCmd(anyString())).thenReturn(inspectImageCmd); + PowerMockito.doAnswer((Answer) invocation -> null).when(inspectImageCmd).exec(); + PowerMockito.when(pullImageCmd.withRegistry(anyString())).thenReturn(pullImageCmd); + PowerMockito.when(pullImageCmd.withTag(anyString())).thenReturn(pullImageCmd); + PowerMockito.when(pullImageCmd.withAuthConfig(any())).thenReturn(pullImageCmd); + PowerMockito.when(pullImageCmd.exec(any())).thenReturn(pullImageResultCallback); + PowerMockito.when(dockerClient.inspectContainerCmd(anyString())).thenReturn(inspectContainerCmd); + PowerMockito.when(dockerClient.createContainerCmd(anyString())).thenReturn(createContainerCmd); + PowerMockito.when(createContainerCmd.withEnv(any(List.class))).thenReturn(createContainerCmd); + PowerMockito.when(createContainerCmd.withName(any())).thenReturn(createContainerCmd); + PowerMockito.when(createContainerCmd.withLabels(any())).thenReturn(createContainerCmd); + PowerMockito.when(createContainerCmd.withExposedPorts(any(ExposedPort.class))).thenReturn(createContainerCmd); + PowerMockito.when(createContainerCmd.withVolumes(any(Volume.class))).thenReturn(createContainerCmd); + PowerMockito.when(createContainerCmd.withCmd(any(List.class))).thenReturn(createContainerCmd); + PowerMockito.when(createContainerCmd.withHostConfig(any(HostConfig.class))).thenReturn(createContainerCmd); + PowerMockito.when(createContainerCmd.exec()).thenReturn(createContainerResponse); + PowerMockito.when(createContainerResponse.getId()).thenReturn(containerID); + PowerMockito.when(inspectContainerCmd.exec()).thenReturn(inspectContainerResponse); + PowerMockito.when(dockerClient.listContainersCmd()).thenReturn(listContainersCmd); + PowerMockito.when(dockerClient.statsCmd(any())).thenReturn(statsCmd); + PowerMockito.when(listContainersCmd.withShowAll(anyBoolean())).thenReturn(listContainersCmd); + PowerMockito.when(listContainersCmd.exec()).thenReturn(containerList); + PowerMockito.when(statsCmd.exec(any())).thenReturn(statsCallback); + PowerMockito.when(statsCallback.getStats()).thenReturn(statistics); + PowerMockito.when(inspectContainerResponse.getState()).thenReturn(containerState); + PowerMockito.when(inspectContainerResponse.getHostConfig()).thenReturn(null); + PowerMockito.when(inspectContainerResponse.getNetworkSettings()).thenReturn(networkSettings); + PowerMockito.when(networkSettings.getIpAddress()).thenReturn(ipAddress); + PowerMockito.when(containerState.getStatus()).thenReturn("UNKNOWN"); + PowerMockito.when(microservice.getContainerId()).thenReturn(containerID); + PowerMockito.when(StatusReporter.setProcessManagerStatus()).thenReturn(processManagerStatus); + PowerMockito.when(container.getNames()).thenReturn(containerNames); + PowerMockito.when(container.getId()).thenReturn(containerID); + PowerMockito.when(portMapping.getInside()).thenReturn(5112); + PowerMockito.when(portMapping.getOutside()).thenReturn(8080); + PowerMockito.when(volumeMapping.getAccessMode()).thenReturn("AUTO"); + PowerMockito.when(volumeMapping.getContainerDestination()).thenReturn("containerDestination"); + PowerMockito.when(volumeMapping.getHostDestination()).thenReturn("hostDestination"); + PowerMockito.when(volumeMapping.getType()).thenReturn(VolumeMappingType.BIND); + PowerMockito.whenNew(MicroserviceStatus.class).withNoArguments().thenReturn(microserviceStatus); + PowerMockito.whenNew(CountDownLatch.class).withArguments(anyInt()).thenReturn(countDownLatch); + PowerMockito.whenNew(StatsCallback.class).withArguments(any(CountDownLatch.class)).thenReturn(statsCallback); + PowerMockito.whenNew(PullImageResultCallback.class).withNoArguments().thenReturn(pullImageResultCallback); + PowerMockito.whenNew(LogConfig.class).withArguments(any(LogConfig.LoggingType.class), any(Map.class)).thenReturn(logConfig); + dockerUtil = spy(DockerUtil.getInstance()); + setMock(dockerUtil); + } + + @After + public void tearDown() throws Exception { + reset(dockerUtil, dockerClient, dockerClientConfig, defaultDockerClientConfig, processManagerStatus, inspectContainerResponse, + hostConfig, inspectContainerCmd, stopContainerCmd, removeContainerCmd, startContainerCmd, listNetworksCmd, statsCmd, statsCallback, containerState, + microservice, container, microserviceStatus) ; + Field instance = DockerUtil.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(null, null); + containerList = null; + networkList = null; + if (method != null) + method.setAccessible(false); + } + + /** + * Set a mock to the {@link DockerUtil} instance + * Throws {@link RuntimeException} in case if reflection failed, see a {@link Field#set(Object, Object)} method description. + * @param mock the mock to be inserted to a class + */ + private void setMock(DockerUtil mock) { + try { + Field instance = DockerUtil.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(instance, mock); + method = DockerUtil.class.getDeclaredMethod("initDockerClient"); + method.setAccessible(true); + method.invoke(dockerUtil); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + /** + * Asserts mock is same as the DockerUtil.getInstance() + */ + @Test + public void testGetInstanceIsSameAsMock() { + assertSame(dockerUtil, DockerUtil.getInstance()); + } + + /** + * Test reInitDockerClient + */ + @Test + public void testReInitDockerClient() { + try { + PowerMockito.doNothing().when(dockerClient).close(); + dockerUtil.reInitDockerClient(); + Mockito.verify(dockerClient).close(); + Mockito.verify(dockerClientConfig, Mockito.atLeastOnce()).withDockerHost(any()); + Mockito.verify(dockerClientConfig, Mockito.atLeastOnce()).withApiVersion(anyString()); + verifyPrivate(dockerUtil, Mockito.atLeastOnce()).invoke("addDockerEventHandler"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getDockerBridgeName when network.getOptions() is empty + */ + @Test + public void tesGetDockerBridgeNameWhenNewtorkIsEmpty() { + assertNull(dockerUtil.getDockerBridgeName()); + } + + + + /** + * test startContainer + */ + @Test + public void testStartContainer() { + dockerUtil.startContainer(microservice); + Mockito.verify(dockerClient).startContainerCmd(anyString()); + Mockito.verify(startContainerCmd).exec(); + } + + /** + * test stopContainer When container is not running + */ + @Test + public void stopContainerWhenContainerIsNotRunning() { + dockerUtil.stopContainer(containerID); + Mockito.verify(dockerClient, Mockito.never()).stopContainerCmd(anyString()); + Mockito.verify(dockerUtil).isContainerRunning(anyString()); + Mockito.verify(dockerUtil).getContainerStatus(anyString()); + } + + /** + * test stopContainer When container running + */ + @Test (expected = NotModifiedException.class) + public void throwsExceptionWhenStopContainerIsCalled() { + PowerMockito.when(containerState.getStatus()).thenReturn("RUNNING"); + PowerMockito.doThrow(spy(new NotModifiedException("Exception"))).when(stopContainerCmd).exec(); + dockerUtil.stopContainer(containerID); + } + + /** + * test stopContainer When container running + */ + @Test + public void teststopContainerWhenContainerIsRunning() { + PowerMockito.when(containerState.getStatus()).thenReturn("RUNNING"); + dockerUtil.stopContainer(containerID); + Mockito.verify(dockerClient).stopContainerCmd(anyString()); + Mockito.verify(dockerUtil).isContainerRunning(anyString()); + Mockito.verify(dockerUtil).getContainerStatus(anyString()); + } + + /** + * Test removeContainer + * throws NotFoundException when container is not found + */ + @Test + public void testRemoveContainer() { + dockerUtil.removeContainer(containerID, true); + Mockito.verify(dockerClient).removeContainerCmd(any()); + Mockito.verify(removeContainerCmd).withForce(any()); + Mockito.verify(removeContainerCmd).withRemoveVolumes(any()); + } + + /** + * Test removeContainer when container is present + * throws NotFoundException when container is not found + */ + @Test (expected = NotFoundException.class) + public void throwsNotFoundExceptionWhenContainerNotFound () { + PowerMockito.doThrow(spy(new NotFoundException("Exception"))).when(removeContainerCmd).exec(); + dockerUtil.removeContainer(containerID, true); + } + + /** + * Test getContainerIpAddress + */ + @Test + public void testGetContainerIpAddress() { + try { + assertEquals(ipAddress, dockerUtil.getContainerIpAddress(containerID)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getContainerIpAddress + * throws AgentSystemException + */ + @Test (expected = AgentSystemException.class) + public void throwsNotFoundExceptionExceptionGetContainerIpAddress() throws AgentSystemException { + PowerMockito.doThrow(spy(new NotFoundException("Exception"))).when(inspectContainerCmd).exec(); + assertEquals(ipAddress, dockerUtil.getContainerIpAddress(containerID)); + } + + /** + * Test getContainerIpAddress + * throws AgentSystemException + */ + @Test (expected = AgentSystemException.class) + public void throwsNotModifiedExceptionGetContainerIpAddress() throws AgentSystemException { + PowerMockito.doThrow(spy(new NotModifiedException("Exception"))).when(inspectContainerCmd).exec(); + assertEquals(ipAddress, dockerUtil.getContainerIpAddress(containerID)); + } + + /** + * Test getContainerName + */ + @Test + public void testGetContainerName() { + assertEquals("iofog_containerName1", dockerUtil.getContainerName(container)); + } + /** + * Test getContainerName + */ + @Test + public void testGetContainerNameWhenThereIsNoContainer() { + String[] containers = {" "}; + PowerMockito.when(container.getNames()).thenReturn(containers); + assertEquals("", dockerUtil.getContainerName(container)); + } + + /** + * Test getContainerMicroserviceUuid + */ + @Test + public void testGetContainerMicroserviceUuidWhenIofogDockerContainerName() { + assertEquals("containerName1", dockerUtil.getContainerMicroserviceUuid(container)); + } + + /** + * Test getContainerMicroserviceUuid + */ + @Test + public void testGetContainerMicroserviceUuid() { + String[] containerNames = {".containerName1",".containerName2"}; + PowerMockito.when(container.getNames()).thenReturn(containerNames); + assertEquals("containerName1", dockerUtil.getContainerMicroserviceUuid(container)); + } + + @Test + public void getIoFogContainerName() { + assertEquals(Constants.IOFOG_DOCKER_CONTAINER_NAME_PREFIX + microserviceUuid, dockerUtil.getIoFogContainerName(microserviceUuid)); + } + + /** + * Test getContainer when microserviceUuid is not found + */ + @Test + public void testGetContainerWhenNotFound() { + assertEquals(Optional.empty(), dockerUtil.getContainer(microserviceUuid)); + } + + /** + * Test getContainer when microserviceUuid is found + */ + @Test + public void testGetContainerWhenFound() { + String[] containerNames = {".microserviceUuid",".microserviceUuid1"}; + PowerMockito.when(container.getNames()).thenReturn(containerNames); + assertEquals(Optional.of(container), dockerUtil.getContainer(microserviceUuid)); + } + + /** + * Test getContainer when microserviceUuid is found + */ + @Test + public void testGetContainerWhenMicroserviceUuidIsblank() { + assertEquals(Optional.empty(), dockerUtil.getContainer("")); + } + + /** + * Test getMicroserviceStatus + * When containerState is Null + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsNull() { + PowerMockito.when(inspectContainerResponse.getState()).thenReturn(null); + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus, Mockito.never()).getContainerId(); + Mockito.verify(microserviceStatus, Mockito.never()).getContainerId(); + } + + /** + * Test getMicroserviceStatus + * When containerState is UNKNOWN + * containerState.getStartedAt() is null + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsContainerStateIsUnknown() { + try { + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus).setContainerId(any()); + Mockito.verify(microserviceStatus).setStatus(any()); + Mockito.verify(microserviceStatus).setUsage(any()); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getMicroserviceStatus + * When containerState is RUNNING + * containerState.getStartedAt() is null + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsContainerStateIsRunning() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("RUNNING"); + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus).setContainerId(any()); + Mockito.verify(microserviceStatus).setStatus(any()); + Mockito.verify(microserviceStatus).setUsage(any()); + Mockito.verify(microserviceStatus, Mockito.never()).setStartTime(anyLong()); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getMicroserviceStatus + * When containerState is start + * containerState.getStartedAt() is null + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsContainerStateIsStart() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("START"); + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus).setContainerId(any()); + Mockito.verify(microserviceStatus).setStatus(any()); + Mockito.verify(microserviceStatus).setUsage(any()); + Mockito.verify(microserviceStatus, Mockito.never()).setStartTime(anyLong()); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getMicroserviceStatus + * When containerState is stop + * containerState.getStartedAt() is null + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsContainerStateIsStop() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("STOP"); + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus).setContainerId(any()); + Mockito.verify(microserviceStatus).setStatus(any()); + Mockito.verify(microserviceStatus).setUsage(any()); + Mockito.verify(microserviceStatus, Mockito.never()).setStartTime(anyLong()); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getMicroserviceStatus + * When containerState is destroy + * containerState.getStartedAt() is null + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsContainerStateIsDestroy() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("DESTROY"); + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus).setContainerId(any()); + Mockito.verify(microserviceStatus).setStatus(any()); + Mockito.verify(microserviceStatus).setUsage(any()); + Mockito.verify(microserviceStatus, Mockito.never()).setStartTime(anyLong()); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getMicroserviceStatus + * When containerState is restart + * containerState.getStartedAt() is null + * RestartStuckChecker.isStuck() returns false + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsContainerStateIsRestart() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("EXITED"); + PowerMockito.when(RestartStuckChecker.isStuck(any())).thenReturn(false); + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus).setContainerId(any()); + Mockito.verify(microserviceStatus).setStatus(eq(MicroserviceState.EXITING)); + Mockito.verify(microserviceStatus).setUsage(any()); + Mockito.verify(microserviceStatus, Mockito.never()).setStartTime(anyLong()); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + PowerMockito.verifyStatic(RestartStuckChecker.class); + RestartStuckChecker.isStuck(any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getMicroserviceStatus + * When containerState is restart + * containerState.getStartedAt() is null + * RestartStuckChecker.isStuck() returns true + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsContainerStateIsRestartIsStuck() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("EXITED"); + PowerMockito.when(RestartStuckChecker.isStuck(any())).thenReturn(true); + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus).setContainerId(any()); + Mockito.verify(microserviceStatus).setStatus(eq(MicroserviceState.STUCK_IN_RESTART)); + Mockito.verify(microserviceStatus).setUsage(any()); + Mockito.verify(microserviceStatus, Mockito.never()).setStartTime(anyLong()); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + PowerMockito.verifyStatic(RestartStuckChecker.class); + RestartStuckChecker.isStuck(any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test getMicroserviceStatus + * When containerState is restart + * containerState.getStartedAt() is null + * RestartStuckChecker.isStuck() returns false + */ + @Test + public void testGetMicroserviceStatusWhenExecReturnsContainerStateIsCreating() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("CREATED"); + PowerMockito.when(RestartStuckChecker.isStuckInContainerCreation(any())).thenReturn(false); + assertEquals(microserviceStatus, dockerUtil.getMicroserviceStatus(containerID, microserviceUuid)); + Mockito.verify(microserviceStatus).setContainerId(any()); + Mockito.verify(microserviceStatus).setStatus(eq(MicroserviceState.CREATED)); + Mockito.verify(microserviceStatus).setUsage(any()); + Mockito.verify(microserviceStatus, Mockito.never()).setStartTime(anyLong()); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + PowerMockito.verifyStatic(RestartStuckChecker.class); + RestartStuckChecker.isStuckInContainerCreation(any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getRunningContainers when none of the container is not RUNNING + */ + @Test + public void testGetRunningContainersWhenContainersAreNotRunning() { + try { + assertEquals(0, dockerUtil.getRunningContainers().size()); + Mockito.verify(dockerUtil).getContainers(); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getRunningContainers when none of the container is not RUNNING + */ + @Test + public void testGetRunningContainersWhenContainersAreRunning() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("RUNNING"); + List list = dockerUtil.getRunningContainers(); + assertEquals(containerList, list); + assertEquals(1, list.size()); + Mockito.verify(dockerUtil).getContainers(); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getRunningIofogContainers when IOFOG Containers are not RUNNING + */ + @Test + public void testGetRunningIofogContainersWhenContainersAreNotRunning() { + try { + //PowerMockito.when(containerState.getStatus()).thenReturn("RUNNING"); + List list = dockerUtil.getRunningIofogContainers(); + assertEquals(0, list.size()); + Mockito.verify(dockerUtil).getContainers(); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + + /** + * Test getRunningIofogContainers when IOFOG Containers are not RUNNING + */ + @Test + public void testGetRunningIofogContainersWhenContainersAreRunning() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("RUNNING"); + List list = dockerUtil.getRunningIofogContainers(); + assertEquals(1, list.size()); + assertEquals(containerList, list); + Mockito.verify(dockerUtil).getContainers(); + PowerMockito.verifyPrivate(dockerUtil).invoke("containerToMicroserviceState", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getContainerStats when stats are present + */ + @Test + public void testGetContainerStatsWhenStatsArePresent() { + assertEquals(Optional.of(statistics), dockerUtil.getContainerStats(containerID)); + Mockito.verify(statsCmd).exec(any()); + Mockito.verify(dockerClient).statsCmd(any()); + Mockito.verify(statsCallback).getStats(); + } + + /** + * Test getContainerStats when stats are not present + */ + @Test + public void testGetContainerStatsWhenStatsAreNotPresent() { + PowerMockito.when(statsCallback.getStats()).thenReturn(null); + assertEquals(Optional.empty(), dockerUtil.getContainerStats(containerID)); + Mockito.verify(statsCmd).exec(any()); + Mockito.verify(dockerClient).statsCmd(any()); + Mockito.verify(statsCallback).getStats(); + } + + /** + * Test getContainerStats when stats are not present + */ + @Test + public void throwsExceptionWhenExecIsCalledInGetContainerStatsWhenStatsAreNotPresent() { + try { + PowerMockito.doThrow(spy(new InterruptedException("InterruptedException"))).when(countDownLatch).await(anyLong(), any(TimeUnit.class)); + assertEquals(Optional.of(statistics), dockerUtil.getContainerStats(containerID)); + Mockito.verify(statsCmd).exec(any()); + Mockito.verify(dockerClient).statsCmd(any()); + Mockito.verify(statsCallback).getStats(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error while getting Container Stats for container id: " + containerID), any()); + } catch (InterruptedException e) { + fail("This should not happen"); + } + } + + /** + * Test getContainerStartedAt when getStartedAt returns null + */ + @Test + public void testGetContainerStartedAt() { + assertNotNull(dockerUtil.getContainerStartedAt(containerID)); + Mockito.verify(dockerClient).inspectContainerCmd(any()); + Mockito.verify(inspectContainerResponse).getState(); + Mockito.verify(containerState).getStartedAt(); + } + + /** + * Test getContainerStartedAt when getStartedAt returns value + */ + @Test + public void testGetContainerStartedAtWhenReturnStartTime() { + Instant startAt = Instant.now(); + PowerMockito.when(containerState.getStartedAt()).thenReturn(String.valueOf(startAt)); + assertEquals(startAt.toEpochMilli(), dockerUtil.getContainerStartedAt(containerID)); + Mockito.verify(dockerClient).inspectContainerCmd(any()); + Mockito.verify(inspectContainerResponse).getState(); + Mockito.verify(containerState).getStartedAt(); + } + + /** + * Test areMicroserviceAndContainerEqual when microservice and container are not equal + * getHostConfig is null + * + */ + @Test + public void testAreMicroserviceAndContainerEqualWhenContainerAndMicorserivceAreNotEqual() { + try { + assertFalse(dockerUtil.areMicroserviceAndContainerEqual(containerID, microservice)); + PowerMockito.verifyPrivate(dockerUtil).invoke("isPortMappingEqual", any(), any()); + PowerMockito.verifyPrivate(dockerUtil).invoke("isNetworkModeEqual", any(), any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test areMicroserviceAndContainerEqual when microservice and container are not equal + * getHostConfig is null + * + */ + @Test + public void testAreMicroserviceAndContainerEqualWhenContainerAndMicorserivceAreEqual() { + try { + PowerMockito.when(inspectContainerResponse.getHostConfig()).thenReturn(hostConfig); + PowerMockito.when(hostConfig.getExtraHosts()).thenReturn(extraHost); + PowerMockito.when(hostConfig.getNetworkMode()).thenReturn("host"); + PowerMockito.when(microservice.isRootHostAccess()).thenReturn(true); + assertTrue(dockerUtil.areMicroserviceAndContainerEqual(containerID, microservice)); + PowerMockito.verifyPrivate(dockerUtil).invoke("isPortMappingEqual", any(), any()); + PowerMockito.verifyPrivate(dockerUtil).invoke("isNetworkModeEqual", any(), any()); + PowerMockito.verifyPrivate(dockerUtil).invoke("getMicroservicePorts", any()); + PowerMockito.verifyPrivate(dockerUtil).invoke("getContainerPorts", any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getContainerStatus when status is UNKNOWN + */ + @Test + public void testGetContainerStatus() { + assertEquals(Optional.of("UNKNOWN"), dockerUtil.getContainerStatus(containerID)); + Mockito.verify(dockerClient).inspectContainerCmd(any()); + Mockito.verify(inspectContainerResponse).getState(); + } + + /** + * Test getContainerStatus when status is null + */ + @Test + public void testGetContainerStatusWhenContainerStateIsNull() { + PowerMockito.when(inspectContainerResponse.getState()).thenReturn(null); + assertEquals(Optional.empty(), dockerUtil.getContainerStatus(containerID)); + Mockito.verify(dockerClient).inspectContainerCmd(any()); + Mockito.verify(inspectContainerResponse).getState(); + } + + /** + * Test getContainerStatus + * throws Exception when dockerClient.inspectContainerCmd is called + */ + @Test + public void throwsExceptionWhenExecISCalledGetContainerStatus() { + PowerMockito.doThrow(spy(new NotFoundException("Exception"))).when(inspectContainerCmd).exec(); + assertEquals(Optional.empty(), dockerUtil.getContainerStatus(containerID)); + Mockito.verify(dockerClient).inspectContainerCmd(any()); + Mockito.verify(inspectContainerResponse, Mockito.never()).getState(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Error getting container status"), any()); + } + + /** + * Test isContainerRunning + */ + @Test + public void testIsContainerRunningWhenContainerStateIsStopped() { + try { + assertFalse(dockerUtil.isContainerRunning(containerID)); + PowerMockito.verifyPrivate(dockerUtil).invoke("getContainerStatus", any()); + Mockito.verify(dockerClient).inspectContainerCmd(any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test isContainerRunning when status is RUNNING + */ + @Test + public void testIsContainerRunningWhenContainerStateIsRunning() { + try { + PowerMockito.when(containerState.getStatus()).thenReturn("RUNNING"); + assertTrue(dockerUtil.isContainerRunning(containerID)); + PowerMockito.verifyPrivate(dockerUtil).invoke("getContainerStatus", any()); + Mockito.verify(dockerClient).inspectContainerCmd(any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getContainers returns list + */ + @Test + public void testGetContainersWhenReturnsList() { + assertEquals(containerList, dockerUtil.getContainers()); + Mockito.verify(dockerClient).listContainersCmd(); + } + + /** + * Test getContainers returns null + */ + @Test + public void testGetContainersWhenReturnsNull() { + PowerMockito.when(listContainersCmd.exec()).thenReturn(null); + assertNull(dockerUtil.getContainers()); + Mockito.verify(dockerClient).listContainersCmd(); + } + /** + * Test getContainers returns null + */ + @Test (expected = NotFoundException.class) + public void throwsExceptionWhenExecIsCalledGetContainers() { + PowerMockito.doThrow(spy(new NotFoundException("Exception"))).when(listContainersCmd).exec(); + dockerUtil.getContainers(); + } + + /** + * Test removeImageById when imageID is found + */ + @Test + public void testRemoveImageById() { + dockerUtil.removeImageById(imageID); + Mockito.verify(dockerClient).removeImageCmd(anyString()); + } + + /** + * Test removeImageById when imageID is not found + * throws NotFoundException + */ + @Test (expected = NotFoundException.class) + public void throwsNotFoundExceptionWhenRemoveImageById() { + PowerMockito.doThrow(spy(new NotFoundException("Exception"))).when(removeImageCmd).exec(); + dockerUtil.removeImageById(imageID); + } + + /** + * Test pullImage + * throws AgentSystemException + */ + @Test (expected = AgentSystemException.class) + public void testPullImageWhenRegistryIsNull() throws AgentSystemException { + dockerUtil.pullImage(imageID, containerID,null); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Image not found"), any()); + + + } + + /** + * Test pullImage + * when registry IsPublic + */ + @Test + public void testPullImageWhenRegistryIsNotNullAndPublic() { + try { + PowerMockito.when(registry.getUrl()).thenReturn("url"); + PowerMockito.when(registry.getIsPublic()).thenReturn(true); + imageID = "agent:1.3.0-beta"; + dockerUtil.pullImage(imageID, containerID, registry); + Mockito.verify(dockerClient).pullImageCmd(any()); + Mockito.verify(pullImageCmd).withRegistry(any()); + Mockito.verify(pullImageCmd).withTag(any()); + Mockito.verify(pullImageCmd).exec(any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } + } + + /** + * Test pullImage + * when registry IsPublic + */ + @Test + public void testPullImageWhenRegistryIsNotPublic() { + try { + PowerMockito.when(registry.getUrl()).thenReturn("registryUrl"); + PowerMockito.when(registry.getUserEmail()).thenReturn("user@gmail.com"); + PowerMockito.when(registry.getUserName()).thenReturn("user"); + PowerMockito.when(registry.getPassword()).thenReturn("password"); + PowerMockito.when(registry.getUrl()).thenReturn("registryUrl"); + PowerMockito.when(registry.getIsPublic()).thenReturn(false); + imageID = "agent:1.3.0-beta"; + containerID ="id"; + dockerUtil.pullImage(imageID, containerID, registry); + Mockito.verify(dockerClient).pullImageCmd(any()); + Mockito.verify(pullImageCmd, Mockito.never()).withRegistry(any()); + Mockito.verify(pullImageCmd).withTag(any()); + Mockito.verify(pullImageCmd).withAuthConfig(any()); + Mockito.verify(pullImageCmd).exec(any()); + } catch (AgentSystemException e) { + fail("This should not happen"); + } + } + + /** + * Test pullImage + * when registry IsPublic + * throws AgentSystemException + */ + @Test (expected = AgentSystemException.class) + public void throwsNotFoundExceptionPullImage() throws AgentSystemException { + PowerMockito.doThrow(spy(new NotFoundException("Exception"))).when(pullImageCmd).exec(any()); + PowerMockito.when(registry.getUrl()).thenReturn("url"); + PowerMockito.when(registry.getIsPublic()).thenReturn(true); + imageID = "agent:1.3.0-beta"; + dockerUtil.pullImage(imageID, containerID, registry); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Image not found"), any()); + } + + /** + * Test pullImage + * when registry IsPublic + * throws AgentSystemException when DockerClient throws NotModifiedException + */ + @Test (expected = AgentSystemException.class) + public void throwsNotModifiedExceptionExceptionPullImage() throws AgentSystemException { + PowerMockito.doThrow(spy(new NotModifiedException("Exception"))).when(pullImageCmd).exec(any()); + PowerMockito.when(registry.getUrl()).thenReturn("url"); + PowerMockito.when(registry.getIsPublic()).thenReturn(true); + imageID = "agent:1.3.0-beta"; + dockerUtil.pullImage(imageID, containerID, registry); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(eq(MODULE_NAME), eq("Image not found"), any()); + } + + /** + * Test findLocalImage + */ + @Test + public void testFindLocalImageIsPresent() { + assertTrue(dockerUtil.findLocalImage(imageID)); + Mockito.verify(dockerClient).inspectImageCmd(any()); + Mockito.verify(inspectImageCmd).exec(); + } + + /** + * Test findLocalImage when not found + */ + @Test + public void testFindLocalImageIsNotPresent() { + PowerMockito.doThrow(spy(new NotFoundException("Exception"))).when(inspectImageCmd).exec(); + assertFalse(dockerUtil.findLocalImage(imageID)); + Mockito.verify(dockerClient).inspectImageCmd(any()); + Mockito.verify(inspectImageCmd).exec(); + } + + /** + * Test createContainer + * When microservice.getPortMappings are present + */ + @Test + public void testCreateContainerWhenPortMappingsArePresent() { + PowerMockito.when(microservice.getPortMappings()).thenReturn(portMappingList); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + assertEquals(containerID, dockerUtil.createContainer(microservice, "host")); + Mockito.verify(createContainerCmd).exec(); + Mockito.verify(createContainerCmd).withHostConfig(any(HostConfig.class)); + Mockito.verify(createContainerCmd).withLabels(any()); + Mockito.verify(createContainerCmd, Mockito.never()).withVolumes(any(Volume.class)); + Mockito.verify(createContainerCmd, Mockito.never()).withCmd(any(List.class)); + } + + /** + * Test createContainer + * When microservice.getPortMappings are present + * microservice.getVolumeMappings are present + * microservice.isRootHostAccess false + */ + @Test + public void testCreateContainerWhenPortMappingsAndBindVolumeMappingsArePresent() { + PowerMockito.when(microservice.getPortMappings()).thenReturn(portMappingList); + PowerMockito.when(microservice.getVolumeMappings()).thenReturn(volumeMappingList); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + assertEquals(containerID, dockerUtil.createContainer(microservice, "host")); + Mockito.verify(microservice).isRootHostAccess(); + Mockito.verify(createContainerCmd).withHostConfig(any(HostConfig.class)); + Mockito.verify(createContainerCmd).withLabels(any()); + Mockito.verify(createContainerCmd, Mockito.never()).withCmd(any(List.class)); + } + + /** + * Test createContainer + * When microservice.getPortMappings are present + * microservice.getVolumeMappings are present + * microservice.isRootHostAccess true + */ + @Test + public void testCreateContainerWhenPortMappingsAndBindVolumeMappingsArePresentWithRootAccess() { + List args = new ArrayList<>(); + args.add("args"); + PowerMockito.when(microservice.getPortMappings()).thenReturn(portMappingList); + PowerMockito.when(microservice.isRootHostAccess()).thenReturn(true); + PowerMockito.when(microservice.getArgs()).thenReturn(args); + PowerMockito.when(microservice.getVolumeMappings()).thenReturn(volumeMappingList); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + assertEquals(containerID, dockerUtil.createContainer(microservice, "host")); + Mockito.verify(createContainerCmd).withHostConfig(any(HostConfig.class)); + Mockito.verify(createContainerCmd).withExposedPorts(any(ExposedPort.class)); + Mockito.verify(createContainerCmd).withLabels(any()); + Mockito.verify(createContainerCmd).withCmd(any(List.class)); + } + + /** + * Test createContainer + * When microservice.getExtraHosts are present + */ + @Test + public void testCreateContainerWhenExtraHostsIsPresent() { + List extraHosts = new ArrayList<>(); + String host = "extra-host:1.2.3.4"; + extraHosts.add(host); + PowerMockito.when(microservice.isRootHostAccess()).thenReturn(false); + PowerMockito.when(microservice.getExtraHosts()).thenReturn(extraHosts); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + assertEquals(containerID, dockerUtil.createContainer(microservice, "host")); + Mockito.verify(createContainerCmd).withHostConfig(argThat((HostConfig hostConfig) -> { + String[] hosts = hostConfig.getExtraHosts(); + return hosts.length == 2 && hosts[0].equals(host); + })); + } + + /** + * Test createContainer + * When microservice.getPortMappings are present + * microservice.getVolumeMappings are present + * microservice.isRootHostAccess true + * throws NotFoundException + */ + @Test (expected = NotFoundException.class) + public void throwsNotFoundCreateContainerWhenPortMappingsAndVolumeMappingsArePresentWithRootAccess() { + PowerMockito.doThrow(spy(new NotFoundException("Exception"))).when(createContainerCmd).exec(); + PowerMockito.when(microservice.getPortMappings()).thenReturn(portMappingList); + PowerMockito.when(microservice.getImageName()).thenReturn("microserviceName"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + assertEquals(containerID, dockerUtil.createContainer(microservice, "host")); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerStatusTest.java new file mode 100644 index 00000000..882fe4fd --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerStatusTest.java @@ -0,0 +1,175 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.microservice.MicroserviceState; +import org.eclipse.iofog.microservice.MicroserviceStatus; +import org.eclipse.iofog.microservice.Registry; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ProcessManagerStatus.class, MicroserviceStatus.class, MicroserviceManager.class, MicroserviceState.class}) +public class ProcessManagerStatusTest { + private ProcessManagerStatus processManagerStatus; + private JsonArrayBuilder arrayBuilder; + private MicroserviceStatus microserviceStatus; + private MicroserviceManager microserviceManager; + private MicroserviceState microserviceState; + private String microserviceUuid; + private List registries; + private Registry registry; + + @Before + public void setUp() throws Exception { + processManagerStatus = spy(new ProcessManagerStatus()); + microserviceStatus = mock(MicroserviceStatus.class); + microserviceManager = mock(MicroserviceManager.class); + registry = mock(Registry.class); + microserviceState = mock(MicroserviceState.class); + arrayBuilder = Json.createArrayBuilder(); + registries = new ArrayList<>(); + mockStatic(MicroserviceManager.class); + microserviceUuid = "microserviceUuid"; + PowerMockito.when(microserviceStatus.getStatus()).thenReturn(MicroserviceState.RUNNING); + PowerMockito.when(MicroserviceManager.getInstance()).thenReturn(microserviceManager); + PowerMockito.whenNew(MicroserviceStatus.class).withNoArguments().thenReturn(microserviceStatus); + } + + @After + public void tearDown() throws Exception { + reset(processManagerStatus); + microserviceUuid = null; + } + + /** + * Test getJsonMicroservicesStatus when microservicesStatus is empty + */ + @Test + public void testGetJsonMicroservicesStatus() { + assertEquals(arrayBuilder.build().toString(), processManagerStatus.getJsonMicroservicesStatus()); + } + + /** + * Test getJsonMicroservicesStatus when microservicesStatus is not empty + */ + @Test + public void testGetAndSetJsonMicroservicesStatus() { + assertEquals(processManagerStatus, processManagerStatus.setMicroservicesStatus(microserviceUuid, microserviceStatus)); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("id")); + } + + /** + * Test getJsonMicroservicesStatus when microservicesStatus is not empty + * status.getContainerId() is not null + */ + @Test + public void testGetAndSetJsonMicroservicesStatusWhenContainerIdIsNotNull() { + PowerMockito.when(microserviceStatus.getContainerId()).thenReturn("id"); + assertEquals(processManagerStatus, processManagerStatus.setMicroservicesStatus(microserviceUuid, microserviceStatus)); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("containerId")); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("startTime")); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("operatingDuration")); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("cpuUsage")); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("memoryUsage")); + PowerMockito.when(microserviceStatus.getStartTime()).thenReturn(System.currentTimeMillis()); + PowerMockito.when(microserviceStatus.getOperatingDuration()).thenReturn(System.currentTimeMillis()); + PowerMockito.when(microserviceStatus.getCpuUsage()).thenReturn(100f); + PowerMockito.when(microserviceStatus.getMemoryUsage()).thenReturn(100l); + assertEquals(processManagerStatus, processManagerStatus.setMicroservicesStatus(microserviceUuid, microserviceStatus)); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("containerId")); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("startTime")); + } + + /** + * Test setMicroservicesStatus when uuid & status are null + */ + @Test + public void testSetMicroservicesStatusWhenStatusAndUuidIsNull() { + assertEquals(processManagerStatus, processManagerStatus.setMicroservicesStatus(null, null)); + assertTrue(processManagerStatus.getJsonMicroservicesStatus().contains("id")); + } + + @Test + public void testGetJsonRegistriesStatus() { + assertEquals(arrayBuilder.build().toString(), processManagerStatus.getJsonRegistriesStatus()); + } + + /** + * Test getRunningMicroservicesCount + */ + @Test + public void testGetRunningMicroservicesCount() { + assertEquals(0, processManagerStatus.getRunningMicroservicesCount()); + assertEquals(processManagerStatus, processManagerStatus.setRunningMicroservicesCount(2)); + assertEquals(2, processManagerStatus.getRunningMicroservicesCount()); + } + + /** + * Test getMicroserviceStatus & setMicroserviceState + */ + @Test + public void testSetAndGetMicroservicesState() { + assertEquals(processManagerStatus,processManagerStatus.setMicroservicesState(microserviceUuid, microserviceState)); + assertEquals(microserviceStatus, processManagerStatus.getMicroserviceStatus(microserviceUuid)); + assertEquals(microserviceStatus, processManagerStatus.getMicroserviceStatus("id")); + } + + /** + * Test removeNotRunningMicroserviceStatus + */ + @Test + public void testRemoveNotRunningMicroserviceStatus() { + processManagerStatus.setMicroservicesStatus(microserviceUuid, microserviceStatus); + processManagerStatus.removeNotRunningMicroserviceStatus(); + verify(microserviceStatus, times(2)).getStatus(); + } + + /** + * Test getRegistriesCount + */ + @Test + public void tstGetRegistriesCount() { + assertEquals(0, processManagerStatus.getRegistriesCount()); + } + + /** + * Test getRegistriesStatus + */ + @Test + public void getRegistriesStatus() { + assertEquals(0, processManagerStatus.getRegistriesStatus().size()); + + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerTest.java new file mode 100644 index 00000000..dcdff799 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerTest.java @@ -0,0 +1,565 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.model.Container; +import org.eclipse.iofog.diagnostics.strace.StraceDiagnosticManager; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.field_agent.FieldAgentStatus; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.microservice.MicroserviceState; +import org.eclipse.iofog.microservice.MicroserviceStatus; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.supervisor.SupervisorStatus; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.*; + +import static org.eclipse.iofog.process_manager.ContainerTask.Tasks.REMOVE; +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.mock; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ProcessManager.class, StatusReporter.class, LoggingService.class, ProcessManagerStatus.class, MicroserviceManager.class, + DockerUtil.class, ContainerManager.class, Microservice.class, Container.class, Thread.class, SupervisorStatus.class, Configuration.class, + ContainerTask.class, StraceDiagnosticManager.class, MicroserviceStatus.class, FieldAgentStatus.class}) +public class ProcessManagerTest { + private ProcessManager processManager; + private ProcessManagerStatus processManagerStatus; + private SupervisorStatus supervisorStatus; + private MicroserviceManager microserviceManager; + private Microservice microservice; + private Container container; + private DockerUtil dockerUtil; + private ContainerManager containerManager; + private ContainerTask containerTask; + private MicroserviceStatus microserviceStatus; + private StraceDiagnosticManager straceDiagnosticManager; + private FieldAgentStatus fieldAgentStatus; + private Thread thread; + private String MODULE_NAME; + List microservicesList; + List containerList; + Map label; + private Method method = null; + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Process Manager"; + processManager = PowerMockito.spy(ProcessManager.getInstance()); + setMock(processManager); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(StatusReporter.class); + PowerMockito.mockStatic(DockerUtil.class); + PowerMockito.mockStatic(MicroserviceManager.class); + PowerMockito.mockStatic(Configuration.class); + PowerMockito.mockStatic(StraceDiagnosticManager.class); + processManagerStatus = mock(ProcessManagerStatus.class); + microserviceManager = mock(MicroserviceManager.class); + microservice = mock(Microservice.class); + supervisorStatus = mock(SupervisorStatus.class); + container = mock(Container.class); + dockerUtil = mock(DockerUtil.class); + containerTask = mock(ContainerTask.class); + containerManager = mock(ContainerManager.class); + microserviceStatus = mock(MicroserviceStatus.class); + straceDiagnosticManager = mock(StraceDiagnosticManager.class); + fieldAgentStatus = mock(FieldAgentStatus.class); + microservicesList = new ArrayList<>(); + microservicesList.add(microservice); + containerList = new ArrayList<>(); + containerList.add(container); + label = new HashMap<>(); + label.put("iofog-uuid", "uuid"); + PowerMockito.when(DockerUtil.getInstance()).thenReturn(dockerUtil); + PowerMockito.when(StatusReporter.getProcessManagerStatus()).thenReturn(processManagerStatus); + PowerMockito.when(StatusReporter.setProcessManagerStatus()).thenReturn(processManagerStatus); + PowerMockito.when(StatusReporter.setSupervisorStatus()).thenReturn(supervisorStatus); + PowerMockito.when(StatusReporter.getFieldAgentStatus()).thenReturn(fieldAgentStatus); + PowerMockito.when(dockerUtil.getRunningContainers()).thenReturn(containerList); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(MicroserviceManager.getInstance()).thenReturn(microserviceManager); + PowerMockito.when(StraceDiagnosticManager.getInstance()).thenReturn(straceDiagnosticManager); + PowerMockito.doNothing().when(straceDiagnosticManager).disableMicroserviceStraceDiagnostics(anyString()); + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(microservicesList); + PowerMockito.when(microserviceManager.getCurrentMicroservices()).thenReturn(microservicesList); + PowerMockito.whenNew(ContainerManager.class).withNoArguments().thenReturn(containerManager); + PowerMockito.when(Configuration.isWatchdogEnabled()).thenReturn(false); + PowerMockito.when(Configuration.getIofogUuid()).thenReturn("Uuid"); + PowerMockito.whenNew(ContainerTask.class).withArguments(Mockito.any(), Mockito.anyString()) + .thenReturn(containerTask); + } + + @After + public void tearDown() throws Exception { + Field instance = ProcessManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(null, null); + MODULE_NAME = null; + containerList = null; + label = null; + reset(microserviceManager, container, dockerUtil, processManager, microservice, container, microserviceStatus); + if (method != null) + method.setAccessible(false); + } + + /** + * Set a mock to the {@link ProcessManager} instance + * Throws {@link RuntimeException} in case if reflection failed, see a {@link Field#set(Object, Object)} method description. + * @param mock the mock to be inserted to a class + */ + private void setMock(ProcessManager mock) { + try { + Field instance = ProcessManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(instance, mock); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Test getModuleIndex + */ + @Test + public void testGetModuleIndex() { + assertEquals(Constants.PROCESS_MANAGER, processManager.getModuleIndex()); + } + + /** + * Test getModuleName + */ + @Test + public void testGetModuleName() { + assertEquals(MODULE_NAME, processManager.getModuleName()); + } + + /** + * Asserts mock is same as the ProcessManager.getInstance() + */ + @Test + public void testGetInstanceIsSameAsMock() { + assertSame(processManager, ProcessManager.getInstance()); + } + + /** + * Test update + */ + @Test + public void testUpdate() { + try { + processManager.update(); + PowerMockito.verifyPrivate(processManager).invoke("updateRegistriesStatus"); + PowerMockito.verifyStatic(StatusReporter.class, Mockito.atLeastOnce()); + StatusReporter.getProcessManagerStatus(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test deleteRemainingMicroservices + */ + @Test + public void testDeleteRemainingMicroserviceswhenThereIsOnlyOneMicroserviceRunning() { + try { + PowerMockito.when(dockerUtil.getContainerMicroserviceUuid(Mockito.any())).thenReturn( "Anotheruuid"); + PowerMockito.when(dockerUtil.getContainerName(Mockito.any())).thenReturn("containerName"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid", "anotherUUid"); + initiateMockStart(); + processManager.deleteRemainingMicroservices(); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(microserviceManager).getCurrentMicroservices(); + PowerMockito.verifyPrivate(processManager).invoke("deleteOldAgentContainers", Mockito.any(Set.class)); + PowerMockito.verifyPrivate(processManager).invoke("deleteUnknownContainers", Mockito.any(Set.class)); + PowerMockito.verifyPrivate(processManager).invoke("disableMicroserviceFeaturesBeforeRemoval", Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test deleteRemainingMicroservices + */ + @Test + public void testDeleteRemainingMicroservicesWhenThereAreMultipleMicroservicesRunning() { + try { + microservicesList.add(microservice); + microservicesList.add(microservice); + containerList.add(container); + containerList.add(container); + PowerMockito.when(Configuration.isWatchdogEnabled()).thenReturn(true); + PowerMockito.when(container.getLabels()).thenReturn(label); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid", "anotherUuid", "uuid1"); + PowerMockito.when(dockerUtil.getContainerMicroserviceUuid(Mockito.any())).thenReturn("Containeruuid", "uuid", "anotherUuid"); + PowerMockito.when(dockerUtil.getContainerName(Mockito.any())).thenReturn("containerName", "containerName1", "containerName2"); + PowerMockito.when(dockerUtil.getIoFogContainerName(Mockito.any())).thenReturn("containerName", "containerName1", "containerName2"); + initiateMockStart(); + processManager.deleteRemainingMicroservices(); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(microserviceManager).getCurrentMicroservices(); + PowerMockito.verifyPrivate(processManager).invoke("deleteOldAgentContainers", Mockito.any(Set.class)); + PowerMockito.verifyPrivate(processManager).invoke("deleteUnknownContainers", Mockito.any(Set.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test deleteRemainingMicroservices + * getLatestMicroservices returns null; + */ + /*@Test + public void testDeleteRemainingMicroserviceswhenGetLatestMicroservicesReturnNull() { + try { + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(null); + PowerMockito.when(dockerUtil.getContainerMicroserviceUuid(Mockito.any())).thenReturn( "Anotheruuid"); + PowerMockito.when(dockerUtil.getContainerName(Mockito.any())).thenReturn("containerName"); + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid", "anotherUUid"); + initiateMockStart(); + processManager.deleteRemainingMicroservices(); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(microserviceManager).getCurrentMicroservices(); + PowerMockito.verifyPrivate(processManager).invoke("deleteOldAgentContainers", Mockito.any(Set.class)); + PowerMockito.verifyPrivate(processManager).invoke("deleteUnknownContainers", Mockito.any(Set.class)); + } catch (Exception e) { + fail("This should not happen"); + } + }*/ + + /** + * Test instanceConfigUpdated + */ + @Test + public void testInstanceConfigUpdated() { + try { + PowerMockito.doNothing().when(dockerUtil).reInitDockerClient(); + initiateMockStart(); + processManager.instanceConfigUpdated(); + verify(dockerUtil).reInitDockerClient(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test HandleLatestMicroservices + * When getContainer returns Empty + * Microservice is marked with delete false + */ + @Test + public void testPrivateMethodHandleLatestMicroservicesWhenGetContainersReturnEmpty() { + try { + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(microservice.isUpdating()).thenReturn(false); + PowerMockito.when(microservice.isDelete()).thenReturn(false); + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(microservicesList); + PowerMockito.when(dockerUtil.getContainer(Mockito.any())).thenReturn(Optional.empty()); + initiateMockStart(); + method = ProcessManager.class.getDeclaredMethod("handleLatestMicroservices"); + method.setAccessible(true); + method.invoke(processManager); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(dockerUtil).getContainer(any()); + Mockito.verify(microservice).isDelete(); + Mockito.verify(microservice).isUpdating(); + PowerMockito.verifyPrivate(processManager, never()).invoke("addMicroservice", Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test HandleLatestMicroservices + * When getContainer returns container + * Microservice is marked with delete false + * Microservice and container are equal + * Microservice status is running + */ + @Test + public void testPrivateMethodHandleLatestMicroservicesWhenGetContainersReturnContainer() { + try { + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(microservice.isUpdating()).thenReturn(false); + PowerMockito.when(microservice.isDelete()).thenReturn(false); + PowerMockito.when(microservice.isRebuild()).thenReturn(true); + PowerMockito.when(container.getId()).thenReturn("containerId"); + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(microservicesList); + PowerMockito.when(dockerUtil.getContainer(Mockito.any())).thenReturn(Optional.of(container)); + PowerMockito.when(dockerUtil.getMicroserviceStatus(Mockito.any(), Mockito.any())).thenReturn(microserviceStatus); + PowerMockito.when(dockerUtil.getContainerIpAddress(Mockito.any())).thenReturn("containerIpAddress"); + PowerMockito.when(dockerUtil.areMicroserviceAndContainerEqual(Mockito.any(), Mockito.any())).thenReturn(true); + PowerMockito.when(microserviceStatus.getStatus()).thenReturn(MicroserviceState.RUNNING); + initiateMockStart(); + method = ProcessManager.class.getDeclaredMethod("handleLatestMicroservices"); + method.setAccessible(true); + method.invoke(processManager); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(dockerUtil).getContainer(any()); + Mockito.verify(microservice, Mockito.times(2)).isDelete(); + Mockito.verify(microservice).isUpdating(); + PowerMockito.verifyPrivate(processManager).invoke("updateMicroservice", Mockito.any(), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test HandleLatestMicroservices + * When getContainer returns container + * Microservice is marked with delete true + * Microservice and container are equal + * Microservice status is running + * isDeleteWithCleanup true + */ + @Test + public void testPrivateMethodHandleLatestMicroservicesWhenGetContainersReturnContainerAndMicroserviceIsMarkedDelete() { + try { + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(microservice.isUpdating()).thenReturn(false); + PowerMockito.when(microservice.isDelete()).thenReturn(true); + PowerMockito.when(microservice.isRebuild()).thenReturn(true); + PowerMockito.when(microservice.isDeleteWithCleanup()).thenReturn(true); + PowerMockito.when(container.getId()).thenReturn("containerId"); + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(microservicesList); + PowerMockito.when(dockerUtil.getContainer(Mockito.any())).thenReturn(Optional.of(container)); + PowerMockito.when(dockerUtil.getMicroserviceStatus(Mockito.any(), Mockito.any())).thenReturn(microserviceStatus); + PowerMockito.when(dockerUtil.getContainerIpAddress(Mockito.any())).thenReturn("containerIpAddress"); + PowerMockito.when(dockerUtil.areMicroserviceAndContainerEqual(Mockito.any(), Mockito.any())).thenReturn(true); + PowerMockito.when(microserviceStatus.getStatus()).thenReturn(MicroserviceState.RUNNING); + initiateMockStart(); + method = ProcessManager.class.getDeclaredMethod("handleLatestMicroservices"); + method.setAccessible(true); + method.invoke(processManager); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(dockerUtil).getContainer(any()); + Mockito.verify(microservice, Mockito.times(1)).isDelete(); + Mockito.verify(microservice).isUpdating(); + PowerMockito.verifyPrivate(processManager).invoke("deleteMicroservice", Mockito.any()); + PowerMockito.verifyPrivate(processManager).invoke("disableMicroserviceFeaturesBeforeRemoval", Mockito.any()); + PowerMockito.verifyPrivate(processManager).invoke("addTask", Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test HandleLatestMicroservices + * When getContainer returns container + * Microservice is marked with delete true + * Microservice and container are equal + * Microservice status is running + * isDeleteWithCleanup false + */ + @Test + public void testPrivateMethodHandleLatestMicroservicesWhenIsDeleteWithCleanupIsFalse() { + try { + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(microservice.isUpdating()).thenReturn(false); + PowerMockito.when(microservice.isDelete()).thenReturn(true); + PowerMockito.when(microservice.isRebuild()).thenReturn(true); + PowerMockito.when(microservice.isDeleteWithCleanup()).thenReturn(false); + PowerMockito.when(container.getId()).thenReturn("containerId"); + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(microservicesList); + PowerMockito.when(dockerUtil.getContainer(Mockito.any())).thenReturn(Optional.of(container)); + PowerMockito.when(dockerUtil.getMicroserviceStatus(Mockito.any(), Mockito.any())).thenReturn(microserviceStatus); + PowerMockito.when(dockerUtil.getContainerIpAddress(Mockito.any())).thenReturn("containerIpAddress"); + PowerMockito.when(dockerUtil.areMicroserviceAndContainerEqual(Mockito.any(), Mockito.any())).thenReturn(true); + PowerMockito.when(microserviceStatus.getStatus()).thenReturn(MicroserviceState.RUNNING); + initiateMockStart(); + method = ProcessManager.class.getDeclaredMethod("handleLatestMicroservices"); + method.setAccessible(true); + method.invoke(processManager); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(dockerUtil).getContainer(any()); + Mockito.verify(microservice, Mockito.times(1)).isDelete(); + Mockito.verify(microservice).isUpdating(); + PowerMockito.verifyPrivate(processManager).invoke("deleteMicroservice", Mockito.any()); + PowerMockito.verifyPrivate(processManager).invoke("disableMicroserviceFeaturesBeforeRemoval", Mockito.any()); + PowerMockito.verifyPrivate(processManager).invoke("addTask", Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test HandleLatestMicroservices + * When getContainer returns container + * Microservice is marked with delete false + * Microservice and container are equal + * Microservice status is running + * Microservice isDeleteWithCleanup is true + * getContainerIpAddress throws AgentSystemException + */ + @Test + public void throwsAgentSystemExceptionWhenGetContainerIpAddressIsCalledInHandleLatestMicroservice() { + try { + PowerMockito.when(microservice.getMicroserviceUuid()).thenReturn("uuid"); + PowerMockito.when(microservice.isUpdating()).thenReturn(false); + PowerMockito.when(microservice.isDelete()).thenReturn(false); + PowerMockito.when(microservice.isRebuild()).thenReturn(true); + PowerMockito.when(microservice.isDeleteWithCleanup()).thenReturn(false); + PowerMockito.when(container.getId()).thenReturn("containerId"); + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(microservicesList); + PowerMockito.when(dockerUtil.getContainer(Mockito.any())).thenReturn(Optional.of(container)); + PowerMockito.when(dockerUtil.getMicroserviceStatus(Mockito.any(), Mockito.any())).thenReturn(microserviceStatus); + PowerMockito.doThrow(mock(AgentSystemException.class)).when(dockerUtil).getContainerIpAddress(Mockito.any()); + PowerMockito.when(dockerUtil.areMicroserviceAndContainerEqual(Mockito.any(), Mockito.any())).thenReturn(true); + PowerMockito.when(microserviceStatus.getStatus()).thenReturn(MicroserviceState.RUNNING); + initiateMockStart(); + method = ProcessManager.class.getDeclaredMethod("handleLatestMicroservices"); + method.setAccessible(true); + method.invoke(processManager); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(dockerUtil).getContainer(any()); + Mockito.verify(microservice, Mockito.times(2)).isDelete(); + PowerMockito.verifyPrivate(processManager).invoke("updateMicroservice", Mockito.any(), Mockito.any()); + verify(microservice).setContainerIpAddress(any()); + + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test updateRunningMicroservicesCount + * + */ + @Test + public void testPrivateMethodUpdateRunningMicroservicesCount() { + try { + initiateMockStart(); + method = ProcessManager.class.getDeclaredMethod("updateRunningMicroservicesCount"); + method.setAccessible(true); + method.invoke(processManager); + PowerMockito.verifyStatic(StatusReporter.class); + StatusReporter.setProcessManagerStatus(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test updateCurrentMicroservices + * + */ + @Test + public void testPrivateMethodUpdateCurrentMicroservices() { + try { + initiateMockStart(); + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(microservicesList); + method = ProcessManager.class.getDeclaredMethod("updateCurrentMicroservices"); + method.setAccessible(true); + method.invoke(processManager); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(microserviceManager).setCurrentMicroservices(any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test retryTask + * when retries is more than 1 + */ + @Test + public void testPrivateMethodRetryTask() { + try { + initiateMockStart(); + PowerMockito.when(containerTask.getAction()).thenReturn(REMOVE); + PowerMockito.when(containerTask.getRetries()).thenReturn(1); + PowerMockito.when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + method = ProcessManager.class.getDeclaredMethod("retryTask", ContainerTask.class); + method.setAccessible(true); + method.invoke(processManager, containerTask); + Mockito.verify(containerTask).incrementRetries(); + PowerMockito.verifyPrivate(processManager).invoke("addTask", Mockito.any()); + } catch (Exception e) { + System.out.println(e); + fail("This should not happen"); + } + } + + /** + * Test retryTask + * when retries is more than 5 + */ + @Test + public void testPrivateMethodRetryTaskMoreThanFive() { + try { + initiateMockStart(); + PowerMockito.when(containerTask.getAction()).thenReturn(REMOVE); + PowerMockito.when(containerTask.getRetries()).thenReturn(6); + PowerMockito.when(fieldAgentStatus.getControllerStatus()).thenReturn(Constants.ControllerStatus.OK); + method = ProcessManager.class.getDeclaredMethod("retryTask", ContainerTask.class); + method.setAccessible(true); + method.invoke(processManager, containerTask); + Mockito.verify(containerTask, never()).incrementRetries(); + PowerMockito.verifyPrivate(processManager, never()).invoke("addTask", Mockito.any()); + PowerMockito.verifyStatic(StatusReporter.class); + StatusReporter.setProcessManagerStatus(); + } catch (Exception e) { + System.out.println(e); + fail("This should not happen"); + } + } + + /** + * Test start + */ + @Test + public void testStart() { + initiateMockStart(); + PowerMockito.verifyStatic(DockerUtil.class); + DockerUtil.getInstance(); + PowerMockito.verifyStatic(MicroserviceManager.class); + MicroserviceManager.getInstance(); + PowerMockito.verifyStatic(StatusReporter.class); + StatusReporter.setSupervisorStatus(); + } + + /** + * Helper method + */ + public void initiateMockStart() { + thread = mock(Thread.class); + try { + PowerMockito.whenNew(Thread.class).withParameterTypes(Runnable.class,String.class) + .withArguments(Mockito.any(Runnable.class), Mockito.anyString()) + .thenReturn(thread); + PowerMockito.doNothing().when(thread).start(); + processManager.start(); + } catch (Exception e) { + fail("this should not happen"); + } + + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/RestartStuckCheckerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/RestartStuckCheckerTest.java new file mode 100644 index 00000000..c171bacf --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/RestartStuckCheckerTest.java @@ -0,0 +1,52 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.reset; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({RestartStuckChecker.class}) +public class RestartStuckCheckerTest { + + /** + * Test isStuck is False for first five times + */ + @Test + public void testIsStuckFirstTime() { + assertFalse(RestartStuckChecker.isStuck("containerId")); + } + + /** + * Test isStuck is true when called 11th time for the same containerID + */ + @Test + public void testIsStuckSixthTime() { + for (int i =1 ; i<=10 ; i++){ + RestartStuckChecker.isStuck("uuid"); + } + assertTrue(RestartStuckChecker.isStuck("uuid")); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/StatsCallbackTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/StatsCallbackTest.java new file mode 100644 index 00000000..82222ade --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/StatsCallbackTest.java @@ -0,0 +1,90 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.process_manager; + +import com.github.dockerjava.api.model.Statistics; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.concurrent.CountDownLatch; + +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({StatsCallback.class, CountDownLatch.class, Statistics.class}) +public class StatsCallbackTest { + private StatsCallback statsCallback; + private CountDownLatch countDownLatch; + private Statistics statistics; + + @Before + public void setUp() throws Exception { + countDownLatch = mock(CountDownLatch.class); + statistics = mock(Statistics.class); + statsCallback = spy(new StatsCallback(countDownLatch)); + } + + @After + public void tearDown() throws Exception { + Mockito.reset(countDownLatch, statsCallback); + } + + /** + * Test getStats is null when called without onNExt + */ + @Test + public void testGetStats() { + assertNull(statsCallback.getStats()); + } + + /** + * Test onNext + */ + @Test + public void testOnNext() { + statsCallback.onNext(statistics); + assertEquals(statistics, statsCallback.getStats()); + } + + /** + * Test gotStats + */ + @Test + public void testGotStats() { + assertFalse(statsCallback.gotStats()); + statsCallback.onNext(statistics); + assertTrue(statsCallback.gotStats()); + } + + /** + * Test rest + */ + @Test + public void testReset() { + statsCallback.onNext(statistics); + assertTrue(statsCallback.gotStats()); + statsCallback.reset(); + assertFalse(statsCallback.gotStats()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/pruning/DockerPruningManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/pruning/DockerPruningManagerTest.java new file mode 100644 index 00000000..493519d1 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/pruning/DockerPruningManagerTest.java @@ -0,0 +1,233 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.pruning; + +import com.github.dockerjava.api.model.Container; +import com.github.dockerjava.api.model.Image; +import com.github.dockerjava.api.model.PruneResponse; +import org.eclipse.iofog.microservice.Microservice; +import org.eclipse.iofog.microservice.MicroserviceManager; +import org.eclipse.iofog.process_manager.DockerUtil; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ScheduledExecutorService; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.powermock.api.mockito.PowerMockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({DockerPruningManager.class, DockerUtil.class, MicroserviceManager.class, Image.class, Container.class, LoggingService.class, + ScheduledExecutorService.class}) +public class DockerPruningManagerTest { + private DockerPruningManager pruningManager; + private DockerUtil dockerUtil; + private MicroserviceManager microserviceManager; + private Container container = null; + private String unwantedImageID = null; + private List images = null; + private Method method = null; + private PruneResponse pruneResponse = null; + + + @Before + public void setUp() throws Exception { + setMock(pruningManager); + dockerUtil = Mockito.mock(DockerUtil.class); + mockStatic(MicroserviceManager.class); + mockStatic(DockerUtil.class); + mockStatic(LoggingService.class); + microserviceManager = PowerMockito.mock(MicroserviceManager.class); + PowerMockito.when(DockerUtil.getInstance()).thenReturn(dockerUtil); + PowerMockito.when(MicroserviceManager.getInstance()).thenReturn(microserviceManager); + images = new ArrayList<>(); + Image unwantedImage = Mockito.mock(Image.class); + Image wantedImage = Mockito.mock(Image.class); + String[] unwantedTags = {"none:"}; + String[] wantedTags = {"edgeworx/calibration-sensors-arm"}; + PowerMockito.when(unwantedImage.getRepoTags()).thenReturn(unwantedTags); + unwantedImageID = "unwantedImage"; + PowerMockito.when(unwantedImage.getId()).thenReturn(unwantedImageID); + PowerMockito.when(wantedImage.getRepoTags()).thenReturn(wantedTags); + images.add(unwantedImage); + images.add(wantedImage); + PowerMockito.when(dockerUtil.getImages()).thenReturn(images); + List latestMicroservices = new ArrayList<>(); + Microservice microservice = new Microservice("uuid", "edgeworx/calibration-sensors-arm"); + latestMicroservices.add(microservice); + PowerMockito.when(microserviceManager.getLatestMicroservices()).thenReturn(latestMicroservices); + pruneResponse = mock(PruneResponse.class); + PowerMockito.when(dockerUtil.dockerPrune()).thenReturn(pruneResponse); + PowerMockito.doNothing().when(dockerUtil).removeImageById(anyString()); + ScheduledExecutorService scheduler = mock(ScheduledExecutorService.class); + pruningManager = PowerMockito.spy(DockerPruningManager.getInstance()); + container = Mockito.mock(Container.class); + + + } + + @After + public void tearDown() throws Exception { + container = null; + images = null; + pruneResponse = null; + Field instance = DockerPruningManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(null, null); + Mockito.reset(microserviceManager, dockerUtil, pruningManager); + pruningManager = null; + if (method != null) + method.setAccessible(false); + + } + + /** + * Set a mock to the {@link DockerPruningManager} instance + * Throws {@link RuntimeException} in case if reflection failed, see a {@link Field#set(Object, Object)} method description. + * @param mock the mock to be inserted to a class + */ + private void setMock(DockerPruningManager mock) { + try { + Field instance = DockerPruningManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(instance, mock); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Test GetUnwantedImagesList when no non iofog containers are running + */ + @Test + public void testGetUnwantedImagesListWhenNoNonIoFogContainersRunning() { + Set imageIDs = pruningManager.getUnwantedImagesList(); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(dockerUtil).getRunningNonIofogContainers(); + Mockito.verify(dockerUtil).getImages(); + assertTrue(imageIDs.contains(unwantedImageID)); + assertTrue(imageIDs.size() == 1); + } + + /** + * Test GetUnwantedImagesList when non iofog containers are running + */ + @Test + public void testGetUnwantedImagesListWhenNonIoFogContainersRunning() { + String[] nonIoFogTags = {"wheelOfFortune"}; + Image nonIoFogImage = Mockito.mock(Image.class); + images.add(nonIoFogImage); + PowerMockito.when(nonIoFogImage.getRepoTags()).thenReturn(nonIoFogTags); + List nonIoFogRunningContainer = new ArrayList<>(); + nonIoFogRunningContainer.add(container); + PowerMockito.when(container.getImageId()).thenReturn("wheelOfFortune"); + PowerMockito.when(dockerUtil.getRunningNonIofogContainers()).thenReturn(nonIoFogRunningContainer); + Set imageIDs = pruningManager.getUnwantedImagesList(); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(dockerUtil).getRunningNonIofogContainers(); + Mockito.verify(dockerUtil).getImages(); + assertTrue(imageIDs.contains(unwantedImageID)); + assertTrue(!imageIDs.contains("wheelOfFortune")); + assertTrue(imageIDs.size() == 2); + } + + /** + * Test GetUnwantedImagesList when no images are found + */ + @Test + public void testGetUnwantedImagesListWhenNoImagesAreFound() { + List images = new ArrayList<>(); + PowerMockito.when(dockerUtil.getImages()).thenReturn(images); + List nonIoFogRunningContainer = new ArrayList<>(); + nonIoFogRunningContainer.add(container); + PowerMockito.when(container.getImageId()).thenReturn(unwantedImageID); + PowerMockito.when(dockerUtil.getRunningNonIofogContainers()).thenReturn(nonIoFogRunningContainer); + Set imageIDs = pruningManager.getUnwantedImagesList(); + Mockito.verify(microserviceManager).getLatestMicroservices(); + Mockito.verify(dockerUtil).getRunningNonIofogContainers(); + Mockito.verify(dockerUtil).getImages(); + assertTrue(!imageIDs.contains(unwantedImageID)); + assertTrue(imageIDs.size() == 0); + } + + /** + * Test removeImagesById when no images to be pruned + */ + @Test + public void testRemoveImageByIDWhenNoImagesToBeRemoved() { + try { + Set imagesList = new HashSet<>(); + method = DockerPruningManager.class.getDeclaredMethod("removeImagesById", Set.class); + method.setAccessible(true); + method.invoke(pruningManager, imagesList); + Mockito.verify(dockerUtil, never()).removeImageById(anyString()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logInfo("Docker Manager", "Start removing image by ID size : " + imagesList.size()); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logInfo("Docker Manager", "Finished removing image by ID"); + } catch (Exception e){ + fail("This should never happen"); + } + } + /** + * Test removeImagesById when images to be pruned + */ + @Test + public void testRemoveImageByIDWhenImagesToBeRemoved() { + try { + String id = "image-uuid"; + Set imagesList = new HashSet<>(); + imagesList.add(id); + method = DockerPruningManager.class.getDeclaredMethod("removeImagesById", Set.class); + method.setAccessible(true); + method.invoke(pruningManager, imagesList); + Mockito.verify(dockerUtil, atLeastOnce()).removeImageById(eq(id)); + + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo("Docker Manager", "Start removing image by ID size : " + imagesList.size()); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo("Docker Manager", "Removing unwanted image id : " + id); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo("Docker Manager", "Finished removing image by ID"); + } catch (Exception e){ + fail("This should never happen"); + } + } + + @Test + public void testPruneAgent() { + PowerMockito.when(pruneResponse.getSpaceReclaimed()).thenReturn(1223421l); + String response = pruningManager.pruneAgent(); + Mockito.verify(dockerUtil, atLeastOnce()).dockerPrune(); + assertTrue(response.equals("\nSuccess - pruned dangling docker images, total reclaimed space: " + pruneResponse.getSpaceReclaimed())); + } +} diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatusTest.java new file mode 100644 index 00000000..8acfbc1e --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatusTest.java @@ -0,0 +1,81 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.resource_consumption_manager; + + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.spy; + +/** + * Agent Exception + * + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ResourceConsumptionManagerStatus.class}) +public class ResourceConsumptionManagerStatusTest { + private ResourceConsumptionManagerStatus resourceConsumptionManagerStatus; + private float memoryUsage; + private float diskUsage; + private float cpuUsage; + private boolean memoryViolation; + private boolean diskViolation; + private boolean cpuViolation; + private long availableMemory; + private float totalCpu; + private long availableDisk; + + @Before + public void setUp() throws Exception { + resourceConsumptionManagerStatus = spy(new ResourceConsumptionManagerStatus()); + memoryUsage = 1000f; + diskUsage = 1000f; + cpuUsage = 1000f; + totalCpu = 1000f; + memoryViolation = true; + diskViolation = true; + cpuViolation = true; + availableMemory = 10000L; + availableDisk = 10000L; + + } + + @After + public void tearDown() throws Exception { + resourceConsumptionManagerStatus = null; + } + + /** + * Test getters and setters + */ + @Test + public void testGetterAndSetter() { + assertEquals(memoryUsage, resourceConsumptionManagerStatus.setMemoryUsage(memoryUsage).getMemoryUsage(), 0f); + assertEquals(diskUsage, resourceConsumptionManagerStatus.setDiskUsage(diskUsage).getDiskUsage(), 0f); + assertEquals(cpuUsage, resourceConsumptionManagerStatus.setCpuUsage(cpuUsage).getCpuUsage(), 0f); + assertEquals(totalCpu, resourceConsumptionManagerStatus.setTotalCpu(totalCpu).getTotalCpu(), 0f); + assertEquals(availableMemory, resourceConsumptionManagerStatus.setAvailableMemory(availableMemory).getAvailableMemory(), 0f); + assertEquals(availableDisk, resourceConsumptionManagerStatus.setAvailableDisk(availableDisk).getAvailableDisk(), 0f); + assertEquals(memoryViolation, resourceConsumptionManagerStatus.setMemoryViolation(memoryViolation).isMemoryViolation()); + assertEquals(diskViolation, resourceConsumptionManagerStatus.setDiskViolation(diskViolation).isDiskViolation()); + assertEquals(cpuViolation, resourceConsumptionManagerStatus.setCpuViolation(cpuViolation).isCpuViolation()); + + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerTest.java new file mode 100644 index 00000000..946fe915 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerTest.java @@ -0,0 +1,576 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.resource_consumption_manager; + +import org.eclipse.iofog.command_line.util.CommandShellExecutor; +import org.eclipse.iofog.command_line.util.CommandShellResultSet; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.whenNew; + +/** + * Agent Exception + * + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ResourceConsumptionManager.class, LoggingService.class, + Configuration.class, CommandShellExecutor.class, StatusReporter.class, Thread.class}) +public class ResourceConsumptionManagerTest { + private ResourceConsumptionManager resourceConsumptionManager; + private static final String MODULE_NAME = "Resource Consumption Manager"; + private File dir = null; + private File tagFile = null; + private CommandShellResultSet, List> resultSetWithPath = null; + private List error; + private List value; + private Method method = null; + private Thread thread; + + @Before + public void setUp() throws Exception { + resourceConsumptionManager = spy(ResourceConsumptionManager.class); + setMock(resourceConsumptionManager); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(Configuration.class); + PowerMockito.mockStatic(CommandShellExecutor.class); + PowerMockito.mockStatic(StatusReporter.class); + PowerMockito.when(Configuration.getGetUsageDataFreqSeconds()).thenReturn(1l); + PowerMockito.when(Configuration.getMemoryLimit()).thenReturn(1.0f); + PowerMockito.when(Configuration.getCpuLimit()).thenReturn(1.0f); + PowerMockito.when(Configuration.getDiskLimit()).thenReturn(1.0f); + PowerMockito.when(Configuration.getDiskDirectory()).thenReturn(""); + thread = PowerMockito.mock(Thread.class); + whenNew(Thread.class).withParameterTypes(Runnable.class,String.class).withArguments(Mockito.any(Runnable.class), Mockito.anyString()).thenReturn(thread); + PowerMockito.doNothing().when(thread).start(); + } + /** + * Set a mock to the {@link ResourceConsumptionManager} instance + * Throws {@link RuntimeException} in case if reflection failed, see a {@link Field#set(Object, Object)} method description. + * @param mock the mock to be inserted to a class + */ + private void setMock(ResourceConsumptionManager mock) { + try { + Field instance = ResourceConsumptionManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(instance, mock); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @After + public void tearDown() throws Exception { + if(tagFile != null && tagFile.exists()){ + tagFile.delete(); + } + if(dir != null && dir.exists()){ + dir.delete(); + } + if (method != null) + method.setAccessible(false); + resultSetWithPath = null; + error = null; + value = null; + Field instance = ResourceConsumptionManager.class.getDeclaredField("instance"); + instance.setAccessible(true); + instance.set(null, null); + } + + /** + * Asserts module index of resource manager is equal to constant value + */ + @Test + public void testGetModuleIndex() { + assertEquals(Constants.RESOURCE_CONSUMPTION_MANAGER, resourceConsumptionManager.getModuleIndex()); + } + + /** + * Asserts module name of resource manager is equal to constant value + */ + @Test + public void testGetModuleName() { + assertEquals("Resource Consumption Manager", resourceConsumptionManager.getModuleName()); + } + + /** + * Asserts mock is same as the StraceDiagnosticManager.getInstance() + */ + @Test + public void testGetInstanceIsSameAsMock() { + assertSame(resourceConsumptionManager, ResourceConsumptionManager.getInstance()); + } + + /** + * Asserts instance config updates + */ + @Test + public void testInstanceConfigUpdated() throws Exception{ + Field privateDiskLimitField = ResourceConsumptionManager.class. + getDeclaredField("diskLimit"); + Field privateCpuLimitField = ResourceConsumptionManager.class. + getDeclaredField("cpuLimit"); + Field privateMemoryLimitField = ResourceConsumptionManager.class. + getDeclaredField("memoryLimit"); + privateDiskLimitField.setAccessible(true); + privateCpuLimitField.setAccessible(true); + privateMemoryLimitField.setAccessible(true); + resourceConsumptionManager.instanceConfigUpdated(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logInfo(MODULE_NAME, + "Start Configuration instance updated"); + LoggingService.logInfo(MODULE_NAME, + "Finished Config updated"); + assertEquals(1.0E9f, privateDiskLimitField.get(resourceConsumptionManager)); + assertEquals(1.0f, privateCpuLimitField.get(resourceConsumptionManager)); + assertEquals(1000000.0f, privateMemoryLimitField.get(resourceConsumptionManager)); + privateDiskLimitField.setAccessible(false); + privateCpuLimitField.setAccessible(false); + privateMemoryLimitField.setAccessible(false); + + } + + /** + * Test start method + */ + @Test + public void testStartThread() { + resourceConsumptionManager.start(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Starting"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "started"); + } + + /** + * Test DirectorySize method when file don't exist then return length 0 + */ + @Test + public void testDirectorySizeWhenFileDontExist() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("directorySize", String.class); + method.setAccessible(true); + long output = (long) method.invoke(resourceConsumptionManager, "file"); + assertEquals(0f, output,0f); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Inside get directory size"); + } + + /** + * Test DirectorySize method when file don't exist then return length 0 + */ + @Test + public void testDirectorySizeWhenFileExist() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("directorySize", String.class); + method.setAccessible(true); + tagFile = new File("file"); + FileWriter fw=new FileWriter(tagFile); + fw.write("Token"); + fw.close(); + long output = (long) method.invoke(resourceConsumptionManager, "file"); + assertEquals(5f, output,0f); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Inside get directory size"); + } + + /** + * Test DirectorySize method when directory exist without file then return length 0 + */ + @Test + public void testDirectorySizeWhenDirectoryExist() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("directorySize", String.class); + method.setAccessible(true); + dir = new File("dir"); + dir.mkdirs(); + long output = (long) method.invoke(resourceConsumptionManager, "dir"); + assertEquals(0f, output,0f); + } + + /** + * Test DirectorySize method when directory exist with empty file then return length 0 + */ + @Test + public void testDirectorySizeWhenDirectoryWithEmptyFileExist() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("directorySize", String.class); + method.setAccessible(true); + dir = new File("emptyDir"); + dir.mkdirs(); + tagFile=new File("emptyDir","emptyFile.txt"); + tagFile.createNewFile(); + long output = (long) method.invoke(resourceConsumptionManager, "emptyDir"); + assertEquals(0f, output,0f); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Inside get directory size"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished directory size : " + output); + + } + + /** + * Test DirectorySize method when file exist then return length 0 + */ + @Test + public void testDirectorySizeWhenDirectoryWithFileContentExist() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("directorySize", String.class); + method.setAccessible(true); + dir = new File("dir"); + dir.mkdirs(); + tagFile=new File("dir","newFile.txt"); + tagFile.createNewFile(); + FileWriter fw=new FileWriter(tagFile); + fw.write("Token"); + fw.close(); + long output = (long) method.invoke(resourceConsumptionManager, "dir"); + assertEquals(5f, output,0f); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Inside get directory size"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished directory size : " + output); + } + + /** + * Test removeArchives method + */ + @Test + public void testRemoveArchives() throws Exception{ + float amount = 0f; + method = ResourceConsumptionManager.class.getDeclaredMethod("removeArchives", float.class); + method.setAccessible(true); + method.invoke(resourceConsumptionManager, amount); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start remove archives : " + amount); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished remove archives : "); + PowerMockito.verifyStatic(Configuration.class, Mockito.atLeastOnce()); + Configuration.getDiskDirectory(); + } + + /** + * Test getMemoryUsage method + */ + @Test + public void testGetMemoryUsage() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getMemoryUsage"); + method.setAccessible(true); + float memoryUsage = (float) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get memory usage"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get memory usage : " + memoryUsage); + } + + /** + * Test getCpuUsage method + */ + @Test + public void testGetCpuUsage() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getCpuUsage"); + method.setAccessible(true); + float response = (float) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get cpu usage"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get cpu usage : " + response); + } + + /** + * Test getSystemAvailableMemory method when executeCommand returns nothing + */ + @Test + public void testGetSystemAvailableMemoryWhenExecuteCommandReturnsNothing() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getSystemAvailableMemory"); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + long output = (long) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get system available memory"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get system available memory : " + output); + } + + /** + * Test getSystemAvailableMemory method when executeCommand returns value 200 + */ + @Test + public void testGetSystemAvailableMemoryWhenExecuteCommandReturnsValue() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getSystemAvailableMemory"); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add("200"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + long output = (long) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get system available memory"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get system available memory : " + output); + } + + /** + * Test getSystemAvailableMemory method when executeCommand returns error + */ + @Test + public void testGetSystemAvailableMemoryWhenExecuteCommandReturnsError() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getSystemAvailableMemory"); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + long output = (long) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get system available memory"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get system available memory : " + output); + } + + /** + * Test getTotalCpu method when executeCommand returns nothing + */ + @Test + public void testGetTotalCpuWhenExecuteCommandReturnsNothing() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getTotalCpu"); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + float output = (float) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get total cpu"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get total cpu : " + output); + } + + /** + * Test getTotalCpu method when executeCommand returns value + */ + @Test + public void testGetTotalCpuWhenExecuteCommandReturnsValue() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getTotalCpu"); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add("5000"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + float output = (float) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get total cpu"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get total cpu : " + output); + } + + /** + * Test getTotalCpu method when executeCommand returns error + */ + @Test + public void testGetTotalCpuWhenExecuteCommandReturnsError() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getTotalCpu"); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + float output = (float) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get total cpu"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get total cpu : " + output); + } + + /** + * Test getTotalCpu method when executeCommand returns null + */ + @Test + public void testGetTotalCpuWhenExecuteCommandReturnsNull() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getTotalCpu"); + method.setAccessible(true); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + float output = (float) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get total cpu"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get total cpu : " + output); + } + + /** + * Test getAvailableDisk method + */ + @Test + public void testGetAvailableDisk() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getAvailableDisk"); + method.setAccessible(true); + long output = (long) method.invoke(resourceConsumptionManager); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Start get available disk"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Finished get available disk : " + output); + } + + /** + * Test parseStat method when process Id is empty + */ + @Test + public void throwsFileNotFoundExceptionWhenParseStatIsPassedWithEmptyProcessId() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("parseStat", String.class); + method.setAccessible(true); + method.invoke(resourceConsumptionManager, ""); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, + "Inside parse Stat"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logError(any(), any(), anyObject()); + } + + /** + * Test parseStat method when process Id is empty + */ + @Test + public void testParseStatWhenProcessIdIsPassed() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("parseStat", String.class); + method.setAccessible(true); + method.invoke(resourceConsumptionManager, "1111"); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug(MODULE_NAME, "Inside parse Stat"); + } + + /** + * Test getWinCPUUsage method when executeCommand returns null + */ + @Test + public void testGetWinCPUUsageWhenExecuteCommandReturnsNull() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getWinCPUUsage", String.class); + method.setAccessible(true); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + String output = (String) method.invoke(resourceConsumptionManager, "1111"); + assertEquals("0", output); + } + /** + * Test getWinCPUUsage method when pid is empty + */ + @Test + public void testGetWinCPUUsageWhenPidIsEmpty() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getWinCPUUsage", String.class); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + String output = (String) method.invoke(resourceConsumptionManager, ""); + assertEquals("0", output); + } + + /** + * Test getWinCPUUsage method when executeCommand returns error + */ + @Test + public void testGetWinCPUUsageWhenExecuteCommandReturnsError() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getWinCPUUsage", String.class); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + error.add("error"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + String output = (String) method.invoke(resourceConsumptionManager, "1"); + assertEquals("0", output); + } + + /** + * Test getWinCPUUsage method when executeCommand returns empty + */ + @Test + public void testGetWinCPUUsageWhenExecuteCommandReturnsEmpty() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getWinCPUUsage", String.class); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + String output = (String) method.invoke(resourceConsumptionManager, "1"); + assertEquals("0", output); + } + + /** + * Test getWinCPUUsage method when executeCommand returns value + */ + @Test + public void testGetWinCPUUsageWhenExecuteCommandReturnsValue() throws Exception{ + method = ResourceConsumptionManager.class.getDeclaredMethod("getWinCPUUsage", String.class); + method.setAccessible(true); + error = new ArrayList<>(); + value = new ArrayList<>(); + value.add("400"); + resultSetWithPath = new CommandShellResultSet<>(value, error); + when(CommandShellExecutor.executeCommand(any())).thenReturn(resultSetWithPath); + String output = (String) method.invoke(resourceConsumptionManager, "1"); + assertEquals("400", output); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerStatusTest.java new file mode 100644 index 00000000..f78fd4ef --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerStatusTest.java @@ -0,0 +1,51 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.resource_manager; + +import static org.junit.Assert.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ResourceManagerStatus.class}) +public class ResourceManagerStatusTest { + private ResourceManagerStatus resourceManagerStatus; + private String hwInfo = "Info"; + private String usbConnectionsInfo = "USB_INFO"; + + @Before + public void setUp() throws Exception { + resourceManagerStatus = spy(new ResourceManagerStatus()); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void testGetterAndSetter() { + resourceManagerStatus.setHwInfo(hwInfo); + resourceManagerStatus.setUsbConnectionsInfo(usbConnectionsInfo); + assertEquals(hwInfo, resourceManagerStatus.getHwInfo()); + assertEquals(usbConnectionsInfo, resourceManagerStatus.getUsbConnectionsInfo()); + } + +} diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerTest.java new file mode 100644 index 00000000..0540e6bc --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerTest.java @@ -0,0 +1,81 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.resource_manager; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.*; +import org.eclipse.iofog.utils.Constants; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ResourceManager.class, FieldAgent.class, LoggingService.class}) +public class ResourceManagerTest { + private ResourceManager resourceManager; + private FieldAgent fieldAgent; + private Thread getUsageData; + + /** + * @throws Exception + */ + @Before + public void setUp() throws Exception { + resourceManager = spy(new ResourceManager()); + fieldAgent = mock(FieldAgent.class); + PowerMockito.mockStatic(FieldAgent.class); + PowerMockito.mockStatic(LoggingService.class); + when(FieldAgent.getInstance()).thenReturn(fieldAgent); + PowerMockito.doNothing().when(fieldAgent).sendUSBInfoFromHalToController(); + PowerMockito.doNothing().when(fieldAgent).sendHWInfoFromHalToController(); + + getUsageData = mock(Thread.class); + PowerMockito.whenNew(Thread.class).withParameterTypes(Runnable.class, String.class).withArguments(Mockito.any(Runnable.class), + anyString()).thenReturn(getUsageData); + } + + /** + * @throws Exception + */ + @After + public void tearDown() throws Exception { + resourceManager = null; + fieldAgent = null; + } + + /** + * Test the start method + */ + @Test ( timeout = 100000L ) + public void testStart() { + resourceManager.start(); + verify(resourceManager, Mockito.times(1)).start(); + assertEquals(Constants.RESOURCE_MANAGER, resourceManager.getModuleIndex()); + assertEquals("ResourceManager", resourceManager.getModuleName()); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.logDebug("ResourceManager", "started"); + } + +} diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterStatusTest.java new file mode 100644 index 00000000..2bb59c37 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterStatusTest.java @@ -0,0 +1,65 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.status_reporter; + +import static org.junit.Assert.*; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import static org.powermock.api.mockito.PowerMockito.spy; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({StatusReporterStatus.class}) +public class StatusReporterStatusTest { + private StatusReporterStatus statusReporterStatus; + private long lastUpdate = System.currentTimeMillis(); + private long systemTime = System.currentTimeMillis(); + /** + * @throws Exception + */ + @Before + public void setUp() throws Exception { + statusReporterStatus = spy(new StatusReporterStatus()); + } + + /** + * @throws Exception + */ + @After + public void tearDown() throws Exception { + } + + /** + * Test getters and setters + */ + @Test + public void testGetterAndSetter() { + + assertNotNull(statusReporterStatus.setLastUpdate(lastUpdate)); + assertNotNull(statusReporterStatus.setSystemTime(systemTime)); + assertNotNull(statusReporterStatus.getLastUpdate()); + assertNotNull(statusReporterStatus.getSystemTime()); + assertEquals(lastUpdate, statusReporterStatus.getLastUpdate()); + assertEquals(systemTime, statusReporterStatus.getSystemTime()); + assertEquals(statusReporterStatus.setLastUpdate(lastUpdate).getLastUpdate(), lastUpdate); + assertEquals(statusReporterStatus.setSystemTime(systemTime).getSystemTime(), systemTime); + } + +} diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterTest.java new file mode 100644 index 00000000..99c0cc4a --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterTest.java @@ -0,0 +1,110 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.status_reporter; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.verify; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import static org.mockito.Mockito.*; + +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.eclipse.iofog.utils.Constants.ModulesStatus; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({StatusReporter.class, Configuration.class, LoggingService.class, ScheduledExecutorService.class, Executors.class, ScheduledFuture.class}) +public class StatusReporterTest { + private StatusReporter statusReporter; + private ScheduledExecutorService scheduledExecutorService; + private ScheduledFuture future; + + @Before + public void setUp() throws Exception { + statusReporter = mock(StatusReporter.class); + mockStatic(Configuration.class); + scheduledExecutorService = mock(ScheduledExecutorService.class); + future = mock(ScheduledFuture.class); + + mockStatic(LoggingService.class); + mockStatic(Executors.class); + when(Configuration.getSetSystemTimeFreqSeconds()).thenReturn(1); + when(Executors.newScheduledThreadPool(anyInt())).thenReturn(scheduledExecutorService); + PowerMockito.when(scheduledExecutorService.scheduleAtFixedRate(any(Runnable.class), anyInt(), anyInt(), any())).thenReturn(future); + } + + @After + public void tearDown() throws Exception { + + } + + @SuppressWarnings("static-access") + @Test ( timeout = 5000L ) + public void testStart() throws InterruptedException { + statusReporter.start(); + // assert that the method was called / + verify( statusReporter ).setStatusReporterStatus().setSystemTime(anyLong());; + } + + + @SuppressWarnings("static-access") + @Test + public void testGetStatusReport() { + statusReporter.setSupervisorStatus().setDaemonStatus(ModulesStatus.STARTING); + assertTrue(statusReporter.getStatusReport().startsWith("ioFog daemon :")); + + } + + @SuppressWarnings("static-access") + @Test + public void testSetters() { + assertNotNull(statusReporter.setSupervisorStatus()); + assertNotNull(statusReporter.setFieldAgentStatus()); + assertNotNull(statusReporter.setLocalApiStatus()); + assertNotNull(statusReporter.setMessageBusStatus()); + assertNotNull(statusReporter.setProcessManagerStatus()); + assertNotNull(statusReporter.setResourceConsumptionManagerStatus()); + assertNotNull(statusReporter.setResourceManagerStatus()); + assertNotNull(statusReporter.setSshProxyManagerStatus()); + assertNotNull(statusReporter.setStatusReporterStatus()); + assertNotNull(statusReporter.setSupervisorStatus()); + + } + + @SuppressWarnings("static-access") + @Test + public void testGetters() { + assertNotNull(statusReporter.getFieldAgentStatus()); + assertNotNull(statusReporter.getLocalApiStatus()); + assertNotNull(statusReporter.getMessageBusStatus()); + assertNotNull(statusReporter.getProcessManagerStatus()); + assertNotNull(statusReporter.getResourceConsumptionManagerStatus()); + assertNotNull(statusReporter.getSshManagerStatus()); + assertNotNull(statusReporter.getStatusReporterStatus()); + assertNotNull(statusReporter.getSupervisorStatus()); + } + +} diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorStatusTest.java new file mode 100644 index 00000000..9f85a5f9 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorStatusTest.java @@ -0,0 +1,96 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.supervisor; + +import org.eclipse.iofog.utils.Constants; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.assertEquals; + +/** + * @author nehanaithani + * + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({SupervisorStatus.class}) +public class SupervisorStatusTest { + private SupervisorStatus supervisorStatus; + private long daemonLastStart; + private long operationDuration; + + + @Before + public void setUp() throws Exception { + supervisorStatus = new SupervisorStatus(); + daemonLastStart = 10000l; + operationDuration = 5000l; + } + + @After + public void tearDown() throws Exception { + supervisorStatus = null; + daemonLastStart = 0l; + operationDuration = 0l; + } + + /** + * Test setModuleStatus with invalid index + */ + @Test + public void testSetModuleStatusWithInvalidValue(){ + supervisorStatus.setModuleStatus(8, Constants.ModulesStatus.STARTING); + assertEquals(null, supervisorStatus.getModuleStatus(8)); + } + + /** + * Test setModuleStatus with Valid index + */ + @Test + public void testSetModuleStatusWithValidValue(){ + supervisorStatus.setModuleStatus(3, Constants.ModulesStatus.STOPPED); + assertEquals(Constants.ModulesStatus.STOPPED, supervisorStatus.getModuleStatus(3)); + } + + /** + * Test SetDaemonStatus + */ + @Test + public void testSetDaemonStatus(){ + supervisorStatus.setDaemonStatus(Constants.ModulesStatus.STARTING); + assertEquals(Constants.ModulesStatus.STARTING, supervisorStatus.getDaemonStatus()); + } + + /** + * Test when operation duration is less then daemon last start + */ + @Test + public void testWhenOperationDurationIsLessThanDaemonLAstStart(){ + assertEquals(daemonLastStart, supervisorStatus.setDaemonLastStart(daemonLastStart).getDaemonLastStart()); + assertEquals(0, supervisorStatus.setOperationDuration(operationDuration).getOperationDuration()); + } + + /** + * Test when operation duration is greater then daemon last start + */ + @Test + public void testWhenOperationDurationIsGreaterThanDaemonLAstStart(){ + operationDuration = 100000l; + assertEquals(daemonLastStart, supervisorStatus.setDaemonLastStart(daemonLastStart).getDaemonLastStart()); + assertEquals((operationDuration - daemonLastStart), supervisorStatus.setOperationDuration(operationDuration).getOperationDuration()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorTest.java new file mode 100644 index 00000000..5e6ed737 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorTest.java @@ -0,0 +1,132 @@ +package org.eclipse.iofog.supervisor; + +import org.eclipse.iofog.IOFogModule; + +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.local_api.LocalApi; +import org.eclipse.iofog.local_api.LocalApiStatus; +import org.eclipse.iofog.message_bus.MessageBus; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.process_manager.DockerUtil; +import org.eclipse.iofog.process_manager.ProcessManager; +import org.eclipse.iofog.pruning.DockerPruningManager; +import org.eclipse.iofog.resource_consumption_manager.ResourceConsumptionManager; +import org.eclipse.iofog.resource_manager.ResourceManager; +import org.eclipse.iofog.status_reporter.StatusReporter; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import java.lang.reflect.Method; + +import static org.eclipse.iofog.utils.Constants.ModulesStatus.RUNNING; +import static org.eclipse.iofog.utils.Constants.ModulesStatus.STARTING; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.spy; +import static org.powermock.api.support.membermodification.MemberMatcher.method; +import static org.powermock.api.support.membermodification.MemberModifier.suppress; + +@RunWith(PowerMockRunner.class) +@PrepareForTest({Supervisor.class, StatusReporter.class, ResourceConsumptionManager.class, + FieldAgent.class, ProcessManager.class, SecurityManager.class, + MessageBus.class, LocalApi.class, LoggingService.class, Configuration.class, IOFogNetworkInterfaceManager.class, DockerPruningManager.class, + DockerUtil.class, SupervisorStatus.class}) +public class SupervisorTest { + private Supervisor supervisor; + private Method method = null; + private ResourceManager resourceManager; + private IOFogNetworkInterfaceManager ioFogNetworkInterfaceManager; + private DockerPruningManager dockerPruningManager; + private DockerUtil dockerUtil; + private SupervisorStatus supervisorStatus; + + @Before + public void initialization() { + try { + supervisor = spy(new Supervisor()); + mockStatic(StatusReporter.class); + mockStatic(ResourceConsumptionManager.class); + mockStatic(FieldAgent.class); + mockStatic(ProcessManager.class); + mockStatic(SecurityManager.class); + mockStatic(MessageBus.class); + mockStatic(LocalApi.class); + mockStatic(LoggingService.class); + mockStatic(IOFogNetworkInterfaceManager.class); + mockStatic(DockerUtil.class); + mockStatic(DockerPruningManager.class); + dockerUtil = PowerMockito.mock(DockerUtil.class); + supervisorStatus = PowerMockito.mock(SupervisorStatus.class); + ioFogNetworkInterfaceManager = PowerMockito.mock(IOFogNetworkInterfaceManager.class); + dockerPruningManager = PowerMockito.mock(DockerPruningManager.class); + PowerMockito.when(StatusReporter.setSupervisorStatus()).thenReturn(supervisorStatus); + PowerMockito.when(supervisorStatus.setDaemonStatus(any())).thenReturn(supervisorStatus); + PowerMockito.when(supervisorStatus.setDaemonLastStart(anyLong())).thenReturn(supervisorStatus); + PowerMockito.when(supervisorStatus.setOperationDuration(anyLong())).thenReturn(supervisorStatus); + PowerMockito.when(StatusReporter.setLocalApiStatus()).thenReturn(mock(LocalApiStatus.class)); + + + PowerMockito.when(ResourceConsumptionManager.getInstance()).thenReturn(null); + PowerMockito.when(FieldAgent.getInstance()).thenReturn(null); + PowerMockito.when(ProcessManager.getInstance()).thenReturn(null); + PowerMockito.when(MessageBus.getInstance()).thenReturn(null); + PowerMockito.when(IOFogNetworkInterfaceManager.getInstance()).thenReturn(ioFogNetworkInterfaceManager); + PowerMockito.doNothing().when(ioFogNetworkInterfaceManager).start(); + PowerMockito.when(DockerUtil.getInstance()).thenReturn(dockerUtil); + PowerMockito.when(DockerPruningManager.getInstance()).thenReturn(dockerPruningManager); + PowerMockito.doNothing().when(dockerPruningManager).start(); + PowerMockito.doNothing().when(StatusReporter.class, "start"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Test + public void supervisor() { + try { + supervisor = spy(new Supervisor()); + suppress(method(Supervisor.class, "startModule")); + suppress(method(Supervisor.class, "operationDuration")); + supervisor.start(); + verify(supervisor, Mockito.atLeastOnce()).start(); + verify(supervisor, Mockito.never()).getModuleIndex(); + verify(supervisor, Mockito.atLeastOnce()).getModuleName(); + verify(supervisor, Mockito.atLeastOnce()).logDebug("Starting Supervisor"); + verify(supervisor, Mockito.atLeastOnce()).logDebug("Started Supervisor"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @After + public void verifyTest() { + if (method != null) + method.setAccessible(false); + } + /** + * Test start module + */ + @Test + public void testStartModule() throws Exception{ + resourceManager = mock(ResourceManager.class); + PowerMockito.when(resourceManager.getModuleIndex()).thenReturn(6); + PowerMockito.when(resourceManager.getModuleName()).thenReturn("ResourceManager"); + PowerMockito.when(StatusReporter.setSupervisorStatus().setModuleStatus(6, STARTING)).thenReturn(mock(SupervisorStatus.class)); + PowerMockito.when(StatusReporter.setSupervisorStatus().setModuleStatus(6, RUNNING)).thenReturn(null); + method = Supervisor.class.getDeclaredMethod("startModule", IOFogModule.class); + method.setAccessible(true); + PowerMockito.doNothing().when(resourceManager).start(); + method.invoke(supervisor, resourceManager); + verify(supervisor, Mockito.atLeastOnce()).logInfo(" Starting ResourceManager"); + verify(supervisor, Mockito.atLeastOnce()).logInfo(" Started ResourceManager"); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/CmdPropertiesTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/CmdPropertiesTest.java new file mode 100644 index 00000000..ef7ecc13 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/CmdPropertiesTest.java @@ -0,0 +1,227 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils; + +import org.eclipse.iofog.command_line.CommandLineConfigParam; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; +import static org.powermock.api.mockito.PowerMockito.mockStatic; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({CmdProperties.class}) +public class CmdPropertiesTest { + + @Before + public void setUp() throws Exception { + mockStatic(CmdProperties.class, Mockito.CALLS_REAL_METHODS); + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void getVersionMessage() { + assertEquals("ioFog %s \nCopyright (C) 2022 Edgeworx, Inc. \nEclipse ioFog is provided under the Eclipse Public License 2.0 (EPL-2.0) \nhttps://www.eclipse.org/legal/epl-v20.html", + CmdProperties.getVersionMessage()); + } + + @Test + public void getVersion() { + assertNotNull(CmdProperties.getVersion()); + } + + /** + * Test getDeprovisionMessage + */ + @Test + public void testGetDeprovisionMessage() { + assertEquals("Deprovisioning from controller ... %s", CmdProperties.getDeprovisionMessage()); + } + + /** + * Test getProvisionMessage + */ + @Test + public void testGetProvisionMessage() { + assertEquals("Provisioning with key \"%s\" ... Result: %s", CmdProperties.getProvisionMessage()); + } + + /** + * Test getProvisionCommonErrorMessage + */ + @Test + public void testGetProvisionCommonErrorMessage() { + assertEquals("\nProvisioning failed", CmdProperties.getProvisionCommonErrorMessage()); + } + + /** + * Test getProvisionStatusErrorMessage + */ + @Test + public void testGetProvisionStatusErrorMessage() { + assertEquals("\nProvision failed with error message: \"%s\"", CmdProperties.getProvisionStatusErrorMessage()); + + } + + /** + * Test getProvisionStatusSuccessMessage + */ + @Test + public void testGetProvisionStatusSuccessMessage() { + assertEquals("\nProvision success - Iofog UUID is %s", CmdProperties.getProvisionStatusSuccessMessage()); + + } + + /** + * Test getConfigParamMessage NETWORK_INTERFACE + */ + @Test + public void testGetConfigParamMessageOfNetworkInterface() { + assertEquals("Network Interface", CmdProperties.getConfigParamMessage(CommandLineConfigParam.NETWORK_INTERFACE)); + + } + /** + * Test getConfigParamMessage DOCKER_URL + */ + @Test + public void testGetConfigParamMessageOfDockerUrl() { + assertEquals("Docker URL", CmdProperties.getConfigParamMessage(CommandLineConfigParam.DOCKER_URL)); + + } + /** + * Test getConfigParamMessage LOG_DISK_CONSUMPTION_LIMIT + */ + @Test + public void testGetConfigParamMessageOfLogDiskLimit() { + assertEquals("Log Disk Limit", CmdProperties.getConfigParamMessage(CommandLineConfigParam.LOG_DISK_CONSUMPTION_LIMIT)); + + } + /** + * Test getConfigParamMessage LOG_DISK_DIRECTORY + */ + @Test + public void testGetConfigParamMessageOfLogDiskDirectory() { + assertEquals("Log File Directory", CmdProperties.getConfigParamMessage(CommandLineConfigParam.LOG_DISK_DIRECTORY)); + } + + /** + * Test getConfigParamMessage LOG_FILE_COUNT + */ + @Test + public void testGetConfigParamMessageOfLogDFileCount() { + assertEquals("Log Rolling File Count", CmdProperties.getConfigParamMessage(CommandLineConfigParam.LOG_FILE_COUNT)); + } + /** + * Test getConfigParamMessage LOG_LEVEL + */ + @Test + public void testGetConfigParamMessageOfLogLevel() { + assertEquals("Log Level", CmdProperties.getConfigParamMessage(CommandLineConfigParam.LOG_LEVEL)); + } + + /** + * Test getConfigParamMessage POST_DIAGNOSTICS_FREQ + */ + @Test + public void testGetConfigParamMessageOfPostDiagnosticFreq() { + assertEquals("Post Diagnostics Frequency", CmdProperties.getConfigParamMessage(CommandLineConfigParam.POST_DIAGNOSTICS_FREQ)); + } + + /** + * Test getConfigParamMessage IOFOG_UUID + */ + @Test + public void testGetConfigParamMessageOfIofogUuid() { + assertEquals("Iofog UUID", CmdProperties.getConfigParamMessage(CommandLineConfigParam.IOFOG_UUID)); + } + + /** + * Test getConfigParamMessage GPS_COORDINATES + */ + @Test + public void testGetConfigParamMessageOfGPSCoordinates() { + assertEquals("GPS coordinates(lat,lon)", CmdProperties.getConfigParamMessage(CommandLineConfigParam.GPS_COORDINATES)); + } + + /** + * Test getConfigParamMessage GPS_MODE + */ + @Test + public void testGetConfigParamMessageOfGPSMode() { + assertEquals("GPS mode", CmdProperties.getConfigParamMessage(CommandLineConfigParam.GPS_MODE)); + } + + /** + * Test getConfigParamMessage FOG_TYPE + */ + @Test + public void testGetConfigParamMessageOfFogType() { + assertEquals("Fog type", CmdProperties.getConfigParamMessage(CommandLineConfigParam.FOG_TYPE)); + } + /** + * Test getConfigParamMessage DEV_MODE + */ + @Test + public void testGetConfigParamMessageOfDevMode() { + assertEquals("Developer's Mode", CmdProperties.getConfigParamMessage(CommandLineConfigParam.DEV_MODE)); + } + + /** + * Test getConfigParamMessage DEVICE_SCAN_FREQUENCY + */ + @Test + public void testGetConfigParamMessageOfDeviceScanFreq() { + assertEquals("Scan Devices Frequency", CmdProperties.getConfigParamMessage(CommandLineConfigParam.DEVICE_SCAN_FREQUENCY)); + } + + /** + * Test getConfigParamMessage CHANGE_FREQUENCY + */ + @Test + public void testGetConfigParamMessageOfChangeFreq() { + assertEquals("Get Changes Frequency", CmdProperties.getConfigParamMessage(CommandLineConfigParam.CHANGE_FREQUENCY)); + } + /** + * Test getConfigParamMessage STATUS_FREQUENCY + */ + @Test + public void testGetConfigParamMessageOfStatusFreq() { + assertEquals("Status Update Frequency", CmdProperties.getConfigParamMessage(CommandLineConfigParam.STATUS_FREQUENCY)); + } + + /** + * Test ipAddressMessage + */ + @Test + public void testGetIpAddressMessage() { + assertEquals("IP Address", CmdProperties.getIpAddressMessage()); + } + /** + * Test getIofogUuidMessage + */ + @Test + public void testGetIofogUuidMessage() { + assertEquals("Iofog UUID", CmdProperties.getIofogUuidMessage()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/OrchestratorTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/OrchestratorTest.java new file mode 100644 index 00000000..07c75e0a --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/OrchestratorTest.java @@ -0,0 +1,683 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils; + +import org.apache.http.HttpEntity; +import org.apache.http.StatusLine; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.client.HttpClients; +import org.eclipse.iofog.exception.AgentSystemException; +import org.eclipse.iofog.exception.AgentUserException; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.field_agent.enums.RequestType; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.eclipse.iofog.utils.device_info.ArchitectureType; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.eclipse.iofog.utils.trustmanager.TrustManagers; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import javax.json.Json; +import javax.json.JsonObject; +import javax.json.JsonObjectBuilder; +import javax.json.JsonReader; +import javax.naming.AuthenticationException; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import java.io.*; +import java.net.InetAddress; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.CertificateFactorySpi; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.powermock.api.mockito.PowerMockito.mockStatic; +import static org.powermock.api.mockito.PowerMockito.spy; + +import javax.net.ssl.TrustManagerFactory; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.ForbiddenException; +import javax.ws.rs.InternalServerErrorException; +import javax.ws.rs.NotFoundException; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Orchestrator.class, Configuration.class, SSLConnectionSocketFactory.class, LoggingService.class, + HttpClients.class, CloseableHttpClient.class, LoggingService.class, IOFogNetworkInterfaceManager.class, + InetAddress.class, HttpGet.class, BufferedReader.class, InputStreamReader.class, CloseableHttpResponse.class, + HttpEntity.class, InputStream.class, StatusLine.class, JsonReader.class, Json.class, File.class, + FileInputStream.class, CertificateFactory.class, Certificate.class, CertificateFactorySpi.class, Provider.class, + SSLContext.class, SSLConnectionSocketFactory.class, HttpClientBuilder.class, StringEntity.class, FieldAgent.class, MultipartEntityBuilder.class, + TrustManagers.class, TrustManagerFactory.class}) +@PowerMockIgnore({"java.net.ssl.*"}) +public class OrchestratorTest { + private Orchestrator orchestrator; + private SSLConnectionSocketFactory sslConnectionSocketFactory; + private CloseableHttpClient httpClients; + private InetAddress inetAddress; + private HttpGet httpGet; + private BufferedReader bufferedReader; + private InputStreamReader reader; + private CloseableHttpResponse response; + private HttpEntity httpEntity; + private InputStream inputStream; + private StatusLine statusLine; + private JsonReader jsonReader; + private JsonObject jsonObject; + private JsonObject anotherJsonObject; + private JsonObjectBuilder jsonObjectBuilder; + private FileInputStream fileInputStream; + private CertificateFactory certificateFactory; + private Certificate certificate; + private CertificateFactorySpi certificateFactorySpi; + private Provider provider; + private SSLContext sslContext; + private HttpClientBuilder httpClientBuilder; + private StringEntity stringEntity; + private FieldAgent fieldAgent; + private String provisionKey; + private File file; + private MultipartEntityBuilder multipartEntityBuilder; + private IOFogNetworkInterfaceManager iOFogNetworkInterfaceManager; + TrustManagerFactory trustManagerFactory; + + @Before + public void setUp() throws Exception { + provisionKey = "provisionKey"; + mockStatic(Configuration.class); + mockStatic(LoggingService.class); + mockStatic(HttpClients.class); + mockStatic(LoggingService.class); + mockStatic(Json.class); + mockStatic(CertificateFactory.class); + mockStatic(SSLContext.class); + mockStatic(FieldAgent.class); + mockStatic(MultipartEntityBuilder.class); + mockStatic(IOFogNetworkInterfaceManager.class); + sslConnectionSocketFactory = mock(SSLConnectionSocketFactory.class); + file = mock(File.class); + multipartEntityBuilder = mock(MultipartEntityBuilder.class); + httpClients = mock(CloseableHttpClient.class); + inetAddress = mock(InetAddress.class); + httpGet = mock(HttpGet.class); + bufferedReader = mock(BufferedReader.class); + reader = mock(InputStreamReader.class); + response = mock(CloseableHttpResponse.class); + httpEntity = mock(HttpEntity.class); + inputStream = mock(InputStream.class); + statusLine = mock(StatusLine.class); + jsonReader = mock(JsonReader.class); + jsonObject = mock(JsonObject.class); + anotherJsonObject = mock(JsonObject.class); + jsonObjectBuilder = mock(JsonObjectBuilder.class); + fileInputStream = mock(FileInputStream.class); + certificate = PowerMockito.mock(Certificate.class); + certificateFactorySpi = mock(CertificateFactorySpi.class); + provider = mock(Provider.class); + certificateFactory = PowerMockito.mock(CertificateFactory.class); + sslContext = PowerMockito.mock(SSLContext.class); + httpClientBuilder = mock(HttpClientBuilder.class); + stringEntity = Mockito.mock(StringEntity.class); + fieldAgent = PowerMockito.mock(FieldAgent.class); + iOFogNetworkInterfaceManager = PowerMockito.mock(IOFogNetworkInterfaceManager.class); + PowerMockito.when(file.getName()).thenReturn("fileName"); + PowerMockito.when(FieldAgent.getInstance()).thenReturn(fieldAgent); + PowerMockito.when(fieldAgent.deProvision(Mockito.anyBoolean())).thenReturn("success"); + PowerMockito.when(MultipartEntityBuilder.create()).thenReturn(multipartEntityBuilder); + PowerMockito.when(multipartEntityBuilder.build()).thenReturn(httpEntity); + PowerMockito.when(Configuration.getIofogUuid()).thenReturn("iofog-uuid"); + PowerMockito.when(Configuration.getFogType()).thenReturn(ArchitectureType.ARM); + PowerMockito.when(Configuration.getAccessToken()).thenReturn("access-token"); + PowerMockito.when(Configuration.getControllerUrl()).thenReturn("http://controller/"); + PowerMockito.when(Configuration.isSecureMode()).thenReturn(false); + PowerMockito.when(Configuration.getControllerCert()).thenReturn("controllerCert"); + PowerMockito.when(IOFogNetworkInterfaceManager.getInstance()).thenReturn(iOFogNetworkInterfaceManager); + PowerMockito.when(iOFogNetworkInterfaceManager.getInetAddress()).thenReturn(inetAddress); + PowerMockito.whenNew(SSLConnectionSocketFactory.class) + .withParameterTypes(SSLContext.class) + .withArguments(Mockito.any()).thenReturn(sslConnectionSocketFactory); + PowerMockito.when(HttpClients.createDefault()).thenReturn(httpClients); + PowerMockito.when(httpClients.execute(Mockito.any())).thenReturn(response); + PowerMockito.when(response.getEntity()).thenReturn(httpEntity); + PowerMockito.when(response.getStatusLine()).thenReturn(statusLine); + PowerMockito.when(statusLine.getStatusCode()).thenReturn(200); + PowerMockito.when(httpEntity.getContent()).thenReturn(inputStream); + PowerMockito.whenNew(HttpGet.class) + .withParameterTypes(String.class) + .withArguments(Mockito.any()).thenReturn(httpGet); + PowerMockito.whenNew(BufferedReader.class) + .withParameterTypes(Reader.class) + .withArguments(Mockito.any()) + .thenReturn(bufferedReader); + PowerMockito.whenNew(InputStreamReader.class) + .withParameterTypes(InputStream.class, String.class) + .withArguments(Mockito.any(), Mockito.anyString()) + .thenReturn(reader); + PowerMockito.whenNew(FileInputStream.class) + .withParameterTypes(String.class) + .withArguments(Mockito.anyString()) + .thenReturn(fileInputStream); + PowerMockito.whenNew(FileInputStream.class) + .withParameterTypes(File.class) + .withArguments(Mockito.any()) + .thenReturn(fileInputStream); + PowerMockito.when(Json.createReader(Mockito.any(Reader.class))).thenReturn(jsonReader); + PowerMockito.when(Json.createObjectBuilder()).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonObjectBuilder.add(Mockito.anyString(), Mockito.anyString())).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonObjectBuilder.add(Mockito.anyString(), Mockito.anyInt())).thenReturn(jsonObjectBuilder); + PowerMockito.when(jsonObjectBuilder.build()).thenReturn(anotherJsonObject); + PowerMockito.when(jsonReader.readObject()).thenReturn(jsonObject); + PowerMockito.when(SSLContext.getInstance(Mockito.anyString())).thenReturn(sslContext); + PowerMockito.doNothing().when(sslContext).init(Mockito.any(KeyManager[].class), + Mockito.any(TrustManager[].class), Mockito.any(SecureRandom.class)); + PowerMockito.when(HttpClients.custom()).thenReturn(httpClientBuilder); + PowerMockito.when(httpClientBuilder.build()).thenReturn(httpClients); + PowerMockito.when(CertificateFactory.getInstance(Mockito.any())).thenReturn(certificateFactory); + PowerMockito.when(certificateFactory.generateCertificate(Mockito.any(InputStream.class))).thenReturn(certificate); + PowerMockito.whenNew(StringEntity.class).withParameterTypes(String.class, ContentType.class) + .withArguments(Mockito.anyString(), Mockito.eq(ContentType.APPLICATION_JSON)) + .thenReturn(stringEntity); + PowerMockito.mock(TrustManagers.class); + mockStatic(TrustManagers.class); + trustManagerFactory = PowerMockito.mock(TrustManagerFactory.class); + mockStatic(TrustManagerFactory.class); + PowerMockito.when(TrustManagerFactory.getInstance(anyString())).thenReturn(trustManagerFactory); + orchestrator = spy(new Orchestrator()); + } + + @After + public void tearDown() throws Exception { + provisionKey = null; + orchestrator = null; + Mockito.reset(certificateFactory, httpClientBuilder, jsonObjectBuilder, jsonReader, fileInputStream, + stringEntity, response, anotherJsonObject, jsonObject); + } + + /** + * Test ping true + */ + @Test + public void testPingSuccess() { + try { + assertTrue(orchestrator.ping()); + PowerMockito.verifyPrivate(orchestrator).invoke("getJSON", Mockito.eq("http://controller/status")); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ping false + */ + @Test + public void testPingFailure() { + try { + PowerMockito.when(jsonObject.isNull("status")).thenReturn(true); + assertFalse(orchestrator.ping()); + PowerMockito.verifyPrivate(orchestrator).invoke("getJSON", Mockito.eq("http://controller/status")); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test ping throws Exception + */ + @Test (expected = Exception.class) + public void throwsExceptionWhenPingIsCalled() throws Exception{ + PowerMockito.when(jsonReader.readObject()).thenReturn(null); + assertFalse(orchestrator.ping()); + } + + /** + * Test ping When Controller url is https & secureMode + */ + @Test (expected = AgentUserException.class) + public void testPingWhenControllerUrlIsHttpsAndDevMode() throws Exception{ + PowerMockito.when(Configuration.getControllerUrl()).thenReturn("https://controller/"); + PowerMockito.when(Configuration.isSecureMode()).thenReturn(true); + assertFalse(orchestrator.ping()); + } + + /** + * Test ping When response code is 400 + */ + @Test (expected = AgentUserException.class) + public void throwsExceptionWhenResponseCodeIsNotOkOnPing() throws Exception{ + PowerMockito.when(statusLine.getStatusCode()).thenReturn(400); + assertFalse(orchestrator.ping()); + } + + /** + * Test ping When response code is 404 + */ + @Test (expected = AgentUserException.class) + public void throwsExceptionWhenResponseCodeIs404OnPing() throws Exception{ + PowerMockito.when(statusLine.getStatusCode()).thenReturn(404); + assertFalse(orchestrator.ping()); + } + /** + * Test ping When InputStream throws Exception + */ + @Test (expected = AgentUserException.class) + public void throwsUnsupportedEncodingExceptionWhenInputStreamIsCreatedInPing() throws Exception{ + PowerMockito.whenNew(InputStreamReader.class) + .withParameterTypes(InputStream.class, String.class) + .withArguments(Mockito.any(), Mockito.anyString()) + .thenThrow(mock(UnsupportedEncodingException.class)); + orchestrator.ping(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJSON", Mockito.eq("http://controller/status")); + } + /** + * Test ping When client throws ClientProtocolException + */ + @Test (expected = AgentUserException.class) + public void throwsClientProtocolExceptionWhenHttpsClientExecuteIsCalledInPing() throws Exception{ + PowerMockito.doThrow(mock(ClientProtocolException.class)).when(httpClients).execute(Mockito.any()); + assertFalse(orchestrator.ping()); + PowerMockito.verifyPrivate(orchestrator).invoke("getJSON", Mockito.eq("http://controller/status")); + } + + /** + * Test ping When client throws IOException + */ + @Test (expected = AgentUserException.class) + public void throwsIOExceptionWhenHttpsClientExecuteIsCalledInPing() throws Exception{ + PowerMockito.doThrow(mock(IOException.class)).when(httpClients).execute(Mockito.any()); + assertFalse(orchestrator.ping()); + PowerMockito.verifyPrivate(orchestrator).invoke("getJSON", Mockito.eq("http://controller/status")); + } + + /** + * Test ping When client throws IOException + */ + @Test (expected = AgentUserException.class) + public void throwsExceptionWhenResponseIsNullCalledInPing() throws Exception{ + PowerMockito.when(httpClients.execute(Mockito.any())).thenReturn(null); + assertFalse(orchestrator.ping()); + PowerMockito.verifyPrivate(orchestrator).invoke("getJSON", Mockito.eq("http://controller/status")); + } + + /** + * Test ping When client throws IOException + */ + @Test (expected = AgentUserException.class) + public void throwsUnsupportedOperationExceptionWhenResponseContentIsCalledInPing() throws Exception{ + PowerMockito.doThrow(mock(UnsupportedOperationException.class)).when(httpEntity).getContent(); + assertFalse(orchestrator.ping()); + PowerMockito.verifyPrivate(orchestrator).invoke("getJSON", Mockito.eq("http://controller/status")); + } + + /** + * Test request when json is null + */ + @Test + public void testRequest() throws Exception { + JsonObject jsonResponse = orchestrator.request("deprovision", RequestType.POST, null, null); + assertEquals(jsonObject, jsonResponse); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.POST), Mockito.eq(stringEntity), Mockito.any()); + } + + /** + * Test request when json is not null & command is blank + */ + @Test + public void testRequestWhenCommandIsBlank() { + JsonObject jsonResponse = null; + try { + jsonResponse = orchestrator.request("", RequestType.PATCH, null, jsonObject); + assertEquals(jsonObject, jsonResponse); + PowerMockito.verifyStatic(Configuration.class); + Configuration.isSecureMode(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.PATCH), Mockito.eq(stringEntity), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test request when devMode is true + */ + @Test (expected = AgentUserException.class) + public void throwsAgentUserExceptionWhenDevModeIsTrue() throws Exception { + PowerMockito.when(Configuration.isSecureMode()).thenReturn(true); + JsonObject jsonResponse = orchestrator.request("delete", RequestType.DELETE, null, null); + assertEquals(jsonObject, jsonResponse); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq("delete"), + Mockito.eq(RequestType.DELETE), Mockito.eq(null), Mockito.eq(null)); + } + + /** + * Test request command is delete & responseCode is 204 + */ + @Test + public void testWhenCommandIsDelete() throws Exception { + PowerMockito.when(statusLine.getStatusCode()).thenReturn(204); + JsonObject jsonResponse = orchestrator.request("delete", RequestType.DELETE, null, null); + assertNotEquals(jsonObject, jsonResponse); + PowerMockito.verifyStatic(Json.class, Mockito.atLeastOnce()); + Json.createObjectBuilder(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.DELETE), Mockito.eq(stringEntity), Mockito.any()); + } + + /** + * Test request command is delete & responseCode is 400 + */ + @Test (expected = BadRequestException.class) + public void throwsBadRequestExceptionWhenCommandIsDelete() throws Exception { + PowerMockito.when(statusLine.getStatusCode()).thenReturn(400); + PowerMockito.when(jsonObject.getString("message")).thenReturn("Error"); + JsonObject jsonResponse = orchestrator.request("delete", RequestType.DELETE, null, null); + assertNotEquals(jsonObject, jsonResponse); + PowerMockito.verifyStatic(Json.class, Mockito.atLeastOnce()); + Json.createObjectBuilder(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.DELETE), Mockito.eq(stringEntity), Mockito.any()); + Mockito.verify(fieldAgent).deProvision(Mockito.eq(true)); + } + /** + * Test request command is delete & responseCode is 401 + */ + @Test (expected = AuthenticationException.class) + public void throwsAuthenticationExceptionWhenCommandIsDelete() throws Exception { + PowerMockito.when(statusLine.getStatusCode()).thenReturn(401); + PowerMockito.when(jsonObject.getString("message")).thenReturn("Error"); + JsonObject jsonResponse = orchestrator.request("delete", RequestType.DELETE, null, null); + assertNotEquals(jsonObject, jsonResponse); + PowerMockito.verifyStatic(Json.class, Mockito.atLeastOnce()); + Json.createObjectBuilder(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.DELETE), Mockito.eq(stringEntity), Mockito.any()); + Mockito.verify(fieldAgent).deProvision(Mockito.eq(true)); + } + + /** + * Test request command is delete & responseCode is 403 + */ + @Test (expected = ForbiddenException.class) + public void throwsForbiddenExceptionWhenCommandIsDelete() throws Exception { + PowerMockito.when(statusLine.getStatusCode()).thenReturn(403); + PowerMockito.when(jsonObject.getString("message")).thenReturn("Error"); + JsonObject jsonResponse = orchestrator.request("delete", RequestType.DELETE, null, null); + assertNotEquals(jsonObject, jsonResponse); + PowerMockito.verifyStatic(Json.class, Mockito.atLeastOnce()); + Json.createObjectBuilder(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.DELETE), Mockito.eq(stringEntity), Mockito.any()); + Mockito.verify(fieldAgent).deProvision(Mockito.eq(true)); + } + + /** + * Test request command is delete & responseCode is 404 + */ + @Test (expected = NotFoundException.class) + public void throwsNotFoundExceptionWhenCommandIsDelete() throws Exception { + PowerMockito.when(statusLine.getStatusCode()).thenReturn(404); + PowerMockito.when(jsonObject.getString("message")).thenReturn("Error"); + JsonObject jsonResponse = orchestrator.request("delete", RequestType.DELETE, null, null); + assertNotEquals(jsonObject, jsonResponse); + PowerMockito.verifyStatic(Json.class, Mockito.atLeastOnce()); + Json.createObjectBuilder(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.DELETE), Mockito.eq(stringEntity), Mockito.any()); + Mockito.verify(fieldAgent).deProvision(Mockito.eq(true)); + } + + /** + * Test request command is delete & responseCode is 404 + */ + @Test (expected = InternalServerErrorException.class) + public void throwsInternalServerErrorExceptionWhenCommandIsDelete() throws Exception { + PowerMockito.when(statusLine.getStatusCode()).thenReturn(500); + PowerMockito.when(jsonObject.getString("message")).thenReturn("Error"); + JsonObject jsonResponse = orchestrator.request("delete", RequestType.DELETE, null, null); + assertNotEquals(jsonObject, jsonResponse); + PowerMockito.verifyStatic(Json.class, Mockito.atLeastOnce()); + Json.createObjectBuilder(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.DELETE), Mockito.eq(stringEntity), Mockito.any()); + Mockito.verify(fieldAgent).deProvision(Mockito.eq(true)); + } + /** + * Test request command is delete & InputStreamReader throws UnsupportedEncodingException + */ + @Test (expected = AgentUserException.class) + public void throwsUnsupportedEncodingExceptionWhenInputStreamReaderIsCreated() throws Exception { + PowerMockito.whenNew(InputStreamReader.class) + .withParameterTypes(InputStream.class, String.class) + .withArguments(Mockito.any(), Mockito.anyString()) + .thenThrow(mock(UnsupportedEncodingException.class)); + JsonObject jsonResponse = orchestrator.request("delete", RequestType.GET, null, null); + assertNotEquals(jsonObject, jsonResponse); + PowerMockito.verifyStatic(Json.class, Mockito.atLeastOnce()); + Json.createObjectBuilder(); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.GET), Mockito.eq(stringEntity), Mockito.any()); + Mockito.verify(fieldAgent).deProvision(Mockito.eq(true)); + } + + /** + * Test request + */ + @Test + public void testRequestWhenCommandIsStrace() { + JsonObject jsonResponse = null; + Map queryParams = new HashMap<>(); + queryParams.put("key", "value"); + try { + jsonResponse = orchestrator.request("strace", RequestType.PUT, queryParams, jsonObject); + assertEquals(jsonObject, jsonResponse); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(queryParams), + Mockito.eq(RequestType.PUT), Mockito.eq(stringEntity), Mockito.any()); + PowerMockito.verifyStatic(Configuration.class); + Configuration.isSecureMode(); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test request when url is https + */ + @Test + public void testRequestWhenControllerUrlIsHttps() { + JsonObject jsonResponse = null; + try { + PowerMockito.when(Configuration.getControllerUrl()).thenReturn("https://controller/"); + orchestrator = spy(new Orchestrator()); + jsonResponse = orchestrator.request("strace", RequestType.PUT, null, jsonObject); + assertEquals(jsonObject, jsonResponse); + PowerMockito.verifyPrivate(orchestrator, Mockito.atLeastOnce()).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.PUT), Mockito.eq(stringEntity), Mockito.any()); + PowerMockito.verifyPrivate(orchestrator).invoke("initialize", Mockito.eq(true)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision + */ + @Test + public void testProvisionSuccess() { + try { + JsonObject jsonResponse = orchestrator.provision(provisionKey); + assertEquals(jsonObject, jsonResponse); + Mockito.verify(orchestrator).request(Mockito.eq("provision"), + Mockito.eq(RequestType.POST), Mockito.eq(null), Mockito.eq(anotherJsonObject)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test provision throws AgentSystemException + */ + @Test (expected = AgentSystemException.class) + public void throwsAgentSystemExceptionOnProvision() throws Exception{ + PowerMockito.doThrow(mock(Exception.class)).when(orchestrator).request(Mockito.eq("provision"), + Mockito.eq(RequestType.POST), Mockito.eq(null), Mockito.eq(anotherJsonObject)); + JsonObject jsonResponse = orchestrator.provision(provisionKey); + assertEquals(jsonObject, jsonResponse); + Mockito.verify(orchestrator).request(Mockito.eq("provision"), + Mockito.eq(RequestType.POST), Mockito.eq(null), Mockito.eq(anotherJsonObject)); + } + + /** + * Test sendFileToController + */ + @Test + public void testSendFileToController() { + try { + orchestrator.sendFileToController("strace", file); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.PUT), Mockito.eq(httpEntity), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test sendFileToController + */ + @Test (expected = Exception.class) + public void throwsExceptionSendFileToController() throws Exception{ + PowerMockito.doThrow(mock(Exception.class)).when(httpClients).execute(Mockito.any()); + orchestrator.sendFileToController("strace", file); + PowerMockito.verifyPrivate(orchestrator).invoke("getJsonObject", Mockito.eq(null), + Mockito.eq(RequestType.PUT), Mockito.eq(httpEntity), Mockito.any()); + } + + /** + * Test update when controller url is http + */ + @Test + public void testUpdateWhenControllerUrlIsHttp() { + try { + orchestrator.update(); + PowerMockito.verifyPrivate(orchestrator).invoke("initialize", Mockito.eq(false)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test update when FileInputStream throws IOException + */ + @Test + public void testUpdateFileInputStreamThrowsException() { + try { + PowerMockito.whenNew(FileInputStream.class) + .withParameterTypes(String.class) + .withArguments(Mockito.anyString()) + .thenThrow(mock(IOException.class)); + PowerMockito.when(Configuration.getControllerUrl()).thenReturn("https://controller/"); + orchestrator.update(); + PowerMockito.verifyPrivate(orchestrator).invoke("initialize", Mockito.eq(true)); + PowerMockito.verifyPrivate(orchestrator, Mockito.never()).invoke("getCert", Mockito.eq(fileInputStream)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test update when FileInputStream throws IOException + */ + @Test + public void testUpdateWhenGetCertThrowsException() { + try { + PowerMockito.when(certificateFactory.generateCertificate(Mockito.any(InputStream.class))).thenThrow(mock(CertificateException.class)); + PowerMockito.when(Configuration.getControllerUrl()).thenReturn("https://controller/"); + orchestrator.update(); + PowerMockito.verifyPrivate(orchestrator).invoke("initialize", Mockito.eq(true)); + PowerMockito.verifyPrivate(orchestrator).invoke("getCert", Mockito.eq(fileInputStream)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(Mockito.eq("Orchestrator"), + Mockito.eq("unable to get certificate"), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test update when initialize throws AgentException + */ + @Test + public void testUpdateWhenInitializeThrowsException() { + try { + orchestrator = spy(new Orchestrator()); + PowerMockito.when(SSLContext.getInstance(Mockito.anyString())).thenThrow(mock(NoSuchAlgorithmException.class)); + PowerMockito.when(Configuration.getControllerUrl()).thenReturn("https://controller/"); + orchestrator.update(); + PowerMockito.verifyPrivate(orchestrator).invoke("initialize", Mockito.eq(true)); + PowerMockito.verifyPrivate(orchestrator).invoke("getCert", Mockito.eq(fileInputStream)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(Mockito.eq("Orchestrator"), + Mockito.eq("Error while updating local variables when changes applied"), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test update when initialize throws AgentException + */ + @Test + public void testUpdateWhenInitializeThrowsKeyManagementException() { + try { + // orchestrator = spy(new Orchestrator()); + PowerMockito.doThrow(mock(KeyManagementException.class)).when(sslContext).init(Mockito.eq(null), + Mockito.any(), Mockito.any(SecureRandom.class)); + PowerMockito.when(Configuration.getControllerUrl()).thenReturn("https://controller/"); + orchestrator.update(); + PowerMockito.verifyPrivate(orchestrator).invoke("initialize", Mockito.eq(true)); + PowerMockito.verifyPrivate(orchestrator).invoke("getCert", Mockito.eq(fileInputStream)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(Mockito.eq("Orchestrator"), + Mockito.eq("Error while updating local variables when changes applied"), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/configuration/ConfigurationTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/configuration/ConfigurationTest.java new file mode 100644 index 00000000..2373708e --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/configuration/ConfigurationTest.java @@ -0,0 +1,1618 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils.configuration; + +import org.eclipse.iofog.command_line.CommandLineConfigParam; +import org.eclipse.iofog.field_agent.FieldAgent; +import org.eclipse.iofog.gps.GpsMode; +import org.eclipse.iofog.gps.GpsWebHandler; +import org.eclipse.iofog.message_bus.MessageBus; +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.process_manager.ProcessManager; +import org.eclipse.iofog.resource_consumption_manager.ResourceConsumptionManager; +import org.eclipse.iofog.supervisor.Supervisor; +import org.eclipse.iofog.utils.Constants; +import org.eclipse.iofog.utils.device_info.ArchitectureType; +import org.eclipse.iofog.utils.logging.LoggingService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.File; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import static org.eclipse.iofog.command_line.CommandLineConfigParam.*; +import static org.eclipse.iofog.utils.Constants.*; +import static org.junit.Assert.*; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.powermock.api.mockito.PowerMockito.*; +import static org.powermock.api.support.membermodification.MemberMatcher.method; +import static org.powermock.api.support.membermodification.MemberModifier.suppress; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({Configuration.class, LoggingService.class, FieldAgent.class, ProcessManager.class, ResourceConsumptionManager.class, + MessageBus.class, Transformer.class, TransformerFactory.class, StreamResult.class, DOMSource.class, Supervisor.class, GpsWebHandler.class, IOFogNetworkInterfaceManager.class}) +@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "org.w3c.*", "com.sun.org.apache.xalan.*"}) +public class ConfigurationTest { + private MessageBus messageBus; + private FieldAgent fieldAgent; + private ProcessManager processManager; + private ResourceConsumptionManager resourceConsumptionManager; + private Supervisor supervisor; + private String MODULE_NAME; + private String MOCK_CONFIG_SWITCHER_PATH; + private String MOCK_DEFAULT_CONFIG_PATH; + private String ORIGINAL_DEFAULT_CONFIG_PATH; + private String ORIGINAL_CONFIG_SWITCHER_PATH; + private IOFogNetworkInterfaceManager networkInterfaceManager; + + @Before + public void setUp() throws Exception { + MODULE_NAME = "Configuration"; + MOCK_CONFIG_SWITCHER_PATH = "../packaging/iofog-agent/etc/iofog-agent/config-switcher_new.xml"; + MOCK_DEFAULT_CONFIG_PATH = "../packaging/iofog-agent/etc/iofog-agent/config_new.xml"; + ORIGINAL_DEFAULT_CONFIG_PATH = DEFAULT_CONFIG_PATH; + ORIGINAL_CONFIG_SWITCHER_PATH = CONFIG_SWITCHER_PATH; + mockStatic(Configuration.class, Mockito.CALLS_REAL_METHODS); + mockStatic(GpsWebHandler.class); + mockStatic(IOFogNetworkInterfaceManager.class); + networkInterfaceManager = mock(IOFogNetworkInterfaceManager.class); + messageBus = mock(MessageBus.class); + fieldAgent = mock(FieldAgent.class); + processManager =mock(ProcessManager.class); + resourceConsumptionManager = mock(ResourceConsumptionManager.class); + supervisor = mock(Supervisor.class); + PowerMockito.mockStatic(LoggingService.class); + PowerMockito.mockStatic(FieldAgent.class); + PowerMockito.mockStatic(ResourceConsumptionManager.class); + PowerMockito.mockStatic(MessageBus.class); + PowerMockito.mockStatic(ProcessManager.class); + PowerMockito.when(FieldAgent.getInstance()).thenReturn(fieldAgent); + PowerMockito.when(ResourceConsumptionManager.getInstance()).thenReturn(resourceConsumptionManager); + PowerMockito.when(MessageBus.getInstance()).thenReturn(messageBus); + PowerMockito.when(ProcessManager.getInstance()).thenReturn(processManager); + PowerMockito.whenNew(DOMSource.class).withArguments(Mockito.any()).thenReturn(mock(DOMSource.class)); + PowerMockito.whenNew(StreamResult.class).withParameterTypes(File.class).withArguments(Mockito.any(File.class)).thenReturn(mock(StreamResult.class)); + PowerMockito.whenNew(Supervisor.class).withNoArguments().thenReturn(supervisor); + PowerMockito.doNothing().when(supervisor).start(); + setFinalStatic(Constants.class.getField("CONFIG_SWITCHER_PATH"), MOCK_CONFIG_SWITCHER_PATH); + setFinalStatic(Constants.class.getField("DEFAULT_CONFIG_PATH"), MOCK_DEFAULT_CONFIG_PATH); + PowerMockito.when(GpsWebHandler.getGpsCoordinatesByExternalIp()).thenReturn("32.00,-121.31"); + PowerMockito.when(GpsWebHandler.getExternalIp()).thenReturn("0.0.0.0"); + PowerMockito.suppress(method(Configuration.class, "updateConfigFile")); + PowerMockito.when(IOFogNetworkInterfaceManager.getInstance()).thenReturn(networkInterfaceManager); + PowerMockito.doNothing().when(networkInterfaceManager).updateIOFogNetworkInterface(); + } + + @After + public void tearDown() throws Exception { + MODULE_NAME = null; + // reset to original + setFinalStatic(Constants.class.getField("CONFIG_SWITCHER_PATH"), ORIGINAL_CONFIG_SWITCHER_PATH); + setFinalStatic(Constants.class.getField("DEFAULT_CONFIG_PATH"), ORIGINAL_DEFAULT_CONFIG_PATH); + } + + /** + * Helper method to mock the CONFIG_SWITCHER_PATH & DEFAULT_CONFIG_PATH + * @param field + * @param newValue + * @throws Exception + */ + static void setFinalStatic(Field field, Object newValue) throws Exception { + field.setAccessible(true); + // remove final modifier from field + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~ Modifier.FINAL); + field.set(null, newValue); + } + + private void initializeConfiguration() throws Exception { + Field privateCurrentSwitcherState = Configuration.class.getDeclaredField("currentSwitcherState"); + privateCurrentSwitcherState.setAccessible(true); + privateCurrentSwitcherState.set(Configuration.class, Constants.ConfigSwitcherState.DEFAULT); + Configuration.loadConfig(); + } + + /** + * Test Default configurations + */ + @Test + public void testDefaultConfigurationSettings() { + try { + initializeConfiguration(); + assertEquals(5, Configuration.getStatusReportFreqSeconds()); + assertEquals(60, Configuration.getPingControllerFreqSeconds()); + assertEquals(1, Configuration.getSpeedCalculationFreqMinutes()); + assertEquals(10, Configuration.getMonitorSshTunnelStatusFreqSeconds()); + assertEquals(10, Configuration.getMonitorContainersStatusFreqSeconds()); + assertEquals(60, Configuration.getMonitorRegistriesStatusFreqSeconds()); + assertEquals(5, Configuration.getGetUsageDataFreqSeconds()); + assertEquals("1.23", Configuration.getDockerApiVersion()); + assertEquals(60, Configuration.getSetSystemTimeFreqSeconds()); + assertEquals("/etc/iofog-agent/cert.crt", Configuration.getControllerCert()); + assertEquals("http://localhost:54421/api/v3/",Configuration.getControllerUrl()); + assertEquals("unix:///var/run/docker.sock", Configuration.getDockerUrl()); + assertEquals("/var/lib/iofog-agent/", Configuration.getDiskDirectory()); + assertEquals(10, Configuration.getDiskLimit(), 0); + assertEquals(4096, Configuration.getMemoryLimit(), 0); + assertEquals(80.0, Configuration.getCpuLimit(), 0); + assertEquals(10.0, Configuration.getLogFileCount(), 0); + assertEquals(20.0, Configuration.getAvailableDiskThreshold(), 0); + assertEquals("Default value", "dynamic", Configuration.getNetworkInterface()); + assertEquals("Default value", "not found(dynamic)", Configuration.getNetworkInterfaceInfo()); + assertEquals("Default value", 10.0, Configuration.getLogDiskLimit(), 0); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getter and setters + */ + @Test + public void testGettersAndSetters() { + try { + initializeConfiguration(); + assertFalse("Default Value", Configuration.isWatchdogEnabled()); + Configuration.setWatchdogEnabled(true); + assertTrue("New Value", Configuration.isWatchdogEnabled()); + assertEquals("Default Value", 30, Configuration.getStatusFrequency()); + Configuration.setStatusFrequency(60); + assertEquals("New Value",60, Configuration.getStatusFrequency()); + assertEquals("Default Value", 60, Configuration.getChangeFrequency()); + Configuration.setChangeFrequency(30); + assertEquals("New Value",30, Configuration.getChangeFrequency()); + assertEquals("Default Value", 60, Configuration.getDeviceScanFrequency()); + Configuration.setDeviceScanFrequency(30); + assertEquals("New Value",30, Configuration.getDeviceScanFrequency()); + assertNotNull(Configuration.getGpsCoordinates()); + Configuration.setGpsCoordinates("-37.6878,170.100"); + assertEquals("New Value","-37.6878,170.100", Configuration.getGpsCoordinates()); + assertEquals("Default value", GpsMode.AUTO, Configuration.getGpsMode()); + Configuration.setGpsMode(GpsMode.DYNAMIC); + assertEquals("New Value",GpsMode.DYNAMIC, Configuration.getGpsMode()); + assertEquals("Default value", 10, Configuration.getPostDiagnosticsFreq()); + Configuration.setPostDiagnosticsFreq(60); + assertEquals("New Value",60, Configuration.getPostDiagnosticsFreq()); + assertEquals("Default value", ArchitectureType.INTEL_AMD, Configuration.getFogType()); + Configuration.setFogType(ArchitectureType.ARM); + assertEquals("New Value",ArchitectureType.ARM, Configuration.getFogType()); + assertEquals("Default value", false, Configuration.isSecureMode()); + Configuration.setSecureMode(false); + assertEquals("New Value", false, Configuration.isSecureMode()); + assertNotNull("Default value", Configuration.getIpAddressExternal()); + Configuration.setIpAddressExternal("ipExternal"); + assertEquals("New Value", "ipExternal", Configuration.getIpAddressExternal()); + assertEquals("Default value", "INFO", Configuration.getLogLevel()); + Configuration.setLogLevel("SEVERE"); + assertEquals("New Value", "SEVERE", Configuration.getLogLevel()); + assertEquals("Default value", "/var/log/iofog-agent/", Configuration.getLogDiskDirectory()); + Configuration.setLogDiskDirectory("/var/new-log/"); + assertEquals("New Value", "/var/new-log/", Configuration.getLogDiskDirectory()); + assertEquals("Default value", "", Configuration.getIofogUuid()); + Configuration.setIofogUuid("uuid"); + assertEquals("New Value", "uuid", Configuration.getIofogUuid()); + assertEquals("Default value", "", Configuration.getAccessToken()); + Configuration.setAccessToken("token"); + assertEquals("New Value", "token", Configuration.getAccessToken()); + assertEquals("Default value", false, Configuration.isDevMode()); + Configuration.setDevMode(true); + assertEquals("New Value", true, Configuration.isDevMode()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getOldNodeValuesForParameters + */ + @Test + public void testGetOldNodeValuesForParameters() { + try { + initializeConfiguration(); + Set config = new HashSet<>(); + config.add("ll"); + HashMap oldValuesMap = Configuration.getOldNodeValuesForParameters(config, Configuration.getCurrentConfig()); + for (HashMap.Entry element : oldValuesMap.entrySet()) { + assertEquals("New Value", Configuration.getLogLevel(), element.getValue()); + } + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test saveConfigUpdates + */ + @Test + public void testSaveConfigUpdates() { + try { + initializeConfiguration(); + Configuration.saveConfigUpdates(); + Mockito.verify(processManager, Mockito.atLeastOnce()).instanceConfigUpdated(); + Mockito.verify(fieldAgent, Mockito.atLeastOnce()).instanceConfigUpdated(); + Mockito.verify(messageBus, Mockito.atLeastOnce()).instanceConfigUpdated(); + Mockito.verify(processManager, Mockito.atLeastOnce()).instanceConfigUpdated(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test reset + * + */ + @Test + public void testResetToDefault() { + try { + initializeConfiguration(); + Configuration.setWatchdogEnabled(true); + assertTrue("New Value", Configuration.isWatchdogEnabled()); + Configuration.setStatusFrequency(60); + assertEquals("New Value",60, Configuration.getStatusFrequency()); + Configuration.setChangeFrequency(30); + assertEquals("New Value",30, Configuration.getChangeFrequency()); + Configuration.setDeviceScanFrequency(30); + assertEquals("New Value",30, Configuration.getDeviceScanFrequency()); + assertNotNull(Configuration.getGpsCoordinates()); + Configuration.setGpsCoordinates("-37.6878,170.100"); + assertEquals("New Value","-37.6878,170.100", Configuration.getGpsCoordinates()); + Configuration.setGpsMode(GpsMode.DYNAMIC); + assertEquals("New Value",GpsMode.DYNAMIC, Configuration.getGpsMode()); + Configuration.setPostDiagnosticsFreq(60); + assertEquals("New Value",60, Configuration.getPostDiagnosticsFreq()); + Configuration.setFogType(ArchitectureType.ARM); + assertEquals("New Value",ArchitectureType.ARM, Configuration.getFogType()); + Configuration.setSecureMode(false); + assertEquals("New Value", false, Configuration.isSecureMode()); + Configuration.setIpAddressExternal("ipExternal"); + assertEquals("New Value", "ipExternal", Configuration.getIpAddressExternal()); + Configuration.setLogLevel("SEVERE"); + assertEquals("New Value", "SEVERE", Configuration.getLogLevel()); + Configuration.setDevMode(true); + assertEquals("New Value", true, Configuration.isDevMode()); + Configuration.resetToDefault(); + assertFalse("Default Value", Configuration.isWatchdogEnabled()); + assertEquals("Default Value", 10, Configuration.getStatusFrequency()); + assertEquals("Default Value", 20, Configuration.getChangeFrequency()); + assertEquals("Default Value", 60, Configuration.getDeviceScanFrequency()); + assertEquals("Default value", GpsMode.AUTO, Configuration.getGpsMode()); + assertEquals("Default value", 10, Configuration.getPostDiagnosticsFreq()); + assertEquals("Default value", false, Configuration.isSecureMode()); + assertEquals("Default value", false, Configuration.isDevMode()); + assertNotNull("Default value", Configuration.getIpAddressExternal()); + assertEquals("Default value", "INFO", Configuration.getLogLevel()); + } catch(Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setGpsDataIfValid + */ + @Test + public void testSetGpsDataIfValid() { + try { + Configuration.setGpsDataIfValid(GpsMode.OFF, "-7.6878,00.100"); + assertEquals("New Value",GpsMode.OFF, Configuration.getGpsMode()); + assertEquals("New Value","-7.6878,00.100", Configuration.getGpsCoordinates()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test writeGpsToConfigFile + */ + @Test + public void testWriteGpsToConfigFile() { + try { + initializeConfiguration(); + Configuration.writeGpsToConfigFile(); + PowerMockito.verifyStatic(LoggingService.class, Mockito.atLeastOnce()); + LoggingService.logDebug("Configuration", "Finished writing GPS coordinates and GPS mode to config file"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test loadConfig + */ + @Test + public void testLoadConfig() { + try { + Field privateCurrentSwitcherState = Configuration.class.getDeclaredField("currentSwitcherState"); + privateCurrentSwitcherState.setAccessible(true); + privateCurrentSwitcherState.set(Configuration.class, Constants.ConfigSwitcherState.DEFAULT); + Configuration.loadConfig(); + PowerMockito.verifyPrivate(Configuration.class).invoke("setIofogUuid", Mockito.any()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setAccessToken", Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test loadConfigSwitcher + */ + @Test + public void testLoadConfigSwitcher() { + try { + Configuration.loadConfigSwitcher(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Start loads configuration about current config from config-switcher.xml"); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logInfo(MODULE_NAME, "Finished loading configuration about current config from config-switcher.xml"); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("getFirstNodeByTagName", + Mockito.eq(SWITCHER_ELEMENT), Mockito.any(Document.class)); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("verifySwitcherNode", + Mockito.eq(SWITCHER_NODE), Mockito.eq(Constants.ConfigSwitcherState.DEFAULT.fullValue())); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getConfigReport + */ + @Test + public void testGetConfigReport() { + try { + initializeConfiguration(); + String report = Configuration.getConfigReport(); + assertTrue(report.contains("Iofog UUID")); + assertTrue(report.contains("Network Interface")); + assertTrue(report.contains("Docker URL")); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getCurrentConfig + */ + @Test + public void tetGetCurrentConfig() { + try { + initializeConfiguration(); + assertNotNull(Configuration.getCurrentConfig()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test getCurrentConfigPath + */ + @Test + public void testGetCurrentConfigPath() { + try { + initializeConfiguration(); + assertEquals(MOCK_DEFAULT_CONFIG_PATH, Configuration.getCurrentConfigPath()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setupConfigSwitcher when currentSwitcherState is same as previousState + */ + @Test + public void testSetupConfigSwitcherAsDefault() { + try { + initializeConfiguration(); + assertEquals("Already using this configuration.", Configuration.setupConfigSwitcher(Constants.ConfigSwitcherState.DEFAULT)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test load + */ + @Test + public void testLoad() { + try { + Configuration.load(); + PowerMockito.verifyStatic(Configuration.class); + Configuration.loadConfigSwitcher(); + PowerMockito.verifyStatic(Configuration.class); + Configuration.loadConfig(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setupSupervisor + */ + @Test + public void testSetupSupervisor() { + try { + Configuration.setupSupervisor(); + Mockito.verify(supervisor).start(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setupSupervisor + */ + @Test + public void testSupervisorThrowsExceptionOnSetupSupervisor() { + try { + PowerMockito.doThrow(mock(Exception.class)).when(supervisor).start(); + Configuration.setupSupervisor(); + Mockito.verify(supervisor).start(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(Mockito.eq("Configuration"), Mockito.eq("Error while starting supervisor"), Mockito.any()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when config is blank + */ + @Test + public void testSetConfigWhenConfigIsBlank() { + try { + initializeConfiguration(); + Map config = new HashMap<>(); + config.put("d", " "); + suppress(method(Configuration.class, "updateConfigFile")); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("Parameter error", k); + assertEquals("Command or value is invalid", v); + }); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.instanceConfigUpdated(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when config is null + */ + @Test + public void testSetConfigWhenConfigIsNull() { + try { + suppress(method(Configuration.class, "updateConfigFile")); + HashMap messageMap = Configuration.setConfig(null, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("invalid", k); + assertEquals("Option and value are null", v); + }); + PowerMockito.verifyPrivate(Configuration.class, never()).invoke("setNode", Mockito.any(CommandLineConfigParam.class), Mockito.anyString(), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class, never()).invoke("setLogLevel", Mockito.anyString()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DISK_CONSUMPTION_LIMIT is invalid which is string instead of float + */ + @Test + public void testSetConfigForDiskConsumptionLimitIsNotValid() { + try { + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("d", "disk"); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("d", k); + assertEquals("Option -d has invalid value: disk", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DISK_CONSUMPTION_LIMIT is invalid + * Disk limit range is not between 1 to 1048576 GB + */ + @Test + public void testSetConfigForDiskConsumptionLimitIsNotWithInRange() { + try { + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("d", "10485769"); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("d", k); + assertEquals("Disk limit range must be 1 to 1048576 GB", v); + }); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.instanceConfigUpdated(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DISK_CONSUMPTION_LIMIT is invalid + * Disk limit range is between 1 to 1048576 GB + */ + @Test + public void testSetConfigForDiskConsumptionLimitIsValid() { + try { + initializeConfiguration(); + String value = "30"; + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("d", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(30, Configuration.getDiskLimit(), 0); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(DISK_CONSUMPTION_LIMIT), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setDiskLimit", Mockito.eq(Float.parseFloat(value))); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DISK_DIRECTORY with valid string + */ + @Test + public void testSetConfigForDiskDirectory() { + try { + String value = "dir"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("dl", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals("/dir/", Configuration.getDiskDirectory()); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class).invoke("addSeparator", Mockito.eq(value)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(DISK_DIRECTORY), Mockito.eq("dir/"), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setDiskDirectory", Mockito.eq("dir/")); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.instanceConfigUpdated(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when MEMORY_CONSUMPTION_LIMIT with valid string + */ + @Test + public void testSetConfigForMemoryConsumptionLimitWhichIsInvalid() { + try { + String value = "dir"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("m", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("m", k); + assertEquals("Option -m has invalid value: dir", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when MEMORY_CONSUMPTION_LIMIT with invalid range + */ + @Test + public void testSetConfigForMemoryConsumptionLimitIsInValidRange() { + try { + String value = "127"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("m", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("m", k); + assertEquals("Memory limit range must be 128 to 1048576 MB", v); + }); + PowerMockito.verifyPrivate(Configuration.class, never()).invoke("setMemoryLimit", Mockito.eq(Float.parseFloat(value))); + PowerMockito.verifyPrivate(Configuration.class, never()).invoke("setNode", Mockito.eq(MEMORY_CONSUMPTION_LIMIT), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when MEMORY_CONSUMPTION_LIMIT with valid string + */ + @Test + public void testSetConfigForMemoryConsumptionLimitIsValidRange() { + try { + String value = "5000"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("m", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setMemoryLimit", Mockito.eq(Float.parseFloat(value))); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(MEMORY_CONSUMPTION_LIMIT), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when PROCESSOR_CONSUMPTION_LIMIT with invalid string + */ + @Test + public void testSetConfigForProcessorConsumptionLimitWithInValidValue() { + try { + String value = "limit"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("p", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("p", k); + assertEquals("Option -p has invalid value: limit", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when PROCESSOR_CONSUMPTION_LIMIT with invalid range + */ + @Test + public void testSetConfigForProcessorConsumptionLimitWithInValidRange() { + try { + String value = "200"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("p", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("p", k); + assertEquals("CPU limit range must be 5% to 100%", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when PROCESSOR_CONSUMPTION_LIMIT with valid range + */ + @Test + public void testSetConfigForProcessorConsumptionLimitWithValidRange() { + try { + String value = "50"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("p", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setCpuLimit", Mockito.eq(Float.parseFloat(value))); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(PROCESSOR_CONSUMPTION_LIMIT), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when CONTROLLER_URL with valid value + */ + @Test + public void testSetConfigForControllerUrlWithInvalidValue() { + try { + String value = "certificate"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("a", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + assertEquals(value+"/", Configuration.getControllerUrl()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setControllerUrl", Mockito.eq(value)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(CONTROLLER_URL), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when CONTROLLER_CERT with valid value + */ + @Test + public void testSetConfigForControllerCertWithInvalidValue() { + try { + String value = "http://controllerCert"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("ac", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + assertEquals(value, Configuration.getControllerCert()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setControllerCert", Mockito.eq(value)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(CONTROLLER_CERT), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DOCKER_URL with invalid value + */ + @Test + public void testSetConfigForDockerUrlWithInvalidValue() { + try { + String value = "http://localhost/dockerUrl"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("c", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("c", k); + assertEquals("Unsupported protocol scheme. Only 'tcp://' or 'unix://' supported.\n", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DOCKER_URL with valid value + */ + @Test + public void testSetConfigForDockerUrlWithValidValue() { + try { + String value = "tcp://localhost/dockerUrl"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("c", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + assertEquals(value, Configuration.getDockerUrl()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setDockerUrl", Mockito.eq(value)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(DOCKER_URL), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when NETWORK_INTERFACE with valid value + */ + @Test + public void testSetConfigForNetworkInterfaceWithValidValue() { + try { + String value = "http://networkUrl"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("n", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + assertEquals(value, Configuration.getNetworkInterface()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNetworkInterface", Mockito.eq(value)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(NETWORK_INTERFACE), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when LOG_DISK_CONSUMPTION_LIMIT with invalid value + */ + @Test + public void testSetConfigForLogDiskConsumptionLimitWithInValidValue() { + try { + String value = "logLimit"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("l", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("l", k); + assertEquals("Option -l has invalid value: logLimit", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when LOG_DISK_CONSUMPTION_LIMIT with invalid range + */ + @Test + public void testSetConfigForLogDiskConsumptionLimitWithInValidRange() { + try { + String value = "110"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("l", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("l", k); + assertEquals("Log disk limit range must be 0.5 to 100 GB", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when LOG_DISK_CONSUMPTION_LIMIT with valid value + */ + @Test + public void testSetConfigForLogDiskConsumptionLimitWithValidValue() { + try { + String value = "1"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("l", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + assertEquals(Float.parseFloat(value), Configuration.getLogDiskLimit(), 0); + PowerMockito.verifyPrivate(Configuration.class).invoke("setLogDiskLimit", Mockito.eq(Float.parseFloat(value))); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(LOG_DISK_CONSUMPTION_LIMIT), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when LOG_DISK_DIRECTORY with valid value + */ + @Test + public void testSetConfigForLogDiskDirectoryWithValidValue() { + try { + String value = "dir"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("ld", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + assertEquals("/"+value+"/", Configuration.getLogDiskDirectory()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setLogDiskDirectory", Mockito.eq(value+"/")); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(LOG_DISK_DIRECTORY), Mockito.eq(value+"/"), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when LOG_FILE_COUNT with invalid value + */ + @Test + public void testSetConfigForLogFileCountWithInValidValue() { + try { + String value = "count"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("lc", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("lc", k); + assertEquals("Option -lc has invalid value: count", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test setConfig when LOG_FILE_COUNT with invalid range + */ + @Test + public void testSetConfigForLogFileCountWithInValidRange() { + try { + String value = "120"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("lc", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("lc", k); + assertEquals("Log file count range must be 1 to 100", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when LOG_FILE_COUNT with valid value + */ + @Test + public void testSetConfigForLogFileCountWithValidValue() { + try { + String value = "20"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("lc", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + assertEquals(Integer.parseInt(value), Configuration.getLogFileCount()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setLogFileCount", Mockito.eq(Integer.parseInt(value))); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(LOG_FILE_COUNT), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when setConfig of invalid log level is called + */ + @Test + public void testSetConfigForLogLevelIsNotValid() { + try { + String value = "terrific"; + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("ll", "terrific"); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("ll", k); + assertEquals("Option -ll has invalid value: terrific", v); + }); + PowerMockito.verifyPrivate(Configuration.class, never()).invoke("setNode", Mockito.eq(LOG_LEVEL), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class, never()).invoke("setLogLevel", Mockito.eq(value)); + PowerMockito.verifyStatic(LoggingService.class, never()); + LoggingService.instanceConfigUpdated(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when setConfig of valid log level is called + */ + @Test + public void testSetConfigForLogLevelIsValid() { + try { + String value = "severe"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("ll", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(value.toUpperCase(), Configuration.getLogLevel()); + assertTrue(messageMap.size() == 0); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(LOG_LEVEL), Mockito.eq(value.toUpperCase()), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setLogLevel", Mockito.eq(value.toUpperCase())); + PowerMockito.verifyStatic(LoggingService.class, atLeastOnce()); + LoggingService.instanceConfigUpdated(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when STATUS_FREQUENCY is invalid value + */ + @Test + public void testSetConfigForStatusFrequencyIsInValid() { + try { + String value = "frequency"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("sf", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("sf", k); + assertEquals("Option -sf has invalid value: frequency", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when STATUS_FREQUENCY is less than 1 + */ + @Test + public void testSetConfigForStatusFrequencyIsLessThanOne() { + try { + String value = "0"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("sf", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("sf", k); + assertEquals("Status update frequency must be greater than 1", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when STATUS_FREQUENCY is valid value + */ + @Test + public void testSetConfigForStatusFrequencyIsValid() { + try { + String value = "40"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("sf", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(Integer.parseInt(value), Configuration.getStatusFrequency()); + assertTrue(messageMap.size() == 0); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(STATUS_FREQUENCY), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setStatusFrequency", Mockito.eq(Integer.parseInt(value))); + } catch (Exception e) { + fail("This should not happen"); + } + } + + + /** + * Test setConfig when CHANGE_FREQUENCY is invalid value + */ + @Test + public void testSetConfigForChangeFrequencyIsInValid() { + try { + String value = "frequency"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("cf", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("cf", k); + assertEquals("Option -cf has invalid value: frequency", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when CHANGE_FREQUENCY is less than 1 + */ + @Test + public void testSetConfigForChangerequencyIsLessThanOne() { + try { + String value = "0"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("cf", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("cf", k); + assertEquals("Get changes frequency must be greater than 1", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when CHANGE_FREQUENCY is valid value + */ + @Test + public void testSetConfigForChangeFrequencyIsValid() { + try { + String value = "40"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("cf", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(Integer.parseInt(value), Configuration.getChangeFrequency()); + assertTrue(messageMap.size() == 0); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(CHANGE_FREQUENCY), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setChangeFrequency", Mockito.eq(Integer.parseInt(value))); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DEVICE_SCAN_FREQUENCY is invalid value + */ + @Test + public void testSetConfigForDeviceScanFrequencyIsInValid() { + try { + String value = "frequency"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("sd", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("sd", k); + assertEquals("Option -sd has invalid value: frequency", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DEVICE_SCAN_FREQUENCY is less than 1 + */ + @Test + public void testSetConfigForDeviceScanFrequencyIsLessThanOne() { + try { + String value = "0"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("sd", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("sd", k); + assertEquals("Get scan devices frequency must be greater than 1", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DEVICE_SCAN_FREQUENCY is valid value + */ + @Test + public void testSetConfigForDeviceScanFrequencyIsValid() { + try { + String value = "40"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("sd", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(Integer.parseInt(value), Configuration.getDeviceScanFrequency()); + assertTrue(messageMap.size() == 0); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(DEVICE_SCAN_FREQUENCY), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setDeviceScanFrequency", Mockito.eq(Integer.parseInt(value))); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when POST_DIAGNOSTICS_FREQ is invalid value + */ + @Test + public void testSetConfigForPostDiagnosticFrequencyIsInValid() { + try { + String value = "frequency"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("df", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("df", k); + assertEquals("Option -df has invalid value: frequency", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when POST_DIAGNOSTICS_FREQ is less than 1 + */ + @Test + public void testSetConfigForPostDiagnosticFrequencyIsLessThanOne() { + try { + String value = "0"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("df", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("df", k); + assertEquals("Post diagnostics frequency must be greater than 1", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when POST_DIAGNOSTICS_FREQ is valid value + */ + @Test + public void testSetConfigForPostDiagnosticFrequencyIsValid() { + try { + String value = "40"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("df", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(Integer.parseInt(value), Configuration.getPostDiagnosticsFreq()); + assertTrue(messageMap.size() == 0); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(POST_DIAGNOSTICS_FREQ), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setPostDiagnosticsFreq", Mockito.eq(Integer.parseInt(value))); + } catch (Exception e) { + fail("This should not happen"); + } + } + + + /** + * Test setConfig when WATCHDOG_ENABLED with invalid value + */ + @Test + public void testSetConfigForWatchdogEnabledWithInValidValue() { + try { + String value = "watchDog"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("idc", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("idc", k); + assertEquals("Option -idc has invalid value: watchDog", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when WATCHDOG_ENABLED with invalid value + */ + @Test + public void testSetConfigForWatchdogEnabledWithInValidValueAsInteger() { + try { + int value = 10; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("idc", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("idc", k); + assertEquals("Option -idc has invalid value: 10", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when WATCHDOG_ENABLED with valid value + */ + @Test + public void testSetConfigForWatchdogEnabledWithValidValue() { + try { + String value = "on"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("idc", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + assertTrue(Configuration.isWatchdogEnabled()); + PowerMockito.verifyPrivate(Configuration.class).invoke("setNode", Mockito.eq(WATCHDOG_ENABLED), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class).invoke("setWatchdogEnabled", Mockito.eq(!value.equals("off"))); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when GPS_MODE with invalid value + */ + @Test + public void testSetConfigForGPSModeWithInValidValue() { + try { + String value = "on"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("gps", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("gps", k); + assertEquals("Option -gps has invalid value: on", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when GPS_MODE with valid value + */ + @Test + public void testSetConfigForGPSModeWithValidValue() { + try { + String value = "off"; + suppress(method(Configuration.class, "saveConfigUpdates")); + initializeConfiguration(); + Map config = new HashMap<>(); + config.put(GPS_MODE.getCommandName(), value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(GpsMode.OFF, Configuration.getGpsMode()); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("writeGpsToConfigFile"); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("configureGps", Mockito.eq(value), Mockito.anyString()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when GPS_Coordinate is set with valid coordinates and gps_mode is switched to manual + */ + @Test + public void testSetConfigForGPSModeWithValidCoordinates() { + try { + String value = "0,0"; + suppress(method(Configuration.class, "saveConfigUpdates")); + initializeConfiguration(); + Map config = new HashMap<>(); + config.put(GPS_MODE.getCommandName(), value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(GpsMode.MANUAL, Configuration.getGpsMode()); + assertEquals(value, Configuration.getGpsCoordinates()); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("writeGpsToConfigFile"); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("configureGps", Mockito.eq(value), Mockito.anyString()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("isValidCoordinates", Mockito.eq("0,0")); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when GPS_Coordinate is set with invalid coordinates and gps_mode is switched to manual + */ + @Test + public void testSetConfigForGPSModeWithInValidCoordinates() { + try { + String value = "I am invalid coordinates"; + suppress(method(Configuration.class, "saveConfigUpdates")); + initializeConfiguration(); + Map config = new HashMap<>(); + config.put(GPS_MODE.getCommandName(), value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("gps", k); + assertEquals("Option -gps has invalid value: I am invalid coordinates", v); + }); + assertNotEquals(value, Configuration.getGpsCoordinates()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("writeGpsToConfigFile"); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("configureGps", Mockito.eq(value), Mockito.anyString()); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when FOG_TYPE with invalid value + */ + @Test + public void testSetConfigForFogTypeWithInValidValue() { + try { + String value = "value"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("ft", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(1, messageMap.size()); + messageMap.forEach((k, v) -> { + assertEquals("ft", k); + assertEquals("Option -ft has invalid value: value", v); + }); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when FOG_TYPE with valid value + */ + @Test + public void testSetConfigForFogTypeWithValidValue() { + try { + String value = "auto"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("ft", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setNode", Mockito.eq(FOG_TYPE), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("configureFogType", Mockito.eq(value)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DEV_MODE with invalid value + */ + @Test + public void testSetConfigForSecureModeWithInValidValue() { + try { + String value = "1020"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("sec", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setNode", Mockito.eq(SECURE_MODE), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setSecureMode", Mockito.eq(!value.equals("off"))); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DEV_MODE with valid value + */ + @Test + public void testSetConfigForSecureModeWithValidValue() { + try { + String value = "off"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("sec", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setNode", Mockito.eq(SECURE_MODE), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setSecureMode", Mockito.eq(!value.equals("off"))); + } catch (Exception e) { + fail("This should not happen"); + } + } + /** + * Test setConfig when DEV_MODE with invalid value + */ + @Test + public void testSetConfigForDevModeWithInValidValue() { + try { + String value = "1020"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("dev", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setNode", Mockito.eq(DEV_MODE), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setDevMode", Mockito.eq(!value.equals("off"))); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setConfig when DEV_MODE with valid value + */ + @Test + public void testSetConfigForDevModeWithValidValue() { + try { + String value = "off"; + initializeConfiguration(); + suppress(method(Configuration.class, "saveConfigUpdates")); + Map config = new HashMap<>(); + config.put("dev", value); + HashMap messageMap = Configuration.setConfig(config, false); + assertEquals(0, messageMap.size()); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setNode", Mockito.eq(DEV_MODE), Mockito.eq(value), + Mockito.any(Document.class), Mockito.any(Element.class)); + PowerMockito.verifyPrivate(Configuration.class, Mockito.atLeastOnce()).invoke("setDevMode", Mockito.eq(!value.equals("off"))); + } catch (Exception e) { + fail("This should not happen"); + } + } + +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/device_info/ArchitectureTypeTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/device_info/ArchitectureTypeTest.java new file mode 100644 index 00000000..f0825645 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/device_info/ArchitectureTypeTest.java @@ -0,0 +1,57 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils.device_info; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import static org.junit.Assert.*; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ArchitectureType.class}) +public class ArchitectureTypeTest { + + /** + * Test getCode + */ + @Test + public void testGetCode() { + assertEquals(2, ArchitectureType.ARM.getCode()); + assertEquals(1, ArchitectureType.INTEL_AMD.getCode()); + assertEquals(0, ArchitectureType.UNDEFINED.getCode()); + } + + /** + * Test getArchTypeByArchName + */ + @Test + public void testGetArchTypeByArchName() { + assertEquals(ArchitectureType.ARM, ArchitectureType.getArchTypeByArchName("arm")); + assertEquals(ArchitectureType.INTEL_AMD, ArchitectureType.getArchTypeByArchName("x32")); + assertEquals(ArchitectureType.UNDEFINED, ArchitectureType.getArchTypeByArchName("")); + + } + + /** + * Test getDeviceArchType + */ + @Test + public void testGetDeviceArchType() { + assertEquals(ArchitectureType.INTEL_AMD, ArchitectureType.getDeviceArchType()); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LogFormatterTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LogFormatterTest.java new file mode 100644 index 00000000..54777408 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LogFormatterTest.java @@ -0,0 +1,72 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils.logging; + +import org.eclipse.iofog.network.IOFogNetworkInterfaceManager; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.util.Formatter; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({LogFormatter.class, LogRecord.class, Configuration.class, IOFogNetworkInterfaceManager.class}) +public class LogFormatterTest { + private LogRecord logRecord; + private LogFormatter logFormatter; + private IOFogNetworkInterfaceManager fogNetworkInterfaceManager; + + @Before + public void setUp() throws Exception { + fogNetworkInterfaceManager = mock(IOFogNetworkInterfaceManager.class); + PowerMockito.mockStatic(IOFogNetworkInterfaceManager.class); + PowerMockito.mockStatic(Configuration.class); + PowerMockito.when(Configuration.getIofogUuid()).thenReturn("uuid"); + PowerMockito.when(IOFogNetworkInterfaceManager.getInstance()).thenReturn(fogNetworkInterfaceManager); + PowerMockito.when(fogNetworkInterfaceManager.getPid()).thenReturn((long) 12324); + PowerMockito.when(fogNetworkInterfaceManager.getHostName()).thenReturn("hostname"); + + logRecord = mock(LogRecord.class); + logFormatter = PowerMockito.spy(new LogFormatter()); + PowerMockito.when(logRecord.getMessage()).thenReturn("log"); + PowerMockito.when(logRecord.getLevel()).thenReturn(Level.SEVERE); + PowerMockito.when(logRecord.getSourceClassName()).thenReturn("Thread"); + PowerMockito.when(logRecord.getSourceMethodName()).thenReturn("module"); + PowerMockito.when(logRecord.getThrown()).thenReturn(new Exception("I'm a mock exception")); + } + + @After + public void tearDown() throws Exception { + } + + /** + * Test format + */ + @Test + public void testFormat() { + assertTrue(logFormatter.format(logRecord).contains("SEVERE")); + } +} \ No newline at end of file diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LoggingServiceTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LoggingServiceTest.java new file mode 100644 index 00000000..8ab7df80 --- /dev/null +++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LoggingServiceTest.java @@ -0,0 +1,289 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ +package org.eclipse.iofog.utils.logging; + + +import org.eclipse.iofog.utils.CmdProperties; +import org.eclipse.iofog.utils.configuration.Configuration; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.util.Properties; +import java.util.logging.FileHandler; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; +import static org.powermock.api.mockito.PowerMockito.*; + +/** + * @author nehanaithani + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({LoggingService.class, Configuration.class, Logger.class, File.class, FileHandler.class, FileSystems.class, FileSystem.class, + UserPrincipalLookupService.class, Files.class, PosixFileAttributeView.class, Handler.class, Properties.class, CmdProperties.class }) +public class LoggingServiceTest { + private String MODULE_NAME; + private String message; + private File file; + private Logger logger; + private FileHandler fileHandler; + private String microUuid; + private long logSize; + private FileSystem fileSystem; + private UserPrincipalLookupService userPrincipalLookupService; + private Files files; + private PosixFileAttributeView posixFileAttributeView; + private Handler handler; + + @Before + public void setUp() throws Exception { + mockStatic(LoggingService.class, Mockito.CALLS_REAL_METHODS); + PowerMockito.mockStatic(Configuration.class); + PowerMockito.mockStatic(FileSystems.class); + PowerMockito.mockStatic(Files.class); + mockStatic(Logger.class); + mockStatic(CmdProperties.class); + file = Mockito.mock(File.class); + logger = Mockito.mock(Logger.class); + fileHandler = Mockito.mock(FileHandler.class); + fileSystem = Mockito.mock(FileSystem.class); + handler = Mockito.mock(Handler.class); + posixFileAttributeView = Mockito.mock(PosixFileAttributeView.class); + userPrincipalLookupService = Mockito.mock(UserPrincipalLookupService.class); + MODULE_NAME = "LoggingService"; + message = "message to be logged"; + microUuid = "microserviceUuid"; + logSize = 10; + Handler[] handlers = new Handler[1]; + handlers[0] = handler; + PowerMockito.when(Configuration.getDiskLimit()).thenReturn(1000.0f); + PowerMockito.when(Configuration.getLogDiskLimit()).thenReturn(10.0f); + PowerMockito.when(Configuration.getLogFileCount()).thenReturn(10); + PowerMockito.when(Configuration.getLogLevel()).thenReturn("info"); + PowerMockito.when(Configuration.getLogDiskDirectory()).thenReturn("/log/"); + PowerMockito.whenNew(File.class).withParameterTypes(String.class).withArguments(Mockito.any()).thenReturn(file); + PowerMockito.whenNew(FileHandler.class) + .withArguments(anyString(), Mockito.anyInt(), Mockito.anyInt()).thenReturn(fileHandler); + PowerMockito.when(file.getPath()).thenReturn("/log/"); + PowerMockito.when(Logger.getLogger(anyString())).thenReturn(logger); + PowerMockito.doNothing().when(logger).addHandler(Mockito.any()); + PowerMockito.when(logger.getHandlers()).thenReturn(handlers); + PowerMockito.doNothing().when(handler).close(); + PowerMockito.when(FileSystems.getDefault()).thenReturn(fileSystem); + PowerMockito.when(fileSystem.getUserPrincipalLookupService()).thenReturn(userPrincipalLookupService); + PowerMockito.when(Files.getFileAttributeView(Mockito.any(), Mockito.any(), Mockito.any())).thenReturn(posixFileAttributeView); + when(CmdProperties.getVersion()).thenReturn("version"); + } + + @After + public void tearDown() throws Exception { + MODULE_NAME = null; + message = null; + microUuid = null; + logger = null; + } + + /** + * Test when logger is not null + */ + @Test + public void testLogInfo() { + try { + LoggingService.setupLogger(); + LoggingService.logInfo(MODULE_NAME, message); + Mockito.verify(logger).logp(Level.INFO, Thread.currentThread().getName(), MODULE_NAME, message); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when logger is not null + */ + @Test + public void testLogWarning() { + try { + LoggingService.setupLogger(); + LoggingService.logWarning(MODULE_NAME, message); + Mockito.verify(logger).logp(Level.WARNING, Thread.currentThread().getName(), MODULE_NAME, message); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when logger is not null + */ + @Test + public void testLogDebug() { + try { + LoggingService.setupLogger(); + LoggingService.logDebug(MODULE_NAME, message); + Mockito.verify(logger).logp(Level.FINE, Thread.currentThread().getName(), MODULE_NAME, message); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when logger is not null + */ + @Test + public void testLogError() { + try { + LoggingService.setupLogger(); + Exception e = new Exception("This is exception"); + LoggingService.logError(MODULE_NAME, message, e); + Mockito.verify(logger).logp(Level.SEVERE, Thread.currentThread().getName(), MODULE_NAME, message, e); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setupLogger + */ + @Test + public void testSetupLogger() { + try { + LoggingService.setupLogger(); + PowerMockito.verifyNew(File.class, Mockito.atLeastOnce()).withArguments(eq(Configuration.getLogDiskDirectory())); + PowerMockito.verifyNew(FileHandler.class).withArguments(eq(file.getPath()+"/iofog-agent.%g.log"), Mockito.anyInt(), Mockito.anyInt()); + Mockito.verify(logger).addHandler(fileHandler); + Mockito.verify(logger).setLevel(Level.INFO); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test setupMicroserviceLogger + */ + @Test + public void testSetupMicroserviceLogger() { + try { + PowerMockito.when(Logger.getLogger(microUuid)).thenReturn(logger); + LoggingService.setupMicroserviceLogger(microUuid, logSize); + PowerMockito.verifyStatic(Logger.class); + Logger.getLogger(microUuid); + Mockito.verify(logger).addHandler(fileHandler); + Mockito.verify(logger).setUseParentHandlers(eq(false)); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when microserviceLogger is null + */ + @Test + public void testMicroserviceLogInfoWhenMicroserviceLoggerIsNull() { + try { + String errorMsg = " Log message parsing error, Logger initialized null"; + LoggingService.setupLogger(); + assertFalse(LoggingService.microserviceLogInfo("uuid", message)); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logWarning(MODULE_NAME, errorMsg); + Mockito.verify(logger).logp(Level.WARNING, Thread.currentThread().getName(), MODULE_NAME, errorMsg); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when microserviceLogger is not null + */ + @Test + public void testMicroserviceLogInfoWhenMicroserviceLoggerIsNotNull() { + try { + LoggingService.setupMicroserviceLogger(microUuid, logSize); + assertTrue(LoggingService.microserviceLogInfo(microUuid, message)); + Mockito.verify(logger, Mockito.atLeastOnce()).info(message); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when microserviceLogger is null + */ + @Test + public void testMicroserviceLogWarningWhenMicroserviceLoggerIsNull() { + try { + LoggingService.setupLogger(); + assertFalse(LoggingService.microserviceLogWarning("uuid", message)); + Mockito.verify(logger).logp(Level.WARNING, Thread.currentThread().getName(), MODULE_NAME, " Log message parsing error, Logger initialized null"); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test when microserviceLogger is not null + */ + @Test + public void testMicroserviceLogWarningWhenMicroserviceLoggerIsNotNull() { + try { + LoggingService.setupMicroserviceLogger(microUuid, logSize); + assertTrue(LoggingService.microserviceLogWarning(microUuid, message)); + Mockito.verify(logger, Mockito.atLeastOnce()).warning(message); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test instanceConfigUpdated + */ + @Test + public void testInstanceConfigUpdated() { + try { + LoggingService.instanceConfigUpdated(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.setupLogger(); + } catch (Exception e) { + fail("This should not happen"); + } + } + + /** + * Test instanceConfigUpdated when setupLogger throws Exception + */ + @Test + public void TestInstanceConfigUpdated() throws IOException { + Exception e = new SecurityException("Error updating logger instance"); + PowerMockito.doThrow(e).when(logger).setLevel(any()); + LoggingService.instanceConfigUpdated(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.setupLogger(); + PowerMockito.verifyStatic(LoggingService.class); + LoggingService.logError(MODULE_NAME, e.getMessage(), e); + Mockito.verify(logger).logp(Level.SEVERE, Thread.currentThread().getName(), MODULE_NAME, e.getMessage(), e); + } +} \ No newline at end of file diff --git a/iofog-version-controller/build.gradle b/iofog-version-controller/build.gradle new file mode 100644 index 00000000..12c3104b --- /dev/null +++ b/iofog-version-controller/build.gradle @@ -0,0 +1,14 @@ +description = 'iofog-version-controller' + +task copyJar(type: Copy) { + from ("$buildDir/libs/") { + include "*.jar" + } + into file('../packaging/iofog-agent/usr/bin/') + rename('.*?(jar$)', 'iofog-agentvc.jar') +} + +jar { + manifest.attributes["Main-Class"] = 'org.eclipse.iofog_version_controller.Main' + manifest.attributes["Implementation-Version"] = rootProject.property('version') +} diff --git a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/Main.java b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/Main.java new file mode 100644 index 00000000..62b25468 --- /dev/null +++ b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/Main.java @@ -0,0 +1,25 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog_version_controller; + +import org.eclipse.iofog_version_controller.command_line.util.CommandShellExecutor; +import org.eclipse.iofog_version_controller.command_line.util.CommandShellResultSet; + +import java.util.List; + +public class Main { + public static void main(String[] args) { + CommandShellExecutor.executeScript(args); + } +} diff --git a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellExecutor.java b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellExecutor.java new file mode 100755 index 00000000..94d1962d --- /dev/null +++ b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellExecutor.java @@ -0,0 +1,81 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog_version_controller.command_line.util; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +/** + * Created by ekrylovich + * on 2/7/18. + */ +public class CommandShellExecutor { + private static final String MODULE_NAME = "CommandShellExecutor"; + private static final String CMD = "/bin/bash"; + private static final String CMD_WIN = "powershell"; + + public static CommandShellResultSet, List> executeScript(String... args) { + String[] fullCommand = computeScript(args); + return execute(fullCommand); + } + + private static CommandShellResultSet, List> execute( String[] fullCommand) { + CommandShellResultSet, List> resultSet = null; + try { + Process process = Runtime.getRuntime().exec(fullCommand); + List value = readOutput(process, Process::getInputStream); + List errors = readOutput(process, Process::getErrorStream); + resultSet = new CommandShellResultSet<>(value, errors); + } catch (IOException e) { + e.printStackTrace(); + } + return resultSet; + } + + + private static String[] computeScript(String... args) { + String[] command = { + isWindows() ? "" : "nohup", + isWindows() ? CMD_WIN : CMD + }; + + Stream s1 = Arrays.stream(command); + Stream s2 = Arrays.stream(args); + return Stream.concat(s1, s2).toArray(String[]::new); + } + + private static List readOutput(Process process, Function streamExtractor) throws IOException { + List result = new ArrayList<>(); + String line; + try (BufferedReader stdInput = new BufferedReader(new + InputStreamReader(streamExtractor.apply(process)))) { + while ((line = stdInput.readLine()) != null) { + result.add(line); + } + } + return result; + } + + private static boolean isWindows() { + String osName = System.getProperty("os.name"); + return osName != null && osName.startsWith("Windows"); + } +} diff --git a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellResultSet.java b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellResultSet.java new file mode 100644 index 00000000..7df6ddbe --- /dev/null +++ b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellResultSet.java @@ -0,0 +1,66 @@ +/* + * ******************************************************************************* + * * Copyright (c) 2018-2022 Edgeworx, Inc. + * * + * * This program and the accompanying materials are made available under the + * * terms of the Eclipse Public License v. 2.0 which is available at + * * http://www.eclipse.org/legal/epl-2.0 + * * + * * SPDX-License-Identifier: EPL-2.0 + * ******************************************************************************* + * + */ + +package org.eclipse.iofog_version_controller.command_line.util; + +import java.util.function.Function; + +/** + * Created by ekrylovich + * on 2/7/18. + */ +public class CommandShellResultSet { + private final E error; + private final V value; + + public CommandShellResultSet(V value, E error) { + this.value = value; + this.error = error; + } + + public E getError() { + return error; + } + + public V getValue() { + return value; + } + + public CommandShellResultSet map(Function, CommandShellResultSet> mapper){ + return mapper.apply(this); + } + + @Override + public String toString() { + return "error=" + error + + ", value=" + value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + CommandShellResultSet that = (CommandShellResultSet) o; + + if (error != null ? !error.equals(that.error) : that.error != null) return false; + return value != null ? value.equals(that.value) : that.value == null; + } + + @Override + public int hashCode() { + int result = error != null ? error.hashCode() : 0; + result = 31 * result + (value != null ? value.hashCode() : 0); + return result; + } +} diff --git a/packaging/iofog-agent/debian.sh b/packaging/iofog-agent/debian.sh new file mode 100644 index 00000000..cd526cab --- /dev/null +++ b/packaging/iofog-agent/debian.sh @@ -0,0 +1,104 @@ +#!/bin/bash + +# killing old running processes +for KILLPID in `ps ax | grep 'iofog-agentd' | awk ' { print $1;}'`; do + kill -9 $KILLPID; +done + +#echo "Starting post-install process..." +useradd -r -U -s /usr/bin/nologin iofog-agent +usermod -aG adm,sudo iofog-agent +echo "Added ioFog-Agent user and group" + +if [ -f /etc/iofog-agent/config.xml ]; +then + rm /etc/iofog-agent/config_new.xml +else + mv /etc/iofog-agent/config_new.xml /etc/iofog-agent/config.xml +fi +echo "Check for config.xml" + +if [ -f /etc/iofog-agent/config-development.xml ]; +then + rm /etc/iofog-agent/config-development_new.xml +else + mv /etc/iofog-agent/config-development_new.xml /etc/iofog-agent/config-development.xml +fi +#echo "Check for config-development.xml" + +if [ -f /etc/iofog-agent/config-production.xml ]; +then + rm /etc/iofog-agent/config-production_new.xml +else + mv /etc/iofog-agent/config-production_new.xml /etc/iofog-agent/config-production.xml +fi +#echo "Check for config-production.xml" + +if [ -f /etc/iofog-agent/config-switcher.xml ]; +then + rm /etc/iofog-agent/config-switcher_new.xml +else + mv /etc/iofog-agent/config-switcher_new.xml /etc/iofog-agent/config-switcher.xml +fi +#echo "Check for config-switcher.xml" + +if [ -f /etc/iofog-agent/cert.crt ]; +then + rm /etc/iofog-agent/cert_new.crt +else + mv /etc/iofog-agent/cert_new.crt /etc/iofog-agent/cert.crt +fi +echo "Check for cert.crt" + +if [ -f /etc/iofog-agent/config-bck.xml ]; +then + rm /etc/iofog-agent/config-bck_new.xml +else + mv /etc/iofog-agent/config-bck_new.xml /etc/iofog-agent/config-bck.xml +fi +echo "Check for config-bck.xml" + + /etc/iofog-agent/local-api + +mkdir -p /var/backups/iofog-agent +mkdir -p /var/log/iofog-agent +mkdir -p /var/lib/iofog-agent +mkdir -p /var/run/iofog-agent +mkdir -p /var/log/iofog-microservices + +chown -R :iofog-agent /etc/iofog-agent +chown -R :iofog-agent /var/log/iofog-agent +chown -R :iofog-agent /var/lib/iofog-agent +chown -R :iofog-agent /var/run/iofog-agent +chown -R :iofog-agent /var/backups/iofog-agent +chown -R :iofog-agent /usr/share/iofog-agent +#echo "Changed ownership of directories to iofog-agent group" + +chmod 774 -R /etc/iofog-agent +chmod 774 -R /var/log/iofog-agent +chmod 774 -R /var/lib/iofog-agent +chmod 774 -R /var/run/iofog-agent +chmod 774 -R /var/backups/iofog-agent +chmod 754 -R /usr/share/iofog-agent +#echo "Changed permissions of directories" + +mv /dev/random /dev/random.real +ln -s /dev/urandom /dev/random +#echo "Moved dev pipes for netty" + +chmod 774 /etc/init.d/iofog-agent +#echo "Changed permissions on service script" + +chmod 754 /usr/bin/iofog-agent +#echo "Changed permissions on command line executable file" + +chown :iofog-agent /usr/bin/iofog-agent +#echo "Changed ownership of command line executable file" + +update-rc.d iofog-agent defaults +#echo "Registered init.d script for iofog-agent service" + +ln -sf /usr/bin/iofog-agent /usr/local/bin/iofog-agent +#echo "Added symlink to iofog-agent command executable" + +#echo "...post-install processing completed" diff --git a/packaging/iofog-agent/etc/bash_completion.d/iofog-agent b/packaging/iofog-agent/etc/bash_completion.d/iofog-agent new file mode 100644 index 00000000..52de3246 --- /dev/null +++ b/packaging/iofog-agent/etc/bash_completion.d/iofog-agent @@ -0,0 +1,22 @@ +_iofog-agent() +{ + local cur prev opts base + COMPREPLY=() + cur="${COMP_WORDS[COMP_CWORD]}" + prev="${COMP_WORDS[COMP_CWORD-1]}" + opts="--help --version -h -? -v help version status provision deprovision info config" + + case "${prev}" in + config) + ;; + provision) + ;; + *) + ;; + esac + + + COMPREPLY=($(compgen -W "${opts}" -- ${cur})) + return 0 +} +complete -F _iofog-agent iofog-agent diff --git a/packaging/iofog-agent/etc/init.d/iofog-agent b/packaging/iofog-agent/etc/init.d/iofog-agent new file mode 100644 index 00000000..c8a24dd8 --- /dev/null +++ b/packaging/iofog-agent/etc/init.d/iofog-agent @@ -0,0 +1,41 @@ +#!/bin/sh +### BEGIN INIT INFO +# Provides: iofog-agent +# Required-Start: $local_fs $remote_fs $network $syslog +# Required-Stop: $local_fs $remote_fs $network $syslog +# Default-Start: 2 3 4 5 +# Default-Stop: 0 1 6 +# X-Interactive: true +# Short-Description: Start/stop iofog-agent server +### END INIT INFO + +SERVICE_NAME=iofog-agentd +PATH_TO_JAR=/usr/bin +JAR_FILE_NAME=$PATH_TO_JAR/$SERVICE_NAME.jar +JAVA_VERSION="$(java -version 2>&1 | awk -F '"' '/version/ {print $2}')" + +if [ "$(id -u)" != '0' ]; then + log_failure_msg "$SERVICE_NAME must be run as root" + exit 1 +fi + +case $1 in +start) + mv /dev/random /dev/random.real + ln -s /dev/urandom /dev/random + + echo "Starting iofog-agent service..." + echo "Using Java version ${JAVA_VERSION} found at '$(command -v java)'" + cd $PATH_TO_JAR + java -jar $JAR_FILE_NAME start & + ;; +stop) + cd $PATH_TO_JAR + java -jar $JAR_FILE_NAME stop + ;; +restart) + echo "Restarting iofog-agent service..." + service iofog-agent stop + service iofog-agent start + ;; +esac diff --git a/packaging/iofog-agent/etc/iofog-agent/cert_new.crt b/packaging/iofog-agent/etc/iofog-agent/cert_new.crt new file mode 100644 index 00000000..00930885 --- /dev/null +++ b/packaging/iofog-agent/etc/iofog-agent/cert_new.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIEsTCCA5mgAwIBAgIQCKWiRs1LXIyD1wK0u6tTSTANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD +QTAeFw0xNzExMDYxMjIzMzNaFw0yNzExMDYxMjIzMzNaMF4xCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xHTAbBgNVBAMTFFJhcGlkU1NMIFJTQSBDQSAyMDE4MIIBIjANBgkqhkiG9w0B +AQEFAAOCAQ8AMIIBCgKCAQEA5S2oihEo9nnpezoziDtx4WWLLCll/e0t1EYemE5n ++MgP5viaHLy+VpHP+ndX5D18INIuuAV8wFq26KF5U0WNIZiQp6mLtIWjUeWDPA28 +OeyhTlj9TLk2beytbtFU6ypbpWUltmvY5V8ngspC7nFRNCjpfnDED2kRyJzO8yoK +MFz4J4JE8N7NA1uJwUEFMUvHLs0scLoPZkKcewIRm1RV2AxmFQxJkdf7YN9Pckki +f2Xgm3b48BZn0zf0qXsSeGu84ua9gwzjzI7tbTBjayTpT+/XpWuBVv6fvarI6bik +KB859OSGQuw73XXgeuFwEPHTIRoUtkzu3/EQ+LtwznkkdQIDAQABo4IBZjCCAWIw +HQYDVR0OBBYEFFPKF1n8a8ADIS8aruSqqByCVtp1MB8GA1UdIwQYMBaAFAPeUDVW +0Uy7ZvCj4hsbw5eyPdFVMA4GA1UdDwEB/wQEAwIBhjAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwEgYDVR0TAQH/BAgwBgEB/wIBADA0BggrBgEFBQcBAQQo +MCYwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBCBgNVHR8E +OzA5MDegNaAzhjFodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRHbG9i +YWxSb290Q0EuY3JsMGMGA1UdIARcMFowNwYJYIZIAYb9bAECMCowKAYIKwYBBQUH +AgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMwCwYJYIZIAYb9bAEBMAgG +BmeBDAECATAIBgZngQwBAgIwDQYJKoZIhvcNAQELBQADggEBAH4jx/LKNW5ZklFc +YWs8Ejbm0nyzKeZC2KOVYR7P8gevKyslWm4Xo4BSzKr235FsJ4aFt6yAiv1eY0tZ +/ZN18bOGSGStoEc/JE4ocIzr8P5Mg11kRYHbmgYnr1Rxeki5mSeb39DGxTpJD4kG +hs5lXNoo4conUiiJwKaqH7vh2baryd8pMISag83JUqyVGc2tWPpO0329/CWq2kry +qv66OSMjwulUz0dXf4OHQasR7CNfIr+4KScc6ABlQ5RDF86PGeE6kdwSQkFiB/cQ +ysNyq0jEDQTkfa2pjmuWtMCNbBnhFXBYejfubIhaUbEv2FOQB3dCav+FPg5eEveX +TVyMnGo= +-----END CERTIFICATE----- + diff --git a/packaging/iofog-agent/etc/iofog-agent/config-bck_new.xml b/packaging/iofog-agent/etc/iofog-agent/config-bck_new.xml new file mode 100644 index 00000000..c7f34aa7 --- /dev/null +++ b/packaging/iofog-agent/etc/iofog-agent/config-bck_new.xml @@ -0,0 +1,70 @@ + + + + + + + + + http://localhost:54421/api/v3/ + + + + on + + /etc/iofog-agent/cert.crt + + auto + + dynamic + + unix:///var/run/docker.sock + + 10 + + /var/lib/iofog-agent/ + + 4096 + + 80.0 + + 10.0 + + /var/log/iofog-agent/ + + 10 + + INFO + + 30 + + 60 + + 10 + + 60 + + auto + + 0,0 + + off + + 1 + + 20 + + 24 + + diff --git a/packaging/iofog-agent/etc/iofog-agent/config-development_new.xml b/packaging/iofog-agent/etc/iofog-agent/config-development_new.xml new file mode 100644 index 00000000..040908c4 --- /dev/null +++ b/packaging/iofog-agent/etc/iofog-agent/config-development_new.xml @@ -0,0 +1,70 @@ + + + + + + + + + http://localhost:51121/api/v3/ + + + off + on + + /etc/iofog-agent/cert.crt + + auto + + dynamic + + unix:///var/run/docker.sock + + 10 + + /var/lib/iofog-agent/ + + 4096 + + 80.0 + + 10.0 + + /var/log/iofog-agent/ + + 10 + + INFO + + 30 + + 60 + + 10 + + 60 + + auto + + 0,0 + + off + + 1 + + 20 + + 24 + + diff --git a/packaging/iofog-agent/etc/iofog-agent/config-production_new.xml b/packaging/iofog-agent/etc/iofog-agent/config-production_new.xml new file mode 100644 index 00000000..8d8db7d4 --- /dev/null +++ b/packaging/iofog-agent/etc/iofog-agent/config-production_new.xml @@ -0,0 +1,71 @@ + + + + + + + + + http://localhost:54421/api/v3/ + + + + on + off + + /etc/iofog-agent/cert.crt + + auto + + dynamic + + unix:///var/run/docker.sock + + 10 + + /var/lib/iofog-agent/ + + 4096 + + 80.0 + + 10.0 + + /var/log/iofog-agent/ + + 10 + + INFO + + 30 + + 60 + + 10 + + 60 + + auto + + 0,0 + + off + + 1 + + 20 + + 24 + + diff --git a/packaging/iofog-agent/etc/iofog-agent/config-switcher_new.xml b/packaging/iofog-agent/etc/iofog-agent/config-switcher_new.xml new file mode 100644 index 00000000..aca11403 --- /dev/null +++ b/packaging/iofog-agent/etc/iofog-agent/config-switcher_new.xml @@ -0,0 +1,17 @@ + + + + + default + \ No newline at end of file diff --git a/packaging/iofog-agent/etc/iofog-agent/config_new.xml b/packaging/iofog-agent/etc/iofog-agent/config_new.xml new file mode 100644 index 00000000..4b103601 --- /dev/null +++ b/packaging/iofog-agent/etc/iofog-agent/config_new.xml @@ -0,0 +1,71 @@ + + + + + + + + + http://localhost:54421/api/v3/ + + + + off + off + + /etc/iofog-agent/cert.crt + + auto + + dynamic + + unix:///var/run/docker.sock + + 10 + + /var/lib/iofog-agent/ + + 4096 + + 80.0 + + 10.0 + + /var/log/iofog-agent/ + + 10 + + INFO + + 30 + + 60 + + 10 + + 60 + + auto + + 0,0 + + off + + 1 + + 20 + + 24 + + diff --git a/packaging/iofog-agent/remove.sh b/packaging/iofog-agent/remove.sh new file mode 100644 index 00000000..880948c9 --- /dev/null +++ b/packaging/iofog-agent/remove.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +rm -rf /etc/iofog-agent +rm -rf /var/log/iofog-agent +rm -rf /var/lib/iofog-agent +rm -rf /var/run/iofog-agent +rm -rf /var/log/iofog-microservices +rm -rf /usr/share/iofog-agent + +containers=$(docker ps | grep iofog_ | awk --posix '{print $1}') +if [ "$containers" != "" ]; then +docker stop $containers +fi diff --git a/packaging/iofog-agent/rpm.sh b/packaging/iofog-agent/rpm.sh new file mode 100644 index 00000000..0046e29c --- /dev/null +++ b/packaging/iofog-agent/rpm.sh @@ -0,0 +1,108 @@ +#!/bin/bash + +# killing old running processes +for KILLPID in `ps ax | grep 'iofog-agentd' | awk ' { print $1;}'`; do + kill -9 $KILLPID; +done + +#echo "Starting post-install process..." +echo 'iofog-agent ALL=(ALL:ALL) ALL' >> /etc/sudoers +#useradd -r -U -s /usr/bin/nologin iofog-agent +#usermod -aG admin,sudo iofog-agent +groupadd -r iofog-agent +useradd -r -g iofog-agent iofog-agent +#echo "Added iofog-agent user and group" + +if [ -f /etc/iofog-agent/config.xml ]; +then + rm /etc/iofog-agent/config_new.xml +else + mv /etc/iofog-agent/config_new.xml /etc/iofog-agent/config.xml +fi +#echo "Check for config.xml" + +if [ -f /etc/iofog-agent/config-development.xml ]; +then + rm /etc/iofog-agent/config-development_new.xml +else + mv /etc/iofog-agent/config-development_new.xml /etc/iofog-agent/config-development.xml +fi +#echo "Check for config-development.xml" + +if [ -f /etc/iofog-agent/config-production.xml ]; +then + rm /etc/iofog-agent/config-production_new.xml +else + mv /etc/iofog-agent/config-production_new.xml /etc/iofog-agent/config-production.xml +fi +#echo "Check for config-production.xml" + +if [ -f /etc/iofog-agent/config-switcher.xml ]; +then + rm /etc/iofog-agent/config-switcher_new.xml +else + mv /etc/iofog-agent/config-switcher_new.xml /etc/iofog-agent/config-switcher.xml +fi +#echo "Check for config-switcher.xml" + +if [ -f /etc/iofog-agent/cert.crt ]; +then + rm /etc/iofog-agent/cert_new.crt +else + mv /etc/iofog-agent/cert_new.crt /etc/iofog-agent/cert.crt +fi +#echo "Check for config.xml" + +if [ -f /etc/iofog-agent/config-bck.xml ]; +then + rm /etc/iofog-agent/config-bck_new.xml +else + mv /etc/iofog-agent/config-bck_new.xml /etc/iofog-agent/config-bck.xml +fi +#echo "Check for config-bck.xml" + + /etc/iofog-agent/local-api + +mkdir -p /var/backups/iofog-agent +mkdir -p /var/log/iofog-agent +mkdir -p /var/lib/iofog-agent +mkdir -p /var/run/iofog-agent +mkdir -p /var/log/iofog-microservices + +chown -R :iofog-agent /etc/iofog-agent +chown -R :iofog-agent /var/log/iofog-agent +chown -R :iofog-agent /var/lib/iofog-agent +chown -R :iofog-agent /var/run/iofog-agent +chown -R :iofog-agent /var/backups/iofog-agent +chown -R :iofog-agent /usr/share/iofog-agent +#echo "Changed ownership of directories to iofog-agent group" + +chmod 774 -R /etc/iofog-agent +chmod 774 -R /var/log/iofog-agent +chmod 774 -R /var/lib/iofog-agent +chmod 774 -R /var/run/iofog-agent +chmod 774 -R /var/backups/iofog-agent +chmod 754 -R /usr/share/iofog-agent +#echo "Changed permissions of directories" + +mv /dev/random /dev/random.real +ln -s /dev/urandom /dev/random +#echo "Moved dev pipes for netty" + +chmod 774 /etc/init.d/iofog-agent +#echo "Changed permissions on service script" + +chmod 754 /usr/bin/iofog-agent +#echo "Changed permissions on command line executable file" + +chown :iofog-agent /usr/bin/iofog-agent +#echo "Changed ownership of command line executable file" + +chkconfig --add iofog-agent +chkconfig iofog-agent on +#echo "Registered init.d script for iofog-agent service" + +ln -sf /usr/bin/iofog-agent /usr/local/bin/iofog-agent +#echo "Added symlink to iofog-agent command executable" + +#echo "...post-install processing completed" diff --git a/packaging/iofog-agent/upgrade.sh b/packaging/iofog-agent/upgrade.sh new file mode 100644 index 00000000..22449095 --- /dev/null +++ b/packaging/iofog-agent/upgrade.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +echo "Upgrading ioFog-Agent package ..." diff --git a/packaging/iofog-agent/usr/bin/iofog-agent b/packaging/iofog-agent/usr/bin/iofog-agent new file mode 100755 index 00000000..1d7f79d9 --- /dev/null +++ b/packaging/iofog-agent/usr/bin/iofog-agent @@ -0,0 +1,4 @@ +#!/bin/sh + +JAR_FILE_NAME=/usr/bin/iofog-agent.jar +java -jar $JAR_FILE_NAME $@ diff --git a/packaging/iofog-agent/usr/share/iofog-agent/rollback.sh b/packaging/iofog-agent/usr/share/iofog-agent/rollback.sh new file mode 100644 index 00000000..987e3beb --- /dev/null +++ b/packaging/iofog-agent/usr/share/iofog-agent/rollback.sh @@ -0,0 +1,64 @@ +#!/bin/bash + +get_distribution() { + lsb_dist="" + # Every system that we officially support has /etc/os-release + if [ -r /etc/os-release ]; then + lsb_dist="$(. /etc/os-release && echo "$ID")" + fi + # Returning an empty string here should be alright since the + # case statements don't act unless you provide an actual value + echo "$lsb_dist" +} + +{ + timeout=${2:-60} + + cd /var/backups/iofog-agent + + # Stop agent + service iofog-agent stop + + # Perform rollback + lsb_dist=$( get_distribution ) + lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" + iofogpackage=$(grep ver prev_version_data | awk '{print $3}') + iofogversion=$(grep ver prev_version_data | awk '{print $2}') + case "$lsb_dist" in + + ubuntu|debian|raspbian) + apt-get purge --auto-remove iofog-agent$iofogpackage -y + apt-get install iofog-agent$iofogpackage=$iofogversion -y + ;; + + centos|rhel|ol|sles) + yum remove iofog-agent$iofogpackage -y + yum install iofog-agent$iofogpackage-$iofogversion -y + ;; + + esac + + # Save logs + tar -cvzf log_backup_rollback$iofogversion.tar.gz -P /var/log/iofog-agent + # Overwrite config based on previous data + rm -rf /etc/iofog-agent/ + tar -xvzf config_backup$iofogpackage.tar.gz -P -C / + rm -rf /var/backups/iofog-agent/prev_version_data + rm -rf /var/backups/iofog-agent/config_backup$iofogpackage.tar.gz + + # Start agent + starttimestamp=$(date +%s) + service iofog-agent start + sleep 1 + + # Wait for agent + while [ "$(iofog-agent status | grep ioFog | awk '{printf $4 }')" != "RUNNING" ]; do + sleep 1 + currenttimestamp=$(date +%s) + currentdeltatime=$(( $currenttimestamp - $starttimestamp )) + if [ $currentdeltatime -gt $timeout ]; then + break + fi + done + +} > /var/log/iofog-agent-rollback.log 2>&1 \ No newline at end of file diff --git a/packaging/iofog-agent/usr/share/iofog-agent/upgrade.sh b/packaging/iofog-agent/usr/share/iofog-agent/upgrade.sh new file mode 100644 index 00000000..2064aafe --- /dev/null +++ b/packaging/iofog-agent/usr/share/iofog-agent/upgrade.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +get_distribution() { + lsb_dist="" + # Every system that we officially support has /etc/os-release + if [ -r /etc/os-release ]; then + lsb_dist="$(. /etc/os-release && echo "$ID")" + fi + # Returning an empty string here should be alright since the + # case statements don't act unless you provide an actual value + echo "$lsb_dist" +} + +{ + timeout=${2:-60} + + # Find current version + iofogpackage=$(apt-cache policy iofog-agent iofog-agent-dev | grep -A1 ^iofog | awk '$2 ~ /^[0-9]/ {print a}{a=$0}' | sed -e 's/iofog-agent\(.*\):/\1/') + iofogversion=$(apt-cache policy iofog-agent$iofogpackage | grep Installed | awk '{ if ($2 ~ /^[0-9]/) print $2}') + + # Copy config + ORIGINAL="/etc/iofog-agent/config.xml" + BACKUP="/var/backups/iofog-agent/config.xml" + cp "$ORIGINAL" "$BACKUP" + + # Stop agent + service iofog-agent stop + + # Create backup for rollback + cd /var/backups/iofog-agent + tar -cvzf config_backup$iofogpackage.tar.gz -P /etc/iofog-agent + tar -cvzf log_backup_upgrade$iofogversion.tar.gz -P /var/log/iofog-agent + printf 'ver: %s %s' $iofogversion $iofogpackage > prev_version_data + + # remove current configs + rm /etc/iofog-agent/* + + # Perform upgrade + lsb_dist=$( get_distribution ) + lsb_dist="$(echo "$lsb_dist" | tr '[:upper:]' '[:lower:]')" + case "$lsb_dist" in + + ubuntu|debian|raspbian) + apt-get update + apt-get install --only-upgrade iofog-agent$iofogpackage -y + ;; + + centos|rhel|ol|sles) + yum check-update + yum update iofog-agent$iofogpackage -y + ;; + + esac + + # Restore config and start agent + cd /var/backups/iofog-agent + tar -xzf config_backup$iofogpackage.tar.gz + mv etc/iofog-agent/* /etc/iofog-agent/ + echo 'config restored' + + cp "$BACKUP" "$ORIGINAL" + starttimestamp=$(date +%s) + service iofog-agent start + sleep 1 + + # Wait for agent + while [ "$(iofog-agent status | grep ioFog | awk '{printf $4 }')" != "RUNNING" ]; do + sleep 1 + currenttimestamp=$(date +%s) + currentdeltatime=$(( $currenttimestamp - $starttimestamp )) + if [ $currentdeltatime -gt $timeout ]; then + break + fi + done + +} > /var/log/iofog-agent-upgrade.log 2>&1 \ No newline at end of file diff --git a/rest-api.yaml b/rest-api.yaml new file mode 100644 index 00000000..680dd2d9 --- /dev/null +++ b/rest-api.yaml @@ -0,0 +1,270 @@ +swagger: '2.0' +info: + version: 2.0.0 + title: iofog-agent +consumes: + - application/json +produces: + - application/json +paths: + '/status': + get: + tags: + - Agent + description: Returns service status + parameters: + - in: header + name: Authorization + description: Agent Token + required: true + type: string + responses: + '200': + description: Service status + schema: + $ref: '#/definitions/ServiceStatusResponse' + '401': + description: Not Authorized + '405': + description: Method Not Allowed + '/info': + get: + tags: + - Agent + description: Returns service info + parameters: + - in: header + name: Authorization + description: Agent Token + required: true + type: string + responses: + '200': + description: Service info + schema: + $ref: '#/definitions/ServiceInfoResponse' + '401': + description: Not Authorized + '405': + description: Method Not Allowed + '/version': + get: + tags: + - Agent + description: Returns service info + parameters: + - in: header + name: Authorization + description: Agent Token + required: true + type: string + responses: + '200': + description: Service info + schema: + $ref: '#/definitions/ServiceVersionResponse' + '401': + description: Not Authorized + '405': + description: Method Not Allowed + '/provision': + post: + tags: + - Agent + description: Provision Agent + parameters: + - in: header + name: Authorization + description: User token + required: true + type: string + - in: body + name: ProvisioningKey + required: false + schema: + $ref: '#/definitions/ProvisioningRequest' + responses: + '200': + description: Provisionined + '400': + description: Bad Request + '401': + description: Not Authorized + '500': + description: Internal Server Error + '/deprovision': + delete: + tags: + - Agent + description: Deprovision Agent + parameters: + - in: header + name: Authorization + description: User token + required: true + type: string + responses: + '200': + description: Deprovisioned + '400': + description: Bad Request + '401': + description: Not Authorized + '500': + description: Internal Server Error + '/config': + post: + tags: + - Agent + description: Provision Agent + parameters: + - in: header + name: Authorization + description: User token + required: true + type: string + - in: body + name: ConfigRequest + required: false + schema: + $ref: '#/definitions/ConfigRequest' + responses: + '200': + description: Provisionined + '400': + description: Bad Request + '401': + description: Not Authorized + '500': + description: Internal Server Error + +definitions: + ServiceStatusResponse: + type: object + properties: + running-microservices: + type: string + system-total-cpu: + type: string + memory-usage: + type: string + system-available-memory: + type: string + system-time: + type: string + disk-usage: + type: string + connection-to-controller: + type: string + cpu-usage: + type: string + messages-processed: + type: string + system-available-disk: + type: string + iofog-daemon: + type: string + ServiceInfoResponse: + type: object + properties: + log-file-directory: + type: string + iofog-controller: + type: string + cpu-usage-limit: + type: string + developer-mode: + type: string + post-diagnostics-frequency: + type: string + docker-url: + type: string + status-update-frequency: + type: string + memory-ram-limit: + type: string + log-disk-limit: + type: string + isolated-docker-containers-mode: + type: string + iofog-certificate: + type: string + ip-address: + type: string + network-interface: + type: string + fog-type: + type: string + disk-usage-limit: + type: string + iofog-uuid: + type: string + gps-mode: + type: string + message-storage-directory: + type: string + get-changes-frequency: + type: string + gps-coordinates: + type: string + log-rolling-file-count: + type: string + scan-devices-frequency: + type: string + ServiceVersionResponse: + type: object + properties: + version: + type: string + ProvisioningRequest: + type: object + properties: + provisioning-key: + type: string + ConfigRequest: + type: object + properties: + disk-limit: + type: string + disk-directory: + type: string + memory-limit: + type: string + cpu-limit: + type: string + controller-url: + type: string + cert-directory: + type: string + docker-url: + type: string + network-adapter: + type: string + logs-limit: + type: string + logs-directory: + type: string + logs-count: + type: string + logs-level: + type: string + status-frequency: + type: string + changes-frequency: + type: string + diagnostics-frequency: + type: string + device-scan-frequency: + type: string + isolated: + type: string + gps: + type: string + fog-type: + type: string + developer-mode: + type: string +schemes: + - http +host: 'localhost:54321' +basePath: /v2 diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100644 index 00000000..fc5d8838 --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# import helper funtion +. scripts/utils.sh + +# Is jq installed? +if ! checkForInstallation "jq"; then + echoInfo " Attempting to install 'jq'" + if [ "$(uname -s)" = "Darwin" ]; then + brew install jq + else + sudo apt install jq + fi +fi + +# Is bats installed? +if ! checkForInstallation "bats"; then + echoInfo " Attempting to install 'bats'" + git clone https://github.com/bats-core/bats-core.git && cd bats-core && git checkout tags/v1.1.0 && sudo ./install.sh /usr/local +fi + +# Is iofogctl installed? +if ! checkForInstallation "iofogctl"; then + echoInfo " Attempting to iofogctl" + if [ "$(uname -s)" = "Darwin" ]; then + brew install iofogctl + else + curl https://packagecloud.io/install/repositories/iofog/iofogctl/script.deb.sh | sudo bash + sudo apt-get install iofogctl + fi +fi + diff --git a/scripts/utils.sh b/scripts/utils.sh new file mode 100644 index 00000000..a1a990e6 --- /dev/null +++ b/scripts/utils.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# +# Check for Installation will check to see whether a particular command exists in +# the $PATH of the current shell. Optionally, you can check for a specific version. +# +# Usage: checkForInstallation protoc "libprotoc 3.6.1" +# +checkForInstallation() { + + # Does the command exist? + if [[ ! "$(command -v "$1")" ]]; then + echoError " [!] $1 not found" + return 1 + else + # Are we looking for a specific version? + if [[ ! -z "$2" ]]; then + if [[ "$2" != "$($1 --version)" ]]; then + echoError " !! $1 is the wrong version. Found $($1 --version) but expected $2" + return 1 + fi + fi + echoSuccess " [x] $1 $2 found at $(command -v "$1")" + return 0 + fi +} + +# Basic subtle output +echoInfo() { + echo ${PRINTARGS} "${C_SKYBLUE1}$1 ${NO_FORMAT}" +} + +# Highlighted output with a background +echoNotify() { + echo ${PRINTARGS} "${C_DEEPSKYBLUE4}${1} ${NO_FORMAT}" +} + +# Hurrah! +echoSuccess() { + echo ${PRINTARGS} "${GREEN}$1 ${NO_FORMAT}" +} + +# Houston, we have a problem! +echoError() { + echo ${PRINTARGS} "${RED}$1 ${NO_FORMAT}" +} \ No newline at end of file diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 00000000..fe33a30e --- /dev/null +++ b/settings.gradle @@ -0,0 +1,4 @@ +rootProject.name = 'iofog-agent' +include(':iofog-agent-daemon') +include(':iofog-agent-client') +include(':iofog-version-controller') diff --git a/test/deploy_ecn.bash b/test/deploy_ecn.bash new file mode 100644 index 00000000..fd8f6441 --- /dev/null +++ b/test/deploy_ecn.bash @@ -0,0 +1,59 @@ +#!/usr/bin/env bash + +set -e + +# Export variables +CONF=test/resources/env.sh +if [ -f "$CONF" ]; then + echo "$CONF" + . "$CONF" +fi + +function createControlPlaneFile() { + echo "--- +apiVersion: iofog.org/v2 +kind: LocalControlPlane +metadata: + name: ecn +spec: + iofogUser: + name: integration + surname: test + email: user@domain.com + password: q1u45ic9kst563art + controller: + container: + image: ${CONTROLLER_IMAGE} +--- +apiVersion: iofog.org/v2 +kind: LocalAgent +metadata: + name: local-agent +spec: + container: + image: ${AGENT_IMAGE}"> /tmp/local_controlplane.yml +} + +function deployControlPlane() { + createControlPlaneFile; + cat /tmp/local_controlplane.yml + iofogctl create namespace "${NAMESPACE}" + iofogctl deploy -f /tmp/local_controlplane.yml -n "${NAMESPACE}" + iofogctl get all -n "${NAMESPACE}" +} + +function deleteECN() { + iofogctl delete all -n "${NAMESPACE}" + iofogctl disconnect -n "${NAMESPACE}" +} + +function createAgentPackage() { + docker build -t gcr.io/focal-freedom-236620/agent:latest . +} + +function deployECN() { + createAgentPackage; + deployControlPlane; +} + +"$@" \ No newline at end of file diff --git a/test/int_test.bats b/test/int_test.bats new file mode 100644 index 00000000..3356cec1 --- /dev/null +++ b/test/int_test.bats @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +@test "test disk threshold value to be equal to default value" { + disk_threshold_conf=$(awk -F "[><]" '/available_disk_threshold/{print $3}' packaging/iofog-agent/etc/iofog-agent/config_new.xml) + deployed_agent_dt_value=$(sudo docker exec iofog-agent iofog-agent info | grep "Available Disk Threshold" | awk '{print $5}') + [[ $(disk_threshold_conf)=$(deployed_agent_dt_value) ]] +} + +@test "test java cpu usage" { + cpu_usage=$(ps -C java -o %cpu | tail -1) + [[ "$USAGE" > $(cpu_usage) ]] + # TODO sleep 2m + sleep 2m + echo "2 minutes complete" + cpu_usage=$(ps -C java -o %cpu | tail -1) + [[ "$USAGE" > $(cpu_usage) ]] +} diff --git a/test/resources/env.sh b/test/resources/env.sh new file mode 100644 index 00000000..ed592ad2 --- /dev/null +++ b/test/resources/env.sh @@ -0,0 +1,4 @@ +export USAGE="5" +export AGENT_IMAGE="gcr.io/focal-freedom-236620/agent:latest" +export CONTROLLER_IMAGE="iofog/controller:latest" +export NAMESPACE="int-test" \ No newline at end of file diff --git a/test/run.bash b/test/run.bash new file mode 100644 index 00000000..21aae163 --- /dev/null +++ b/test/run.bash @@ -0,0 +1,14 @@ +#!/bin/bash + +set -e + +# Export variables +CONF=test/resources/env.sh +if [ -f "$CONF" ]; then + echo "$CONF" + . "$CONF" +fi + +echo "$USAGE" + +bats test/int_test.bats \ No newline at end of file From b41702f327a651993709e729b3cd21722b80bae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= <51929176+emirhandurmus@users.noreply.github.com> Date: Sat, 12 Aug 2023 00:25:36 +0300 Subject: [PATCH 003/114] Create ci.yml --- .github/workflows/ci.yml | 225 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 225 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..558c1eb7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,225 @@ +name: CI +on: + push: + branches: + - main + tags: [v*] + paths-ignore: + - README.md + - CHANGELOG.md + - LICENSE + pull_request: + # Sequence of patterns matched against refs/heads + branches: + - main + paths-ignore: + - README.md + - CHANGELOG.md + - LICENSE +env: + PROJECT: 'datasance-pot' + IMAGE_NAME: 'agent' + IOFOGCTL_VERSION: '3.0.8' + CONTROLLER_IMAGE: 'iofog/controller:latest' + +jobs: + build: + runs-on: ubuntu-20.04 + permissions: + contents: 'read' + id-token: 'write' + packages: 'write' + name: Build + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + java-version: '8' + distribution: 'temurin' +# cache: 'gradle' + - uses: gradle/gradle-build-action@v2 + with: + arguments: build + + Integration: + runs-on: ubuntu-20.04 + name: Integration + needs: Build + permissions: + contents: 'read' + id-token: 'write' + packages: 'write' + steps: + - uses: actions/checkout@v3 + - run: sudo apt-get install jq + - run: | + curl https://packagecloud.io/install/repositories/iofog/iofogctl/script.deb.sh | sudo bash + sudo apt-get install iofogctl=${{ env.IOFOGCTL_VERSION }} + - name: 'Install bats' + run: | + sudo apt-get update -y + sudo apt-get install -y bats + - name: 'Deploy local ecn' + shell: bash + run: | + sed -i "s|CONTROLLER_IMAGE=.*|CONTROLLER_IMAGE=\"${{ env.CONTROLLER_IMAGE }}\"|g" test/resources/env.sh + sudo bash test/deploy_ecn.bash deployControlPlane + - name: 'Run integration test' + run: sudo bash test/run.bash + - name: 'Delete ECN' + if: always() + run: sudo bash test/deploy_ecn.bash deleteECN + + Publish: + runs-on: ubuntu-20.04 + needs: [Build, Integration] + permissions: + contents: 'read' + id-token: 'write' + packages: 'write' + name: Publish + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: 'Get Previous tag' + id: previoustag + uses: "WyriHaximus/github-action-get-previous-tag@v1" + with: + fallback: 0.0.0 + - name: Set image tag + shell: bash + id: tags + run: | + if [[ ${{ github.ref_name }} =~ ^v.* ]] ; then + VERSION=${{ github.ref_name }} + echo "VERSION=${VERSION:1}" >> "${GITHUB_OUTPUT}" + else + VERSION=${{ steps.previoustag.outputs.tag }} + echo "VERSION=${VERSION:1}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" + fi + - name: Build and Push to GCR + if: (github.ref == 'refs/heads/develop') || contains(github.ref_name, '^v.*') + id: build_push_gcr + uses: RafikFarhad/push-to-gcr-github-action@v5-beta + with: + gcloud_service_key: ${{ secrets.GCLOUD_SERVICE_KEY }} + registry: gcr.io + project_id: ${{ env.PROJECT }} + image_name: ${{ env.IMAGE_NAME }} + image_tag: latest, develop, ${{ steps.tags.outputs.VERSION }} + dockerfile: Dockerfile + + - name: Login to Github Container Registry + if: (github.ref == 'refs/heads/develop') || contains(github.ref_name, '^v.*') + uses: docker/login-action@v2 + with: + registry: "ghcr.io" + username: ${{ github.actor }} + password: ${{ github.token }} + + - name: Build and Push to ghcr + if: (github.ref == 'refs/heads/develop') || contains(github.ref_name, '^v.*') + uses: docker/build-push-action@v3 + id: build_push_ghcr + with: + file: Dockerfile + push: true + tags: | + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.VERSION }} + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:latest + ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:develop + + - name: Set up Ruby 3.1.4 + uses: actions/setup-ruby@v1 + with: + ruby-version: 3.1.4 + - run: | + gem install --no-document fpm + fpm -h + - name: Install package_Cloud + run: | + gem install package_cloud + package_cloud -h + - name: get gradle version + shell: bash + id: version + run: echo "version=$(./gradlew properties --no-daemon --console=plain -q | grep "^version:" | awk '{printf $2}')" >> "${GITHUB_OUTPUT}" + - name: get package version + shell: bash + id: pkg_version + run: | + if [[ ${{ github.ref_name }} =~ ^v.* ]] ; then + echo "version=${{ steps.version.outputs.version }}" >> "${GITHUB_OUTPUT}" + else + echo "version=${{ steps.version.outputs.version }}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" + fi + - run: echo ${{ steps.version.outputs.version }} + - name: Create deb package + shell: bash + id: create_deb_package + run: | + cd packaging/iofog-agent + fpm -s dir -d 'openjdk-8-jdk | openjdk-11-jdk' -d docker -t deb -n iofog-agent -v ${{ steps.pkg_version.outputs.version }} -a all --deb-no-default-config-files --after-install debian.sh --after-remove remove.sh --before-upgrade upgrade.sh --after-upgrade debian.sh etc usr + echo "pkg created" + ls + - name: Create rpm package + shell: bash + id: create_rpm_package + run: | + cd packaging/iofog-agent + fpm -s dir --depends java-11-openjdk -d docker-ce -t rpm -n iofog-agent -v ${{ steps.pkg_version.outputs.version }} -a all --rpm-os 'linux' --after-install rpm.sh --after-remove remove.sh --before-upgrade upgrade.sh --after-upgrade rpm.sh etc usr; + echo "pkg created" + ls + - uses: actions/upload-artifact@v2 + name: Upload deb package + with: + name: deb-package + path: packaging/iofog-agent/iofog-agent_${{ steps.pkg_version.outputs.version }}_all.deb + + - uses: bluwy/substitute-string-action@v1 + id: sub + with: + _input-text: ${{ steps.pkg_version.outputs.version }} + '-': _ + - run: echo ${{ steps.sub.outputs.result }} + + - uses: actions/upload-artifact@v2 + name: Upload rpm package + with: + name: rpm-package + path: packaging/iofog-agent/iofog-agent-${{ steps.sub.outputs.result }}-1.noarch.rpm + + - uses: actions/download-artifact@v2 + with: + name: deb-package + + - name: Publish deb package to packagecloud + uses: danielmundi/upload-packagecloud@v1 + with: + PACKAGE-NAME: iofog-agent_${{ steps.pkg_version.outputs.version }}_all.deb + PACKAGECLOUD-USERNAME: iofog + PACKAGECLOUD-REPO: iofog-agent-dev + PACKAGECLOUD-DISTRIB: any/any + PACKAGECLOUD-TOKEN: ${{ secrets.packagecloud_token }} + + - uses: actions/download-artifact@v2 + with: + name: rpm-package + + - name: Publish rpm package to packagecloud + uses: danielmundi/upload-packagecloud@v1 + with: + PACKAGE-NAME: packaging/iofog-agent/iofog-agent-${{ steps.sub.outputs.result }}-1.noarch.rpm + PACKAGECLOUD-USERNAME: iofog + PACKAGECLOUD-REPO: iofog-agent-dev + PACKAGECLOUD-DISTRIB: rpm_any/rpm_any + PACKAGECLOUD-TOKEN: ${{ secrets.packagecloud_token }} + + - name: Upload Agent Artifact + uses: actions/upload-artifact@v3 + with: + name: agent + path: packaging/**/* + + From 331f20ca86694d693ce7b5d3d58c3bf48361f655 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emirhan=20Durmu=C5=9F?= Date: Sat, 12 Aug 2023 01:25:26 +0300 Subject: [PATCH 004/114] workflow --- .github/workflows/ci.yml | 37 +++++++------------ Agent | 1 + docs/ioFog-Command-Line-Specification.md | 2 +- .../main/java/org/eclipse/iofog/Client.java | 4 +- .../main/java/org/eclipse/iofog/Daemon.java | 2 +- .../java/org/eclipse/iofog/IOFogModule.java | 2 +- .../iofog/command_line/CommandLineAction.java | 2 +- .../command_line/CommandLineConfigParam.java | 2 +- .../iofog/command_line/CommandLineParser.java | 2 +- .../util/CommandShellExecutor.java | 2 +- .../util/CommandShellResultSet.java | 2 +- .../diagnostics/ImageDownloadManager.java | 2 +- .../strace/MicroserviceStraceData.java | 2 +- .../strace/StraceDiagnosticManager.java | 2 +- .../eclipse/iofog/edge_resources/Display.java | 2 +- .../iofog/edge_resources/EdgeEndpoints.java | 2 +- .../iofog/edge_resources/EdgeInterface.java | 2 +- .../iofog/edge_resources/EdgeResource.java | 2 +- .../edge_resources/EdgeResourceManager.java | 2 +- .../iofog/exception/AgentException.java | 2 +- .../iofog/exception/AgentSystemException.java | 2 +- .../iofog/exception/AgentUserException.java | 2 +- .../eclipse/iofog/field_agent/FieldAgent.java | 2 +- .../iofog/field_agent/FieldAgentStatus.java | 2 +- .../iofog/field_agent/VersionHandler.java | 2 +- .../field_agent/enums/VersionCommand.java | 2 +- .../UnknownVersionCommandException.java | 2 +- .../java/org/eclipse/iofog/gps/GpsMode.java | 2 +- .../org/eclipse/iofog/gps/GpsWebHandler.java | 2 +- .../iofog/local_api/BluetoothApiHandler.java | 2 +- .../local_api/CommandLineApiHandler.java | 2 +- .../iofog/local_api/ConfigApiHandler.java | 2 +- .../iofog/local_api/ConfigurationMap.java | 2 +- .../local_api/ControlSignalSentInfo.java | 2 +- .../local_api/ControlWebsocketHandler.java | 2 +- .../local_api/ControlWebsocketWorker.java | 2 +- .../local_api/DeprovisionApiHandler.java | 2 +- .../iofog/local_api/EdgeResourceHandler.java | 2 +- .../local_api/GetConfigurationHandler.java | 2 +- .../iofog/local_api/GpsApiHandler.java | 2 +- .../iofog/local_api/InfoApiHandler.java | 2 +- .../org/eclipse/iofog/local_api/LocalApi.java | 2 +- .../iofog/local_api/LocalApiServer.java | 2 +- .../local_api/LocalApiServerHandler.java | 2 +- .../LocalApiServerPipelineFactory.java | 2 +- .../iofog/local_api/LocalApiStatus.java | 2 +- .../iofog/local_api/LogApiHandler.java | 2 +- .../iofog/local_api/MessageCallback.java | 2 +- .../local_api/MessageReceiverHandler.java | 2 +- .../iofog/local_api/MessageSenderHandler.java | 2 +- .../iofog/local_api/MessageSentInfo.java | 2 +- .../local_api/MessageWebsocketHandler.java | 2 +- .../local_api/MessageWebsocketWorker.java | 2 +- .../iofog/local_api/ProvisionApiHandler.java | 2 +- .../QueryMessageReceiverHandler.java | 2 +- .../iofog/local_api/StatusApiHandler.java | 2 +- .../iofog/local_api/VersionApiHandler.java | 2 +- .../eclipse/iofog/local_api/WebSocketMap.java | 2 +- .../iofog/local_api/WebsocketUtil.java | 2 +- ...MessageReceiverWebSocketClientHandler.java | 2 +- .../MessageSenderWebSocketClientHandler.java | 2 +- .../local_api/test/MessageSocketTestMain.java | 2 +- .../test/MessageWebsocketReceiverClient.java | 2 +- .../test/MessageWebsocketSenderClient.java | 2 +- .../iofog/local_api/test/RestApiDriver.java | 2 +- .../iofog/local_api/test/RestPublishTest.java | 2 +- .../iofog/local_api/test/RestReceiveTest.java | 2 +- .../test/WebSocketClientControl.java | 2 +- .../test/WebSocketClientHandlerControl.java | 2 +- .../iofog/message_bus/IOMessageListener.java | 2 +- .../eclipse/iofog/message_bus/Message.java | 2 +- .../iofog/message_bus/MessageArchive.java | 2 +- .../eclipse/iofog/message_bus/MessageBus.java | 2 +- .../iofog/message_bus/MessageBusServer.java | 2 +- .../iofog/message_bus/MessageBusStatus.java | 2 +- .../iofog/message_bus/MessageBusUtil.java | 2 +- .../iofog/message_bus/MessageIdGenerator.java | 2 +- .../iofog/message_bus/MessagePublisher.java | 2 +- .../iofog/message_bus/MessageReceiver.java | 2 +- .../eclipse/iofog/microservice/EnvVar.java | 2 +- .../iofog/microservice/Microservice.java | 2 +- .../microservice/MicroserviceManager.java | 2 +- .../iofog/microservice/MicroserviceState.java | 2 +- .../microservice/MicroserviceStatus.java | 2 +- .../iofog/microservice/PortMapping.java | 2 +- .../eclipse/iofog/microservice/Registry.java | 2 +- .../org/eclipse/iofog/microservice/Route.java | 2 +- .../iofog/microservice/VolumeMapping.java | 2 +- .../iofog/network/IOFogNetworkInterface.java | 2 +- .../process_manager/ContainerManager.java | 2 +- .../iofog/process_manager/ContainerTask.java | 2 +- .../iofog/process_manager/DockerUtil.java | 2 +- .../iofog/process_manager/ProcessManager.java | 2 +- .../process_manager/ProcessManagerStatus.java | 2 +- .../process_manager/RestartStuckChecker.java | 2 +- .../iofog/process_manager/StatsCallback.java | 2 +- .../iofog/process_manager/TaskComparator.java | 2 +- .../iofog/process_manager/TaskManager.java | 2 +- .../eclipse/iofog/proxy/SshConnection.java | 2 +- .../iofog/proxy/SshConnectionStatus.java | 2 +- .../eclipse/iofog/proxy/SshProxyManager.java | 2 +- .../iofog/proxy/SshProxyManagerStatus.java | 2 +- .../iofog/pruning/DockerPruningManager.java | 2 +- .../ResourceConsumptionManager.java | 2 +- .../ResourceConsumptionManagerStatus.java | 2 +- .../resource_manager/ResourceManager.java | 2 +- .../ResourceManagerStatus.java | 2 +- .../iofog/status_reporter/StatusReporter.java | 2 +- .../status_reporter/StatusReporterStatus.java | 2 +- .../eclipse/iofog/supervisor/Supervisor.java | 2 +- .../iofog/supervisor/SupervisorStatus.java | 2 +- .../org/eclipse/iofog/utils/BytesUtil.java | 2 +- .../eclipse/iofog/utils/CmdProperties.java | 2 +- .../org/eclipse/iofog/utils/Constants.java | 2 +- .../org/eclipse/iofog/utils/Orchestrator.java | 2 +- .../utils/configuration/Configuration.java | 2 +- .../ConfigurationItemException.java | 2 +- .../utils/device_info/ArchitectureType.java | 2 +- .../iofog/utils/functional/Either.java | 2 +- .../iofog/utils/functional/Function3.java | 2 +- .../iofog/utils/functional/Function4.java | 2 +- .../iofog/utils/functional/Functions.java | 2 +- .../eclipse/iofog/utils/functional/Left.java | 2 +- .../eclipse/iofog/utils/functional/Pair.java | 2 +- .../eclipse/iofog/utils/functional/Right.java | 2 +- .../eclipse/iofog/utils/functional/Unit.java | 2 +- .../iofog/utils/logging/LogFormatter.java | 2 +- .../iofog/utils/logging/LoggingService.java | 2 +- .../main/resources/cmd_messages.properties | 2 +- .../command_line/CommandLineActionTest.java | 4 +- .../CommandLineConfigParamTest.java | 2 +- .../command_line/CommandLineParserTest.java | 2 +- .../util/CommandShellExecutorTest.java | 2 +- .../util/CommandShellResultSetTest.java | 2 +- .../diagnostics/ImageDownloadManagerTest.java | 2 +- .../strace/MicroserviceStraceDataTest.java | 2 +- .../strace/StraceDiagnosticManagerTest.java | 2 +- .../field_agent/FieldAgentStatusTest.java | 2 +- .../iofog/field_agent/FieldAgentTest.java | 2 +- .../iofog/field_agent/VersionHandlerTest.java | 2 +- .../field_agent/enums/VersionCommandTest.java | 2 +- .../local_api/ApiHandlerHelpersTest.java | 2 +- .../local_api/BluetoothApiHandlerTest.java | 2 +- .../local_api/CommandLineApiHandlerTest.java | 2 +- .../iofog/local_api/ConfigApiHandlerTest.java | 2 +- .../local_api/ControlSignalSentInfoTest.java | 2 +- .../ControlWebsocketHandlerTest.java | 2 +- .../local_api/ControlWebsocketWorkerTest.java | 2 +- .../local_api/DeprovisionApiHandlerTest.java | 2 +- .../GetConfigurationHandlerTest.java | 2 +- .../iofog/local_api/GpsApiHandlerTest.java | 2 +- .../LocalApiServerPipelineFactoryTest.java | 2 +- .../iofog/local_api/LocalApiServerTest.java | 2 +- .../iofog/local_api/LocalApiStatusTest.java | 2 +- .../iofog/local_api/LogApiHandlerTest.java | 2 +- .../iofog/local_api/MessageCallbackTest.java | 2 +- .../message_bus/IOMessageListenerTest.java | 2 +- .../iofog/message_bus/MessageArchiveTest.java | 2 +- .../message_bus/MessageBusServerTest.java | 2 +- .../message_bus/MessageBusStatusTest.java | 2 +- .../iofog/message_bus/MessageBusTest.java | 2 +- .../iofog/message_bus/MessageBusUtilTest.java | 2 +- .../message_bus/MessageIdGeneratorTest.java | 2 +- .../message_bus/MessagePublisherTest.java | 2 +- .../message_bus/MessageReceiverTest.java | 2 +- .../iofog/message_bus/MessageTest.java | 2 +- .../process_manager/ContainerManagerTest.java | 2 +- .../process_manager/ContainerTaskTest.java | 2 +- .../iofog/process_manager/DockerUtilTest.java | 2 +- .../ProcessManagerStatusTest.java | 2 +- .../process_manager/ProcessManagerTest.java | 2 +- .../RestartStuckCheckerTest.java | 2 +- .../process_manager/StatsCallbackTest.java | 2 +- .../pruning/DockerPruningManagerTest.java | 2 +- .../ResourceConsumptionManagerStatusTest.java | 2 +- .../ResourceConsumptionManagerTest.java | 2 +- .../ResourceManagerStatusTest.java | 2 +- .../resource_manager/ResourceManagerTest.java | 2 +- .../StatusReporterStatusTest.java | 2 +- .../status_reporter/StatusReporterTest.java | 2 +- .../supervisor/SupervisorStatusTest.java | 2 +- .../iofog/utils/CmdPropertiesTest.java | 2 +- .../eclipse/iofog/utils/OrchestratorTest.java | 2 +- .../configuration/ConfigurationTest.java | 2 +- .../device_info/ArchitectureTypeTest.java | 2 +- .../iofog/utils/logging/LogFormatterTest.java | 2 +- .../utils/logging/LoggingServiceTest.java | 2 +- .../iofog_version_controller/Main.java | 2 +- .../util/CommandShellExecutor.java | 2 +- .../util/CommandShellResultSet.java | 2 +- .../etc/iofog-agent/config-bck_new.xml | 2 +- .../iofog-agent/config-development_new.xml | 2 +- .../etc/iofog-agent/config-production_new.xml | 2 +- .../etc/iofog-agent/config-switcher_new.xml | 2 +- .../etc/iofog-agent/config_new.xml | 2 +- 195 files changed, 209 insertions(+), 219 deletions(-) create mode 160000 Agent diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 558c1eb7..24f647d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,7 +19,7 @@ on: env: PROJECT: 'datasance-pot' IMAGE_NAME: 'agent' - IOFOGCTL_VERSION: '3.0.8' + POTCTL_VERSION: '1.0.0' CONTROLLER_IMAGE: 'iofog/controller:latest' jobs: @@ -53,8 +53,8 @@ jobs: - uses: actions/checkout@v3 - run: sudo apt-get install jq - run: | - curl https://packagecloud.io/install/repositories/iofog/iofogctl/script.deb.sh | sudo bash - sudo apt-get install iofogctl=${{ env.IOFOGCTL_VERSION }} + curl https://packagecloud.io/install/repositories/datasance/potctl/script.deb.sh | sudo bash + sudo apt-get install potctl=${{ env.POTCTL_VERSION }} - name: 'Install bats' run: | sudo apt-get update -y @@ -98,17 +98,6 @@ jobs: VERSION=${{ steps.previoustag.outputs.tag }} echo "VERSION=${VERSION:1}-${{ github.run_number }}" >> "${GITHUB_OUTPUT}" fi - - name: Build and Push to GCR - if: (github.ref == 'refs/heads/develop') || contains(github.ref_name, '^v.*') - id: build_push_gcr - uses: RafikFarhad/push-to-gcr-github-action@v5-beta - with: - gcloud_service_key: ${{ secrets.GCLOUD_SERVICE_KEY }} - registry: gcr.io - project_id: ${{ env.PROJECT }} - image_name: ${{ env.IMAGE_NAME }} - image_tag: latest, develop, ${{ steps.tags.outputs.VERSION }} - dockerfile: Dockerfile - name: Login to Github Container Registry if: (github.ref == 'refs/heads/develop') || contains(github.ref_name, '^v.*') @@ -126,9 +115,9 @@ jobs: file: Dockerfile push: true tags: | - ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.VERSION }} - ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:latest - ghcr.io/eclipse-iofog/${{ env.IMAGE_NAME }}:develop + ghcr.io/datasance/${{ env.IMAGE_NAME }}:${{ steps.tags.outputs.VERSION }} + ghcr.io/datasance/${{ env.IMAGE_NAME }}:latest + ghcr.io/datasance/${{ env.IMAGE_NAME }}:develop - name: Set up Ruby 3.1.4 uses: actions/setup-ruby@v1 @@ -175,7 +164,7 @@ jobs: name: Upload deb package with: name: deb-package - path: packaging/iofog-agent/iofog-agent_${{ steps.pkg_version.outputs.version }}_all.deb + path: packaging/datasance/iofog-agent_${{ steps.pkg_version.outputs.version }}_all.deb - uses: bluwy/substitute-string-action@v1 id: sub @@ -188,7 +177,7 @@ jobs: name: Upload rpm package with: name: rpm-package - path: packaging/iofog-agent/iofog-agent-${{ steps.sub.outputs.result }}-1.noarch.rpm + path: packaging/datasance/iofog-agent-${{ steps.sub.outputs.result }}-1.noarch.rpm - uses: actions/download-artifact@v2 with: @@ -198,8 +187,8 @@ jobs: uses: danielmundi/upload-packagecloud@v1 with: PACKAGE-NAME: iofog-agent_${{ steps.pkg_version.outputs.version }}_all.deb - PACKAGECLOUD-USERNAME: iofog - PACKAGECLOUD-REPO: iofog-agent-dev + PACKAGECLOUD-USERNAME: datasance + PACKAGECLOUD-REPO: iofog-agent PACKAGECLOUD-DISTRIB: any/any PACKAGECLOUD-TOKEN: ${{ secrets.packagecloud_token }} @@ -210,9 +199,9 @@ jobs: - name: Publish rpm package to packagecloud uses: danielmundi/upload-packagecloud@v1 with: - PACKAGE-NAME: packaging/iofog-agent/iofog-agent-${{ steps.sub.outputs.result }}-1.noarch.rpm - PACKAGECLOUD-USERNAME: iofog - PACKAGECLOUD-REPO: iofog-agent-dev + PACKAGE-NAME: iofog-agent-${{ steps.sub.outputs.result }}-1.noarch.rpm + PACKAGECLOUD-USERNAME: datasance + PACKAGECLOUD-REPO: iofog-agent PACKAGECLOUD-DISTRIB: rpm_any/rpm_any PACKAGECLOUD-TOKEN: ${{ secrets.packagecloud_token }} diff --git a/Agent b/Agent new file mode 160000 index 00000000..9913c8dc --- /dev/null +++ b/Agent @@ -0,0 +1 @@ +Subproject commit 9913c8dcd39da3788bf088f76ea3fa9e204b9921 diff --git a/docs/ioFog-Command-Line-Specification.md b/docs/ioFog-Command-Line-Specification.md index cbea4b88..230390dd 100644 --- a/docs/ioFog-Command-Line-Specification.md +++ b/docs/ioFog-Command-Line-Specification.md @@ -86,7 +86,7 @@ iofog -v
 ioFog 1.0
-Copyright (C) 2018-2022 Edgeworx, Inc.
+Copyright (c) 2023 Datasance Teknoloji A.S.
 License ######### http://iofog.org/license
 This is open-source software with a commercial license: your usage is free until you use it in production commercially.
 There is NO WARRANTY, to the extent permitted by law.
diff --git a/iofog-agent-client/src/main/java/org/eclipse/iofog/Client.java b/iofog-agent-client/src/main/java/org/eclipse/iofog/Client.java
index a2d55cdc..b2581d19 100644
--- a/iofog-agent-client/src/main/java/org/eclipse/iofog/Client.java
+++ b/iofog-agent-client/src/main/java/org/eclipse/iofog/Client.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -228,7 +228,7 @@ private static String showHelp() {
 
     private static String version() {
         return "ioFog Agent " + getVersion() + " " +
-                "\nCopyright (C) 2018-2022 Edgeworx, Inc." +
+                "\nCopyright (c) 2023 Datasance Teknoloji A.S." +
                 "\nEclipse ioFog is provided under the Eclipse Public License (EPL2)" +
                 "\nhttps://www.eclipse.org/legal/epl-v20.html";
     }
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/Daemon.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/Daemon.java
index 8642daf9..6649d53f 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/Daemon.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/Daemon.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/IOFogModule.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/IOFogModule.java
index f3f59b1c..ebcbac00 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/IOFogModule.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/IOFogModule.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineAction.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineAction.java
index e5fa4a5b..bb063f56 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineAction.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineAction.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineConfigParam.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineConfigParam.java
index 111a6123..03ffe1b4 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineConfigParam.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineConfigParam.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineParser.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineParser.java
index ea82084e..fcd29a34 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineParser.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/CommandLineParser.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellExecutor.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellExecutor.java
index 56b1ffdc..eae38147 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellExecutor.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellExecutor.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellResultSet.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellResultSet.java
index 9792a681..6b2d931d 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellResultSet.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/command_line/util/CommandShellResultSet.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/ImageDownloadManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/ImageDownloadManager.java
index 77937136..efcd904c 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/ImageDownloadManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/ImageDownloadManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceData.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceData.java
index 9d774cee..772b1c0d 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceData.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceData.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManager.java
index e5b8a729..37f8a77e 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/Display.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/Display.java
index 14d9c94d..6574f960 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/Display.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/Display.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeEndpoints.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeEndpoints.java
index 4d51879f..497cedd5 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeEndpoints.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeEndpoints.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeInterface.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeInterface.java
index 2fffd9fb..5ac8a53e 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeInterface.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeInterface.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResource.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResource.java
index 5d73ffd3..9512198c 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResource.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResource.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResourceManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResourceManager.java
index 1ca0541c..997d097a 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResourceManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/edge_resources/EdgeResourceManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentException.java
index 73e2a06a..aad45b90 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentException.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentException.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentSystemException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentSystemException.java
index 9e525b45..f17c6ccb 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentSystemException.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentSystemException.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentUserException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentUserException.java
index 3f94a4b4..8d015a87 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentUserException.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/exception/AgentUserException.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgent.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgent.java
index 21ae19ba..e74602dd 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgent.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgent.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgentStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgentStatus.java
index 4f3b5704..9289bf20 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgentStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/FieldAgentStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/VersionHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/VersionHandler.java
index 4cbe05ae..1b6ee335 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/VersionHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/VersionHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/VersionCommand.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/VersionCommand.java
index 09f232c9..a5a76228 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/VersionCommand.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/enums/VersionCommand.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/exceptions/UnknownVersionCommandException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/exceptions/UnknownVersionCommandException.java
index 06c6aaeb..493332d7 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/exceptions/UnknownVersionCommandException.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/field_agent/exceptions/UnknownVersionCommandException.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsMode.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsMode.java
index 8bea825b..faa777e2 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsMode.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsMode.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsWebHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsWebHandler.java
index 839f6c4f..41c8eb60 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsWebHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/gps/GpsWebHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/BluetoothApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/BluetoothApiHandler.java
index c1f20b6a..5dc118a7 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/BluetoothApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/BluetoothApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/CommandLineApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/CommandLineApiHandler.java
index d4a0ea02..0938e4a9 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/CommandLineApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/CommandLineApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigApiHandler.java
index 5a231ca9..23b1f155 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigurationMap.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigurationMap.java
index 701b1be2..f1d4266d 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigurationMap.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ConfigurationMap.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlSignalSentInfo.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlSignalSentInfo.java
index b64d13e2..da95f0c3 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlSignalSentInfo.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlSignalSentInfo.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketHandler.java
index 6512ebbb..746f3a8a 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketWorker.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketWorker.java
index 113cba4b..d61de588 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketWorker.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ControlWebsocketWorker.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/DeprovisionApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/DeprovisionApiHandler.java
index 7867a977..a6a97509 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/DeprovisionApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/DeprovisionApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/EdgeResourceHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/EdgeResourceHandler.java
index 425d2b24..eebc0618 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/EdgeResourceHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/EdgeResourceHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GetConfigurationHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GetConfigurationHandler.java
index b219ee42..aa063906 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GetConfigurationHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GetConfigurationHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GpsApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GpsApiHandler.java
index 2611c84a..b5248a70 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GpsApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/GpsApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/InfoApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/InfoApiHandler.java
index 7a60744d..3fafb026 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/InfoApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/InfoApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApi.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApi.java
index 56e6feb4..14c60d5c 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApi.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApi.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServer.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServer.java
index 970fa433..9252b63c 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServer.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServer.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerHandler.java
index 43a010dd..43c56f80 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactory.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactory.java
index d97e61ec..e961f005 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactory.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactory.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiStatus.java
index dc32734f..6b07c18a 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LocalApiStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LogApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LogApiHandler.java
index 1a50a8ec..cefc8589 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LogApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/LogApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageCallback.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageCallback.java
index 3b5f71a1..3caabc69 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageCallback.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageCallback.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageReceiverHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageReceiverHandler.java
index 1a965a1b..637603a7 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageReceiverHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageReceiverHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSenderHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSenderHandler.java
index efc7362b..e0bb076f 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSenderHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSenderHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSentInfo.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSentInfo.java
index 85bc2212..bad47b44 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSentInfo.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageSentInfo.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketHandler.java
index 993de1a8..702a9a08 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketWorker.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketWorker.java
index 79b3397e..dbfacd8d 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketWorker.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/MessageWebsocketWorker.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ProvisionApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ProvisionApiHandler.java
index b4b99cff..f6543ebf 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ProvisionApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/ProvisionApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/QueryMessageReceiverHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/QueryMessageReceiverHandler.java
index c1092e5d..5d5b3277 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/QueryMessageReceiverHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/QueryMessageReceiverHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/StatusApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/StatusApiHandler.java
index ee9c28bb..78c0c770 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/StatusApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/StatusApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/VersionApiHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/VersionApiHandler.java
index c242606e..06b0f576 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/VersionApiHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/VersionApiHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebSocketMap.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebSocketMap.java
index 3cf4f23e..17929ae6 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebSocketMap.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebSocketMap.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebsocketUtil.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebsocketUtil.java
index d2a198ad..ec305393 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebsocketUtil.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/WebsocketUtil.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageReceiverWebSocketClientHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageReceiverWebSocketClientHandler.java
index ef8e9845..63259286 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageReceiverWebSocketClientHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageReceiverWebSocketClientHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSenderWebSocketClientHandler.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSenderWebSocketClientHandler.java
index 4bd7ee23..ca186566 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSenderWebSocketClientHandler.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSenderWebSocketClientHandler.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSocketTestMain.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSocketTestMain.java
index 302b79de..f9dbde70 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSocketTestMain.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageSocketTestMain.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketReceiverClient.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketReceiverClient.java
index 32f1e93a..aeda24aa 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketReceiverClient.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketReceiverClient.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketSenderClient.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketSenderClient.java
index 8ffcd691..c9ef7388 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketSenderClient.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/MessageWebsocketSenderClient.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestApiDriver.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestApiDriver.java
index f810914a..723b4d0e 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestApiDriver.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestApiDriver.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestPublishTest.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestPublishTest.java
index 7e1f0fa9..76ed7a3e 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestPublishTest.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestPublishTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestReceiveTest.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestReceiveTest.java
index 512d7e03..0d8a9faf 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestReceiveTest.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/RestReceiveTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientControl.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientControl.java
index cfcadd37..58080e77 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientControl.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientControl.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientHandlerControl.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientHandlerControl.java
index 6bf5737e..e8c2f4bf 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientHandlerControl.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/local_api/test/WebSocketClientHandlerControl.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/IOMessageListener.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/IOMessageListener.java
index dafdd412..5394979e 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/IOMessageListener.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/IOMessageListener.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/Message.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/Message.java
index d2140c80..1e6ec960 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/Message.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/Message.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageArchive.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageArchive.java
index e26f89c7..952301d2 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageArchive.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageArchive.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBus.java
index ff1c0652..365bc5d4 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusServer.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusServer.java
index 8aa178ab..98160e7e 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusServer.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusServer.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusStatus.java
index 32e8bcf8..782f17e5 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusUtil.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusUtil.java
index c1a8feda..f6ddd11b 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusUtil.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageBusUtil.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageIdGenerator.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageIdGenerator.java
index 56a9b881..9520baed 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageIdGenerator.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageIdGenerator.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessagePublisher.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessagePublisher.java
index 4b2447f3..28812669 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessagePublisher.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessagePublisher.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageReceiver.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageReceiver.java
index 83e2ab32..5be59032 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageReceiver.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/message_bus/MessageReceiver.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/EnvVar.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/EnvVar.java
index 20bb8bad..3fd6436f 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/EnvVar.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/EnvVar.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Microservice.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Microservice.java
index 54ef5aaa..e2cc322c 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Microservice.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Microservice.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceManager.java
index f1319406..d38b6bc6 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceState.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceState.java
index 87233996..f1e147a8 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceState.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceState.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceStatus.java
index c9296171..40140d28 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/MicroserviceStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/PortMapping.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/PortMapping.java
index b7d9a79f..148e4781 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/PortMapping.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/PortMapping.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Registry.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Registry.java
index b2911138..ec17cf64 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Registry.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Registry.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Route.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Route.java
index a453c049..74d3b0bd 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Route.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/Route.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMapping.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMapping.java
index 6da7b55d..8cf690e9 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMapping.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/microservice/VolumeMapping.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterface.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterface.java
index eefa2d2e..809920d8 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterface.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/network/IOFogNetworkInterface.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerManager.java
index a1039bb7..90053e0f 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerTask.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerTask.java
index 6381b8a4..d4a5401d 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerTask.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ContainerTask.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/DockerUtil.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/DockerUtil.java
index 5817d6a5..0e942fbc 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/DockerUtil.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/DockerUtil.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManager.java
index fd944d60..f7d18f42 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManagerStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManagerStatus.java
index f9ce7b83..913b8fa5 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManagerStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/ProcessManagerStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/RestartStuckChecker.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/RestartStuckChecker.java
index b3853364..2ee22c29 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/RestartStuckChecker.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/RestartStuckChecker.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/StatsCallback.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/StatsCallback.java
index 01e70614..e0467b8a 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/StatsCallback.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/StatsCallback.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskComparator.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskComparator.java
index 7a89f652..69d564f0 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskComparator.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskComparator.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskManager.java
index 4d56e1cd..534cadd9 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/process_manager/TaskManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnection.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnection.java
index 2f4b0ed9..e7d897bc 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnection.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnection.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnectionStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnectionStatus.java
index 02140291..a0529432 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnectionStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshConnectionStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManager.java
index a68cc962..07c1f6c2 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManagerStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManagerStatus.java
index 8d99c67a..77d91b5b 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManagerStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/proxy/SshProxyManagerStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/pruning/DockerPruningManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/pruning/DockerPruningManager.java
index 2923b122..8e13a6bc 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/pruning/DockerPruningManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/pruning/DockerPruningManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManager.java
index 44c8b807..e2561fac 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatus.java
index 6a3f9989..2522234d 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManager.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManager.java
index f1105854..37c0cbbe 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManager.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManager.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManagerStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManagerStatus.java
index 040620fd..22e77ca1 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManagerStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/resource_manager/ResourceManagerStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporter.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporter.java
index e488f667..8121fa82 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporter.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporter.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporterStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporterStatus.java
index bdf5d8be..234aec21 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporterStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/status_reporter/StatusReporterStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/Supervisor.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/Supervisor.java
index ca84e94c..8ebd9c79 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/Supervisor.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/Supervisor.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/SupervisorStatus.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/SupervisorStatus.java
index 97503848..9a920e35 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/SupervisorStatus.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/supervisor/SupervisorStatus.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/BytesUtil.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/BytesUtil.java
index 9240d49b..24a85104 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/BytesUtil.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/BytesUtil.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/CmdProperties.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/CmdProperties.java
index d04e8a06..86108f1c 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/CmdProperties.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/CmdProperties.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Constants.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Constants.java
index 76427cd5..e9cf66f7 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Constants.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Constants.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Orchestrator.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Orchestrator.java
index cb9c0b11..6b959a3b 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Orchestrator.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/Orchestrator.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/Configuration.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/Configuration.java
index 2727c469..6f76fd16 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/Configuration.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/Configuration.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/ConfigurationItemException.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/ConfigurationItemException.java
index d4e37158..6b5fc625 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/ConfigurationItemException.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/configuration/ConfigurationItemException.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/device_info/ArchitectureType.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/device_info/ArchitectureType.java
index f194709a..a8a96cba 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/device_info/ArchitectureType.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/device_info/ArchitectureType.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Either.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Either.java
index 9bf36054..d5045351 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Either.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Either.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function3.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function3.java
index eb7fb087..956c61a7 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function3.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function3.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function4.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function4.java
index 16874974..dd16ff92 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function4.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Function4.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Functions.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Functions.java
index f850fa43..bf3b4924 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Functions.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Functions.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Left.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Left.java
index 7721bae5..1d57b95f 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Left.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Left.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Pair.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Pair.java
index cc706249..e3321d64 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Pair.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Pair.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Right.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Right.java
index 19194ec0..37a20fcc 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Right.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Right.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Unit.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Unit.java
index 0e508b69..98c5794e 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Unit.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/functional/Unit.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LogFormatter.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LogFormatter.java
index 385286da..273c7e66 100644
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LogFormatter.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LogFormatter.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LoggingService.java b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LoggingService.java
index e37ff434..19162718 100755
--- a/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LoggingService.java
+++ b/iofog-agent-daemon/src/main/java/org/eclipse/iofog/utils/logging/LoggingService.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/main/resources/cmd_messages.properties b/iofog-agent-daemon/src/main/resources/cmd_messages.properties
index 8ab455f2..8cd9e457 100644
--- a/iofog-agent-daemon/src/main/resources/cmd_messages.properties
+++ b/iofog-agent-daemon/src/main/resources/cmd_messages.properties
@@ -1,4 +1,4 @@
-version_msg=ioFog %s \\nCopyright (C) 2022 Edgeworx, Inc. \\nEclipse ioFog is provided under the Eclipse Public License 2.0 (EPL-2.0) \\nhttps://www.eclipse.org/legal/epl-v20.html
+version_msg=ioFog %s \\nCopyright (c) 2023 Datasance Teknoloji A.S. \\nEclipse ioFog is provided under the Eclipse Public License 2.0 (EPL-2.0) \\nhttps://www.eclipse.org/legal/epl-v20.html
 deprovision_msg=Deprovisioning from controller ... %s
 provision_msg=Provisioning with key "%s" ... Result: %s
 provision_common_error=\\nProvisioning failed
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineActionTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineActionTest.java
index 9b448f6b..19ebf097 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineActionTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineActionTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
@@ -367,7 +367,7 @@ private static boolean isEqual(List list1, List list2) {
             "0.00 MB\\nSystem Total CPU            : 0.00 %";
 
     private String version = "ioFog 1 \n" +
-            "Copyright (C) 2018-2022 Edgeworx, Inc. \n" +
+            "Copyright (c) 2023 Datasance Teknoloji A.S. \n" +
             "Eclipse ioFog is provided under the Eclipse Public License 2.0 (EPL-2.0) \n" +
             "https://www.eclipse.org/legal/epl-v20.html";
 
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineConfigParamTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineConfigParamTest.java
index 3eb488a1..86a29731 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineConfigParamTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineConfigParamTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineParserTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineParserTest.java
index 1c6e660d..de1cfbf8 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineParserTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/CommandLineParserTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellExecutorTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellExecutorTest.java
index 5b21df28..5516a484 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellExecutorTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellExecutorTest.java
@@ -1,7 +1,7 @@
 package org.eclipse.iofog.command_line.util;
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellResultSetTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellResultSetTest.java
index 9ba322b9..c620ff64 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellResultSetTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/command_line/util/CommandShellResultSetTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/ImageDownloadManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/ImageDownloadManagerTest.java
index 0c6c1f1f..ee6a2974 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/ImageDownloadManagerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/ImageDownloadManagerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceDataTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceDataTest.java
index 6288da50..ab67b398 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceDataTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/MicroserviceStraceDataTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManagerTest.java
index fb7397b0..06e86548 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManagerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/diagnostics/strace/StraceDiagnosticManagerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentStatusTest.java
index 60dfb403..effcb011 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentStatusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentStatusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentTest.java
index ff5406e3..0db31fd3 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/FieldAgentTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/VersionHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/VersionHandlerTest.java
index 5bf402a6..3b4b96a3 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/VersionHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/VersionHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/enums/VersionCommandTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/enums/VersionCommandTest.java
index 0c9fdc23..aad77a8d 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/enums/VersionCommandTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/field_agent/enums/VersionCommandTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ApiHandlerHelpersTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ApiHandlerHelpersTest.java
index 4d72e104..b6043a46 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ApiHandlerHelpersTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ApiHandlerHelpersTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/BluetoothApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/BluetoothApiHandlerTest.java
index 108ff299..4f1bfd98 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/BluetoothApiHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/BluetoothApiHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/CommandLineApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/CommandLineApiHandlerTest.java
index 95ce885a..8e17432a 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/CommandLineApiHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/CommandLineApiHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ConfigApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ConfigApiHandlerTest.java
index 6c89979f..d7d8d2ec 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ConfigApiHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ConfigApiHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlSignalSentInfoTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlSignalSentInfoTest.java
index b5fd1c4c..e8e7fee4 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlSignalSentInfoTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlSignalSentInfoTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketHandlerTest.java
index f2f4bc98..a1b9f198 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketWorkerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketWorkerTest.java
index a2b15ff7..8e85ba78 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketWorkerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/ControlWebsocketWorkerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/DeprovisionApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/DeprovisionApiHandlerTest.java
index e0dea7a1..7d16e4d6 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/DeprovisionApiHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/DeprovisionApiHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GetConfigurationHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GetConfigurationHandlerTest.java
index 4134c82c..11bdfd57 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GetConfigurationHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GetConfigurationHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GpsApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GpsApiHandlerTest.java
index 58c77c33..50ab9932 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GpsApiHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/GpsApiHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactoryTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactoryTest.java
index 0e084baa..4cf26998 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactoryTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerPipelineFactoryTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerTest.java
index ef53ee0b..3373f296 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiServerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiStatusTest.java
index acb0ae54..93fd79df 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiStatusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LocalApiStatusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LogApiHandlerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LogApiHandlerTest.java
index 33e415cd..f336ace0 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LogApiHandlerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/LogApiHandlerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/MessageCallbackTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/MessageCallbackTest.java
index 865971d0..e6006969 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/MessageCallbackTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/local_api/MessageCallbackTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/IOMessageListenerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/IOMessageListenerTest.java
index 4ed8363d..e1760fa5 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/IOMessageListenerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/IOMessageListenerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageArchiveTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageArchiveTest.java
index 52a04ee9..1fb9c645 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageArchiveTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageArchiveTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusServerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusServerTest.java
index 774075cd..4f0cad20 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusServerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusServerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusStatusTest.java
index 4e00f990..4d03ee2e 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusStatusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusStatusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusTest.java
index 16cc7d40..d326bec2 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusUtilTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusUtilTest.java
index 3bf031c8..9ceff7a5 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusUtilTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageBusUtilTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageIdGeneratorTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageIdGeneratorTest.java
index 87197816..ae31e68b 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageIdGeneratorTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageIdGeneratorTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessagePublisherTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessagePublisherTest.java
index 8879ac56..3c570729 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessagePublisherTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessagePublisherTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageReceiverTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageReceiverTest.java
index 404996e7..3791eb13 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageReceiverTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageReceiverTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageTest.java
index bd9a6187..d5cc95d4 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/message_bus/MessageTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerManagerTest.java
index 16ecc772..227159a1 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerManagerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerManagerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerTaskTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerTaskTest.java
index a58cef89..5eb11dcf 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerTaskTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ContainerTaskTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/DockerUtilTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/DockerUtilTest.java
index c5270620..d1c4ca6c 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/DockerUtilTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/DockerUtilTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerStatusTest.java
index 882fe4fd..7a195516 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerStatusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerStatusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerTest.java
index dcdff799..932c3c09 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/ProcessManagerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/RestartStuckCheckerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/RestartStuckCheckerTest.java
index c171bacf..3e2fac3a 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/RestartStuckCheckerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/RestartStuckCheckerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/StatsCallbackTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/StatsCallbackTest.java
index 82222ade..2070667f 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/StatsCallbackTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/process_manager/StatsCallbackTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/pruning/DockerPruningManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/pruning/DockerPruningManagerTest.java
index 493519d1..4e02fab5 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/pruning/DockerPruningManagerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/pruning/DockerPruningManagerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatusTest.java
index 8acfbc1e..637e8365 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerStatusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerTest.java
index 946fe915..cee0f35a 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_consumption_manager/ResourceConsumptionManagerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerStatusTest.java
index f78fd4ef..b79e899d 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerStatusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerStatusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerTest.java
index 0540e6bc..5c62a4c5 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/resource_manager/ResourceManagerTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterStatusTest.java
index 2bb59c37..59a83060 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterStatusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterStatusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterTest.java
index 99c0cc4a..5dc357ef 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/status_reporter/StatusReporterTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorStatusTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorStatusTest.java
index 9f85a5f9..8101f7b9 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorStatusTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/supervisor/SupervisorStatusTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/CmdPropertiesTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/CmdPropertiesTest.java
index ef7ecc13..8805e1b9 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/CmdPropertiesTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/CmdPropertiesTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/OrchestratorTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/OrchestratorTest.java
index 07c75e0a..a77c75e7 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/OrchestratorTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/OrchestratorTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/configuration/ConfigurationTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/configuration/ConfigurationTest.java
index 2373708e..3b3f6a84 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/configuration/ConfigurationTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/configuration/ConfigurationTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/device_info/ArchitectureTypeTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/device_info/ArchitectureTypeTest.java
index f0825645..7fcfbf28 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/device_info/ArchitectureTypeTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/device_info/ArchitectureTypeTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LogFormatterTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LogFormatterTest.java
index 54777408..d9258894 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LogFormatterTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LogFormatterTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LoggingServiceTest.java b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LoggingServiceTest.java
index 8ab7df80..c8e50e59 100644
--- a/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LoggingServiceTest.java
+++ b/iofog-agent-daemon/src/test/java/org/eclipse/iofog/utils/logging/LoggingServiceTest.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/Main.java b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/Main.java
index 62b25468..f4c97408 100644
--- a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/Main.java
+++ b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/Main.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellExecutor.java b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellExecutor.java
index 94d1962d..4e7a0997 100755
--- a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellExecutor.java
+++ b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellExecutor.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellResultSet.java b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellResultSet.java
index 7df6ddbe..ca40452f 100644
--- a/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellResultSet.java
+++ b/iofog-version-controller/src/main/java/org/eclipse/iofog_version_controller/command_line/util/CommandShellResultSet.java
@@ -1,6 +1,6 @@
 /*
  * *******************************************************************************
- *  * Copyright (c) 2018-2022 Edgeworx, Inc.
+ *  * Copyright (c) 2023 Datasance Teknoloji A.S.
  *  *
  *  * This program and the accompanying materials are made available under the
  *  * terms of the Eclipse Public License v. 2.0 which is available at
diff --git a/packaging/iofog-agent/etc/iofog-agent/config-bck_new.xml b/packaging/iofog-agent/etc/iofog-agent/config-bck_new.xml
index c7f34aa7..e2dcc744 100644
--- a/packaging/iofog-agent/etc/iofog-agent/config-bck_new.xml
+++ b/packaging/iofog-agent/etc/iofog-agent/config-bck_new.xml
@@ -1,7 +1,7 @@