diff --git a/Mirth/Mirthix_channel.xml b/Mirth/Mirthix_channel.xml new file mode 100644 index 0000000..a230c1b --- /dev/null +++ b/Mirth/Mirthix_channel.xml @@ -0,0 +1,708 @@ + + ae0b7d75-5ce1-484b-afe0-aff6fa412073 + 2 + Zabbix Monitoring + MIRTHIX - Zabbix agent implementation for Mirth Connect. +https://github.com/cboyer/mirth-zabbix + true + + + America/Toronto + + 445 + + 0 + sourceConnector + + + + 0.0.0.0 + 10050 + + + d1 + true + false + true + + Default Resource + + + + Basic + + 0A + + true + + + false + 5000 + 0 + 65536 + 1000 + true + true + DEFAULT_ENCODING + 0 + + + + + + + 0 + Decode base64 + + JavaScript + + + Script + /** + * MIRTHIX - Zabbix agent implementation for Mirth Connect. + * Copyright (C) 2018 Cyril Boyer + * https://github.com/cboyer/mirth-zabbix + * + * source_transformer.js + * Decode base 64 data from TCP Listener (binary mode). + * + * Released under the GNU General Public License v3 (GPLv3) + */ + + msg = new java.lang.String(FileUtil.decode(msg)); + + + + + + + RAW + RAW + + + JavaScript + + + + + + JavaScript + + + + + + + + TCP Listener + SOURCE + true + true + + + + 1 + Zabbix Server + + + + false + false + 10000 + false + 0 + false + false + 1 + + false + + Default Resource + + + + + + + + 0 + Zabbix request processing + + JavaScript + + + Script + /** + * MIRTHIX - Zabbix agent implementation for Mirth Connect. + * Copyright (C) 2018 Cyril Boyer + * https://github.com/cboyer/mirth-zabbix + * + * destination_transformer.js + * Process requested data (discovery, item). + * + * Released under the GNU General Public License v3 (GPLv3) + */ + +var agent_version = '1.0.0'; +var item_requested = msg.toString(); +//logger.info("Zabbix requested: " + item_requested); //Debug + +//Parse parameters in requested item +if (item_requested.indexOf('[') != -1 && item_requested.indexOf(']') != -1 ) { + var connector_id = ''; + var channel_id = ''; + var metric = ''; + + //Get connector id and/or channel id + if (item_requested.indexOf('_') != -1) { + + if (item_requested.indexOf(',') != -1) { + connector_id = item_requested.split('_')[1]; + connector_id = connector_id.split(',')[0]; + } + else { + connector_id = item_requested.split('_')[1].replace(']', ''); + } + + connector_id = parseInt(connector_id); + channel_id = item_requested.split('_')[0]; + channel_id = channel_id.split('[')[1]; + } + else { + channel_id = item_requested.split('[')[1]; + channel_id = channel_id.replace(']', ''); + } + + //Get metric parameter + if (item_requested.indexOf(',') != -1) { + metric = item_requested.split(',')[1].replace(']', ''); + } + + item_requested = item_requested.split('[')[0]; +} + + +/* + * Zabbix agent passive checks implementation + * https://www.zabbix.com/documentation/4.0/manual/appendix/items/activepassive + */ + +switch (item_requested) { + + case 'agent.ping': + msg = 1; + //logger.info("Agent ping: " + msg); //Debug + break; + + case 'agent.version': + msg = 'Mirthix ' + agent_version; + //logger.info("Agent version: " + msg); //Debug + break; + + case 'agent.hostname': + case 'system.uname': + msg = com.mirth.connect.server.controllers.ConfigurationController.getInstance().getServerName(); + //logger.info("System name: " + msg); //Debug + break; + + case 'mirth.deployementdate': + var controller = com.mirth.connect.server.controllers.ControllerFactory.getFactory().createEngineController(); + msg = controller.getChannelStatus(channel_id).getDeployedDate().getTime().toString(); + //logger.info("Deployment date: " + channel_id + " " + msg); //Debug + break; + + case 'mirth.statistics': + switch (metric) { + case 'received': + msg = ChannelUtil.getReceivedCount(channel_id, connector_id); + //logger.info("Received: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + case 'sent': + msg = ChannelUtil.getSentCount(channel_id, connector_id); + //logger.info("Sent: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + case 'errored': + msg = ChannelUtil.getErrorCount(channel_id, connector_id); + //logger.info("Errored: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + case 'queued': + msg = ChannelUtil.getQueuedCount(channel_id, connector_id); + //logger.info("Queued: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + case 'filtered': + msg = ChannelUtil.getFilteredCount(channel_id, connector_id); + //logger.info("Filtered: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + default: + msg = "ZBX_NOTSUPPORTED\x00Metric not implemented in Mirthix: " + msg; + } + break; + + case 'mirth.channel.status': + case 'mirth.connector.status': + var item_status = ''; + if (item_requested == 'mirth.connector.status') + item_status = ChannelUtil.getConnectorState(channel_id, connector_id) + ""; //toString() not effective in switch + + if (item_requested == 'mirth.channel.status') + item_status = ChannelUtil.getChannelState(channel_id) + ""; //toString() not effective in switch + + switch (item_status) { + case 'Started': + msg = 0; + break; + case 'Stopped': + msg = 1; + break; + case 'Paused': + msg = 2; + break; + case 'Deploying': + msg = 3; + break; + case 'Pausing': + msg = 4; + break; + case 'Starting': + msg = 5; + break; + case 'Stopping': + msg = 6; + break; + case 'Undeploying': + msg = 7; + break; + case null: + msg = 8; + break; + default: + msg = 9; + } + + //logger.info("Status: " + msg); //Debug + break; + + /* + * Zabbix low level discovery implementation (JSON) for deployed channels and enabled connectors + * https://www.zabbix.com/documentation/4.0/manual/discovery/low_level_discovery + */ + + //Autodiscovery for deployed channels + case 'mirth.discovery.channel': + var deployed_channel_ids = ChannelUtil.getDeployedChannelIds().toArray(); + var controller = com.mirth.connect.server.controllers.ControllerFactory.getFactory().createChannelController(); + var zabbix_autodiscovery = { + "data" : [] + }; + + //Loop over deployed channels + for (var i = 0; i < deployed_channel_ids.length; i++) { + var channel_id = deployed_channel_ids[i]; + var channel_name = ChannelUtil.getDeployedChannelName(channel_id); + var channel = { + "{#ID}" : new String(channel_id), + "{#NAME}" : new String(channel_name) + } + + zabbix_autodiscovery.data.push(channel); + } + + msg = JSON.stringify(zabbix_autodiscovery); + break; + + //Autodiscovery for enabled connectors + case 'mirth.discovery.connector': + var deployed_channel_ids = ChannelUtil.getDeployedChannelIds().toArray(); + var controller = com.mirth.connect.server.controllers.ControllerFactory.getFactory().createChannelController(); + var zabbix_autodiscovery = { + "data" : [] + }; + + //Loop over deployed channels + for (var i = 0; i < deployed_channel_ids.length; i++) { + var channel_id = deployed_channel_ids[i]; + var channel_name = ChannelUtil.getDeployedChannelName(channel_id); + + //Deployed channel always has a Source connector with MetaDataId=0 + //MetaDataId only unique in channel context, so concatenate with ChannelId + if (ChannelUtil.getConnectorState(channel_id, 0) != null) { + var source_connector = { + "{#ID}" : new String(channel_id + "_0"), + "{#NAME}" : new String(channel_name + " | Source") + } + + zabbix_autodiscovery.data.push(source_connector); + } + + //Loop over destination connectors + var channel_controller = controller.getChannelById(channel_id); + for (var destination in Iterator(channel_controller.getDestinationConnectors())) { + + if (ChannelUtil.getConnectorState(channel_id, destination.getMetaDataId()) != null) { + var destination_connector = { + "{#ID}" : new String(channel_id + "_" + destination.getMetaDataId()), + "{#NAME}" : new String(channel_name+" | "+destination.getName()) + } + + zabbix_autodiscovery.data.push(destination_connector); + } + } + } + + msg = JSON.stringify(zabbix_autodiscovery); + break; + + default: + msg = "ZBX_NOTSUPPORTED\x00Key not implemented in Mirthix: " + msg; +} + + + + + + + RAW + RAW + + + JavaScript + + + + + + JavaScript + + + + + + + RAW + RAW + + + JavaScript + + + + + + JavaScript + + + + + + + + JavaScript Writer + DESTINATION + true + true + + + // Modify the message variable below to pre process data +return message; + // This script executes once after a message has been processed +// Responses returned from here will be stored as "Postprocessor" in the response map +return; + // This script executes once when the channel is deployed +// You only have access to the globalMap and globalChannelMap here to persist data +return; + // This script executes once when the channel is undeployed +// You only have access to the globalMap and globalChannelMap here to persist data +return; + + true + DISABLED + false + false + false + STARTED + false + + + + None + + + 1 + 1 + false + + Default Resource + + + \ No newline at end of file diff --git a/README.md b/README.md index 067d529..9b98b45 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,54 @@ -# mirth-zabbix -Zabbix protocol implementation for Mirth Connect integration engine. +# MIRTHIX + +Zabbix protocol implementation for [Mirth Connect](https://www.nextgen.com/products-and-services/NextGen-Connect-Integration-Engine-Downloads) integration engine. Provides direct monitoring capabilities with a channel acting like Zabbix agent. + +## Implemented functionalities + +- [Low level discovery](https://www.zabbix.com/documentation/4.0/manual/discovery/low_level_discovery) for deployed channels and enabled connectors in Mirth Connect. +- [Passive agent checks](https://www.zabbix.com/documentation/4.0/manual/appendix/items/activepassive) for data collection (polling): + - Agent ping (agent.ping) + - Host name of zabbix_agentd running (agent.hostname, system.uname) + - Version of zabbix_agent(d) running (agent.version) + - Channel deployment date (mirth.deployementdate) + - Connector statistics: received, errored, filtered, queued, sent (mirth.statistics) + - Channel status (mirth.channel.status) + - Connector status (mirth.connector.status) + +## Getting Started + +### Prerequisites + +- Mirth Connect ≥ 3.2.1 +- Zabbix ≥ 3.4.12 +- Zabbix template (Zabbix_template.xml) +- Zabbix value map (Zabbix_valuemap.xml) +- Mirthix channel (Mirthix_channel.xml) + + +### Installing + +1. Import Mirthix channel `Mirthix_channel.xml` in Mirth Connect Administrator. +2. Configuration settings for Mirthix channel: + - TCP Listener: don't use port 10050 if Zabbix agent is already running on the server. + - Message storage/pruning is disabled by default because the channel will produce a lot of messages and full your database/file system. It should be activated only for debug purposes. +3. Import Zabbix template `Zabbix_template.xml` and value map `Zabbix_valuemap.xml` in Zabbix console. +4. Create host (or use an existing one) in Zabbix console with Mirth server IP address as Agent interface (with TCP Listener port) and add templates `Template App Mirth` and `Template App Zabbix Agent` (default agent availability template provided by Zabbix). + + +### Trigger adjustment + +Trigger adjustment is done with template [macros](https://www.zabbix.com/documentation/3.4/manual/config/macros/usermacros) and [macro contexts](https://www.zabbix.com/documentation/3.4/manual/config/macros/usermacros#user_macro_context): Templates > Template App Mirth > Macro + +Example: +To trigger "Queue on Zabbix Monitoring | Zabbix Server" problem when queued > 20, add macro `{$QUEUED:"Zabbix Monitoring | Zabbix Server"}` with value `20`. If no context is set on a macro, default macro `{$QUEUED}` will be used. + +To disable unwanted item/trigger creation, you have to disable item/trigger prototype in template discovery rules (Templates > Template App Mirth > Discovery rules). + +## What's next ? + +- IP source filtering for security +- [UserParameter](https://www.zabbix.com/documentation/4.0/manual/config/items/userparameters) functionality to trigger custom actions in Mirth. + +## License + +This project is licensed under the GNU General Public License v3 (GPLv3) - see the [LICENSE](LICENSE) file for details. diff --git a/Zabbix/Zabbix_template.xml b/Zabbix/Zabbix_template.xml new file mode 100644 index 0000000..a6055c3 --- /dev/null +++ b/Zabbix/Zabbix_template.xml @@ -0,0 +1,608 @@ + + + 3.4 + 2018-11-22T23:46:46Z + + + Templates + + + + + + + + Mirth channel status + + + 0 + Started + + + 1 + Stopped + + + 2 + Paused + + + 3 + Deploying + + + 4 + Pausing + + + 5 + Starting + + + 6 + Stopping + + + 7 + Undeploying + + + 8 + Disabled + + + 9 + Unknown + + + + + diff --git a/Zabbix/Zabbix_valuemap.xml b/Zabbix/Zabbix_valuemap.xml new file mode 100644 index 0000000..7ae93c5 --- /dev/null +++ b/Zabbix/Zabbix_valuemap.xml @@ -0,0 +1,52 @@ + + + 3.4 + 2018-11-18T22:56:54Z + + + Mirth channel status + + + 0 + Started + + + 1 + Stopped + + + 2 + Paused + + + 3 + Deploying + + + 4 + Pausing + + + 5 + Starting + + + 6 + Stopping + + + 7 + Undeploying + + + 8 + Disabled + + + 9 + Unknown + + + + + diff --git a/src/destination.js b/src/destination.js new file mode 100644 index 0000000..363d280 --- /dev/null +++ b/src/destination.js @@ -0,0 +1,38 @@ +/** + * MIRTHIX - Zabbix agent implementation for Mirth Connect. + * Copyright (C) 2018 Cyril Boyer + * https://github.com/cboyer/mirth-zabbix + * + * destination.js + * Build response for Zabbix server + * + * Released under the GNU General Public License v3 (GPLv3) + */ + +/* + * Zabbix Protocol + * https://www.zabbix.com/documentation/4.0/manual/appendix/protocols/header_datalen + */ + +var header = "ZBXD\x01"; +var data = connectorMessage.getEncodedData(); + +//logger.info("Sent to Zabbix: "+ data); //Debug + +var header_bytes = new java.lang.String(header).getBytes('UTF-8'); +var data_bytes = new java.lang.String(data).getBytes('UTF-8'); + +if (data_bytes.length + 1 >= 134217728) { // +1 for final 0x0A (LF) + throw('Message exceeds the maximum size 134217728 bytes.'); +} + +var length_bytes = Packages.java.nio.ByteBuffer.allocate(8); +length_bytes.order(java.nio.ByteOrder.LITTLE_ENDIAN); +length_bytes.putInt(data_bytes.length + 1); // +1 for final 0x0A (LF) + +var zabbix_message_bytes = Packages.java.nio.ByteBuffer.allocate(header_bytes.length + length_bytes.array().length + data_bytes.length); +zabbix_message_bytes.put(header_bytes); +zabbix_message_bytes.put(length_bytes.array()); +zabbix_message_bytes.put(data_bytes); + +return Packages.org.apache.commons.codec.binary.Base64.encodeBase64String(zabbix_message_bytes.array()); diff --git a/src/destination_transformer.js b/src/destination_transformer.js new file mode 100644 index 0000000..3d4e7fd --- /dev/null +++ b/src/destination_transformer.js @@ -0,0 +1,223 @@ +/** + * MIRTHIX - Zabbix agent implementation for Mirth Connect. + * Copyright (C) 2018 Cyril Boyer + * https://github.com/cboyer/mirth-zabbix + * + * destination_transformer.js + * Process requested data (discovery, item). + * + * Released under the GNU General Public License v3 (GPLv3) + */ + +var agent_version = '1.0.0'; +var item_requested = msg.toString(); +//logger.info("Zabbix requested: " + item_requested); //Debug + +//Parse parameters in requested item +if (item_requested.indexOf('[') != -1 && item_requested.indexOf(']') != -1 ) { + var connector_id = ''; + var channel_id = ''; + var metric = ''; + + //Get connector id and/or channel id + if (item_requested.indexOf('_') != -1) { + + if (item_requested.indexOf(',') != -1) { + connector_id = item_requested.split('_')[1]; + connector_id = connector_id.split(',')[0]; + } + else { + connector_id = item_requested.split('_')[1].replace(']', ''); + } + + connector_id = parseInt(connector_id); + channel_id = item_requested.split('_')[0]; + channel_id = channel_id.split('[')[1]; + } + else { + channel_id = item_requested.split('[')[1]; + channel_id = channel_id.replace(']', ''); + } + + //Get metric parameter + if (item_requested.indexOf(',') != -1) { + metric = item_requested.split(',')[1].replace(']', ''); + } + + item_requested = item_requested.split('[')[0]; +} + + +/* + * Zabbix agent passive checks implementation + * https://www.zabbix.com/documentation/4.0/manual/appendix/items/activepassive + */ + +switch (item_requested) { + + case 'agent.ping': + msg = 1; + //logger.info("Agent ping: " + msg); //Debug + break; + + case 'agent.version': + msg = 'Mirthix ' + agent_version; + //logger.info("Agent version: " + msg); //Debug + break; + + case 'agent.hostname': + case 'system.uname': + msg = com.mirth.connect.server.controllers.ConfigurationController.getInstance().getServerName(); + //logger.info("System name: " + msg); //Debug + break; + + case 'mirth.deployementdate': + var controller = com.mirth.connect.server.controllers.ControllerFactory.getFactory().createEngineController(); + msg = controller.getChannelStatus(channel_id).getDeployedDate().getTime().toString(); + //logger.info("Deployment date: " + channel_id + " " + msg); //Debug + break; + + case 'mirth.statistics': + switch (metric) { + case 'received': + msg = ChannelUtil.getReceivedCount(channel_id, connector_id); + //logger.info("Received: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + case 'sent': + msg = ChannelUtil.getSentCount(channel_id, connector_id); + //logger.info("Sent: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + case 'errored': + msg = ChannelUtil.getErrorCount(channel_id, connector_id); + //logger.info("Errored: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + case 'queued': + msg = ChannelUtil.getQueuedCount(channel_id, connector_id); + //logger.info("Queued: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + case 'filtered': + msg = ChannelUtil.getFilteredCount(channel_id, connector_id); + //logger.info("Filtered: " + channel_id + " " + connector_id + " : " + msg); //Debug + break; + default: + msg = "ZBX_NOTSUPPORTED\x00Metric not implemented in Mirthix: " + msg; + } + break; + + case 'mirth.channel.status': + case 'mirth.connector.status': + var item_status = ''; + if (item_requested == 'mirth.connector.status') + item_status = ChannelUtil.getConnectorState(channel_id, connector_id) + ""; //toString() not effective in switch + + if (item_requested == 'mirth.channel.status') + item_status = ChannelUtil.getChannelState(channel_id) + ""; //toString() not effective in switch + + switch (item_status) { + case 'Started': + msg = 0; + break; + case 'Stopped': + msg = 1; + break; + case 'Paused': + msg = 2; + break; + case 'Deploying': + msg = 3; + break; + case 'Pausing': + msg = 4; + break; + case 'Starting': + msg = 5; + break; + case 'Stopping': + msg = 6; + break; + case 'Undeploying': + msg = 7; + break; + case null: + msg = 8; + break; + default: + msg = 9; + } + + //logger.info("Status: " + msg); //Debug + break; + + /* + * Zabbix low level discovery implementation (JSON) for deployed channels and enabled connectors + * https://www.zabbix.com/documentation/4.0/manual/discovery/low_level_discovery + */ + + //Autodiscovery for deployed channels + case 'mirth.discovery.channel': + var deployed_channel_ids = ChannelUtil.getDeployedChannelIds().toArray(); + var controller = com.mirth.connect.server.controllers.ControllerFactory.getFactory().createChannelController(); + var zabbix_autodiscovery = { + "data" : [] + }; + + //Loop over deployed channels + for (var i = 0; i < deployed_channel_ids.length; i++) { + var channel_id = deployed_channel_ids[i]; + var channel_name = ChannelUtil.getDeployedChannelName(channel_id); + var channel = { + "{#ID}" : new String(channel_id), + "{#NAME}" : new String(channel_name) + } + + zabbix_autodiscovery.data.push(channel); + } + + msg = JSON.stringify(zabbix_autodiscovery); + break; + + //Autodiscovery for enabled connectors + case 'mirth.discovery.connector': + var deployed_channel_ids = ChannelUtil.getDeployedChannelIds().toArray(); + var controller = com.mirth.connect.server.controllers.ControllerFactory.getFactory().createChannelController(); + var zabbix_autodiscovery = { + "data" : [] + }; + + //Loop over deployed channels + for (var i = 0; i < deployed_channel_ids.length; i++) { + var channel_id = deployed_channel_ids[i]; + var channel_name = ChannelUtil.getDeployedChannelName(channel_id); + + //Deployed channel always has a Source connector with MetaDataId=0 + //MetaDataId only unique in channel context, so concatenate with ChannelId + if (ChannelUtil.getConnectorState(channel_id, 0) != null) { + var source_connector = { + "{#ID}" : new String(channel_id + "_0"), + "{#NAME}" : new String(channel_name + " | Source") + } + + zabbix_autodiscovery.data.push(source_connector); + } + + //Loop over destination connectors + var channel_controller = controller.getChannelById(channel_id); + for (var destination in Iterator(channel_controller.getDestinationConnectors())) { + + if (ChannelUtil.getConnectorState(channel_id, destination.getMetaDataId()) != null) { + var destination_connector = { + "{#ID}" : new String(channel_id + "_" + destination.getMetaDataId()), + "{#NAME}" : new String(channel_name+" | "+destination.getName()) + } + + zabbix_autodiscovery.data.push(destination_connector); + } + } + } + + msg = JSON.stringify(zabbix_autodiscovery); + break; + + default: + msg = "ZBX_NOTSUPPORTED\x00Key not implemented in Mirthix: " + msg; +} diff --git a/src/source_transformer.js b/src/source_transformer.js new file mode 100644 index 0000000..a89faa1 --- /dev/null +++ b/src/source_transformer.js @@ -0,0 +1,12 @@ +/** + * MIRTHIX - Zabbix agent implementation for Mirth Connect. + * Copyright (C) 2018 Cyril Boyer + * https://github.com/cboyer/mirth-zabbix + * + * source_transformer.js + * Decode base 64 data from TCP Listener (binary mode). + * + * Released under the GNU General Public License v3 (GPLv3) + */ + + msg = new java.lang.String(FileUtil.decode(msg));