├── .gitignore ├── .vagrant ├── Dockerfile ├── LICENSE.txt ├── MANIFEST.in ├── Makefile ├── README.rst ├── Vagrantfile ├── rabbitmqalert ├── __init__.py ├── apiclient.py ├── argumentsparser.py ├── conditionchecker.py ├── config │ ├── config.ini.example │ ├── defaults.ini │ └── service │ │ ├── rabbitmq-alert │ │ └── rabbitmq-alert.service ├── logger.py ├── models │ ├── __init__.py │ └── argument.py ├── notifier.py ├── rabbitmqalert.py └── tests │ ├── __init__.py │ ├── models │ ├── __init__.py │ └── test_argument.py │ ├── test_apiclient.py │ ├── test_argumentsparser.py │ ├── test_conditionchecker.py │ ├── test_logger.py │ ├── test_notifier.py │ └── test_rabbitmqalert.py ├── requirements_dev └── setup.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .project 3 | .pydevproject 4 | .settings/ 5 | 6 | **/*.pyc 7 | **/*.pyo 8 | build/ 9 | dist/ 10 | rabbitmq_alert.egg-info/ 11 | 12 | -------------------------------------------------------------------------------- /.vagrant: -------------------------------------------------------------------------------- 1 | {"active":{"default":"63854035-4bd8-4031-a8cc-5b85c34532bb"}} -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:2-alpine 2 | 3 | LABEL maintainer="milonas.ko@gmail.com" 4 | 5 | RUN pip install --no-cache-dir rabbitmq-alert 6 | 7 | CMD ["rabbitmq-alert"] 8 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Germano Fronza 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 2. Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | 3. Neither the name of copyright holders nor the names of its 12 | contributors may be used to endorse or promote products derived 13 | from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 16 | ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 17 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 18 | PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS 19 | BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 25 | POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include rabbitmqalert/config/config.ini.example 2 | include rabbitmqalert/config/service/rabbitmq-alert.service 3 | include rabbitmqalert/config/service/rabbitmq-alert 4 | include README.rst 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | deps-dev: 2 | sudo pip2 install -r requirements_dev 3 | 4 | test: clean 5 | python2 -m rabbitmqalert.tests.test_apiclient -b; \ 6 | python2 -m rabbitmqalert.tests.test_argumentsparser -b; \ 7 | python2 -m rabbitmqalert.tests.test_conditionchecker -b; \ 8 | python2 -m rabbitmqalert.tests.test_logger -b; \ 9 | python2 -m rabbitmqalert.tests.test_notifier -b; \ 10 | python2 -m rabbitmqalert.tests.test_rabbitmqalert -b; \ 11 | python2 -m rabbitmqalert.tests.models.test_argument -b; 12 | 13 | test-install: 14 | sudo python2 setup.py install 15 | 16 | clean: 17 | rm -rf build/ dist/ rabbitmq_alert.egg-info/ .eggs/ 18 | find . -name *.pyc -delete 19 | 20 | dist: clean 21 | python2 setup.py sdist 22 | 23 | dist-inspect: 24 | tar -tvf dist/* 25 | 26 | publish: dist 27 | twine upload dist/* 28 | 29 | lint: 30 | pylint rabbitmqalert --ignore=tests 31 | 32 | test-publish: dist 33 | twine upload --repository-url https://test.pypi.org/legacy/ dist/* 34 | 35 | test-metadata: clean 36 | python2 setup.py check --restructuredtext; \ 37 | python2 setup.py checkdocs 38 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | About RabbitMQ Alert 2 | ==================== 3 | 4 | Send notifications when predefined conditions are met. 5 | 6 | Which conditions? 7 | ================= 8 | 9 | - Ready messages 10 | - Unacknowledged messages 11 | - Total queued messages 12 | - Number of connected consumers 13 | - Number of open connections 14 | - Number of nodes running 15 | - Memory used by each node in MBs 16 | 17 | | My inspiration to create this notification sender is to monitor a set 18 | of Celery workers. Sometimes they stop working and monitoring 19 | | the queue size seems to be an easy way to know when these situations 20 | happen. Additionally, automatically monitoring the queue sizes 21 | | is a great way to scale up/down the number of workers. 22 | 23 | What type of notifications? 24 | =========================== 25 | 26 | Currently the following are supported: 27 | 28 | - E-mails 29 | - Slack messages 30 | - Telegram messages 31 | 32 | Installation 33 | ============ 34 | 35 | Use the ``PIP`` command, which should already exist in your Linux installation: 36 | 37 | :: 38 | 39 | sudo pip install rabbitmq-alert 40 | 41 | Usage 42 | ===== 43 | 44 | Execute with options 45 | -------------------- 46 | 47 | | You can execute ``rabbitmq-alert`` along using the provided options, 48 | but first take a look at ``--help`` to see whats available 49 | | and the purpose of each option. 50 | 51 | Example: 52 | 53 | :: 54 | 55 | sudo rabbitmq-alert \ 56 | --host=my-server --port=55672 --username=guest --password=guest \ 57 | --vhost=%2F --queue=my_queue1,my_queue2 --ready-queue-size=3 --check-rate=300 \ 58 | --email-to=admin@example.com --email-from=admin@example.com \ 59 | --email-subject="RabbitMQ alert at %s - %s" --email-server=localhost 60 | 61 | Execute with the global configuration file 62 | ------------------------------------------ 63 | Copy the example configuration file to the default path of the global configuration file: 64 | 65 | :: 66 | 67 | sudo cp /etc/rabbitmq-alert/config.ini.example /etc/rabbitmq-alert/config.ini 68 | 69 | | Edit it with you preferred settings. Then you are ready to execute ``rabbitmq-alert`` 70 | | using the global configuration file. Just execute: 71 | 72 | :: 73 | 74 | sudo rabbitmq-alert 75 | 76 | Execute with a custom configuration file 77 | ---------------------------------------- 78 | 79 | | Alternatively, you can use a custom configuration file. 80 | For the required format, take a look 81 | | at the ``/etc/rabbitmq-alert/config.ini.example`` file. 82 | 83 | Then execute ``rabbitmq-alert`` with the configuration file option: 84 | 85 | :: 86 | 87 | sudo rabbitmq-alert -c my_config.ini 88 | 89 | For a detailed description of the available sections and options, `view this wiki page `_. 90 | 91 | Different options per queue 92 | --------------------------- 93 | | Except conditions for all queues, you can also define queue specific conditions 94 | | in the configuration file, in case you want to have fine-tuned options for each queue. 95 | | Just create a ``[Conditions]`` section for each queue. Example: 96 | 97 | :: 98 | 99 | [Conditions:my-queue] 100 | ... 101 | 102 | [Conditions:my-other-queue] 103 | ... 104 | 105 | Note that queue names also have to be defined in the ``[Server]`` 106 | section of the configuration file: 107 | 108 | :: 109 | 110 | [Server] 111 | ... 112 | queues=my-queue,my-other-queue 113 | ... 114 | 115 | Execute as a daemon 116 | ------------------- 117 | 118 | | A ``systemd`` script is created upon installation with ``PIP``. 119 | | Use the following commands to reload the ``systemd`` configuration 120 | | and start ``rabbitmq-alert`` as a daemon. 121 | 122 | :: 123 | 124 | sudo systemctl daemon-reload 125 | sudo systemctl start rabbitmq-alert 126 | 127 | To have ``rabbitmq-alert`` always started on boot: 128 | 129 | :: 130 | 131 | sudo systemctl enable rabbitmq-alert 132 | 133 | In case your system still uses ``init.d``, an ``init.d`` script has been created 134 | in ``/etc/init.d`` upon ``PIP`` installation. To start ``rabbitmq-alert`` as a daemon: 135 | 136 | :: 137 | 138 | sudo /etc/init.d/rabbitmq-alert start 139 | 140 | To have ``rabbitmq-alert`` always started on boot: 141 | 142 | :: 143 | 144 | sudo update-rc.d rabbitmq-alert defaults 145 | 146 | Execute in a container 147 | ---------------------- 148 | 149 | | There is a docker image for the project. First, you have to create a configuration file 150 | | for ``rabbitmq-alert``, which will then be copied into the container. Then you can run 151 | | ``rabbitmq-alert`` inside a container. 152 | 153 | :: 154 | 155 | docker run -d --name rabbitmq-alert -v config.ini:/etc/rabbitmq-alert/config.ini \ 156 | mylkoh/rabbitmq-alert:latest 157 | 158 | For the configuration file, advise the ``config.ini.example`` that exists in the project's repository. 159 | 160 | Logging 161 | ------- 162 | 163 | | You can find the logs of ``rabbitmq-alert`` to ``/var/log/rabbitmq-alert/``. 164 | | Log files are rotated in a daily basis. 165 | 166 | Contribute 167 | ========== 168 | 169 | | The project ``rabbitmq-alert`` is written in ``python2``. 170 | | Of course, you can contribute to the project. Take a look at the 171 | GitHub “Issues” page and pick an issue to implement / fix. 172 | | Fork the project, develop and then create a pull request, in order for 173 | your code to be added to the project. 174 | 175 | Prepare your environment 176 | ------------------------ 177 | 178 | To start, you have to install the dev dependencies which are some 179 | required python packages: 180 | 181 | :: 182 | 183 | make deps-dev 184 | 185 | Run the tests! 186 | -------------- 187 | 188 | After writing your awesomeness, run the test suites to ensure that 189 | everything is still fine: 190 | 191 | :: 192 | 193 | make test 194 | 195 | Firstly, ensure that you have removed the rabbitmqalert package from your system. 196 | Otherwise you may find yourself running the tests on the installed package 197 | instead of the source code. 198 | 199 | Do add tests yourself for the code you contribute to ensure the quality 200 | of the project. 201 | 202 | Happy coding :-) 203 | 204 | Playing with the container 205 | -------------------------- 206 | 207 | Create a network that all containers will belong to: 208 | 209 | :: 210 | 211 | docker network create rabbitmq-alert 212 | 213 | 214 | Run ``rabbitmq`` into a container: 215 | 216 | :: 217 | 218 | docker run -d --name some-rabbit --net rabbitmq-alert -p 15672:15672 rabbitmq:management 219 | 220 | | You can then go to http://localhost:15672 in a browser to use the management plugin. 221 | | The username and password are both ``guest``. 222 | 223 | Create a fake SMTP server and check everything is okay with the email message functionality: 224 | 225 | :: 226 | 227 | docker run -d --name fake-smtp --net rabbitmq-alert -p 25:25 munkyboy/fakesmtp 228 | 229 | Now, run ``rabbitmq-alert`` using the same network: 230 | 231 | :: 232 | 233 | docker run -d --name rabbitmq-alert --net rabbitmq-alert \ 234 | -v config.ini:/etc/rabbitmq-alert/config.ini mylkoh/rabbitmq-alert:latest 235 | 236 | Publishing 237 | ---------- 238 | 239 | For publishing the package and the container image, `view this wiki page `_. 240 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | MOUNT_POINT = "/vagrant" 5 | 6 | Vagrant::Config.run do |config| 7 | # Every Vagrant virtual environment requires a box to build off of. 8 | config.vm.box = "base-precise64" 9 | 10 | # The url from where the 'config.vm.box' box will be fetched if it 11 | # doesn't already exist on the user's system. 12 | config.vm.box_url = "http://files.vagrantup.com/precise64.box" 13 | 14 | # Forward a port from the guest to the host, which allows for outside 15 | # computers to access the VM, whereas host only networking does not. 16 | config.vm.forward_port 55672, 55672 17 | 18 | # Configure a private network required by nfs folder share 19 | config.vm.network :hostonly, "33.33.33.10" 20 | 21 | # NFS just does not work on windows 22 | if RUBY_PLATFORM =~ /mswin(32|64)/ 23 | config.vm.share_folder("v-root", MOUNT_POINT, ".") 24 | else 25 | # Root shared folder using nfs for better performance 26 | config.vm.share_folder("v-root", MOUNT_POINT, ".", :nfs => true) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /rabbitmqalert/__init__.py: -------------------------------------------------------------------------------- 1 | import apiclient 2 | import argumentsparser 3 | import logger 4 | import models 5 | import rabbitmqalert 6 | -------------------------------------------------------------------------------- /rabbitmqalert/apiclient.py: -------------------------------------------------------------------------------- 1 | import urllib2 2 | import json 3 | 4 | 5 | class ApiClient: 6 | 7 | def __init__(self, log, arguments): 8 | self.log = log 9 | self.arguments = arguments 10 | 11 | def get_queue(self): 12 | uri = "/api/queues/%s/%s" % (self.arguments["server_vhost"], self.arguments["server_queue"]) 13 | data = self.send_request(uri) 14 | 15 | return data 16 | 17 | def get_queues(self): 18 | uri = "/api/queues?page=1&page_size=300" 19 | data = self.send_request(uri) 20 | if data is None: 21 | self.log.error("No queues discovered (request failed).") 22 | return [] 23 | 24 | queues = [] 25 | for queue in data.get("items"): 26 | queues.append(queue.get("name")) 27 | 28 | if queues: 29 | self.log.info("Queues discovered: {0}".format(", ".join(queues))) 30 | else: 31 | self.log.error("No queues discovered.") 32 | 33 | return queues 34 | 35 | def get_connections(self): 36 | uri = "/api/connections" 37 | data = self.send_request(uri) 38 | 39 | return data 40 | 41 | def get_consumers(self): 42 | uri = "/api/consumers" 43 | data = self.send_request(uri) 44 | 45 | return data 46 | 47 | def get_nodes(self): 48 | uri = "/api/nodes" 49 | data = self.send_request(uri) 50 | 51 | return data 52 | 53 | def send_request(self, uri): 54 | url = "%s://%s:%s%s" % (self.arguments["server_scheme"], self.arguments["server_host"], self.arguments["server_port"], uri) 55 | 56 | password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm() 57 | password_mgr.add_password(None, url, self.arguments["server_username"], self.arguments["server_password"]) 58 | handler = urllib2.HTTPBasicAuthHandler(password_mgr) 59 | opener = urllib2.build_opener(handler) 60 | 61 | try: 62 | request = opener.open(url) 63 | response = request.read() 64 | request.close() 65 | 66 | return json.loads(response) 67 | except (urllib2.HTTPError, urllib2.URLError): 68 | self.log.error("Error while consuming the API endpoint \"{0}\"".format(url)) 69 | return None 70 | -------------------------------------------------------------------------------- /rabbitmqalert/argumentsparser.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import os.path 3 | 4 | import apiclient 5 | from models import argument 6 | 7 | REQUIRED_ARGUMENTS = [ 8 | "server_host", 9 | "server_port", 10 | "server_username", 11 | "server_password", 12 | "server_vhost", 13 | "server_check_rate", 14 | ] 15 | 16 | GENERIC_CONDITIONS = ( 17 | "conditions_consumers_connected", 18 | "conditions_open_connections", 19 | "conditions_nodes_running", 20 | "conditions_node_memory_used" 21 | ) 22 | 23 | QUEUE_CONDITIONS = ( 24 | "conditions_ready_queue_size", 25 | "conditions_unack_queue_size", 26 | "conditions_total_queue_size", 27 | "conditions_queue_consumers_connected" 28 | ) 29 | 30 | 31 | class ArgumentsParser: 32 | 33 | def __init__(self, logger): 34 | self.log = logger 35 | 36 | def parse(self, parser): 37 | arguments = vars(parser.parse_args()) 38 | 39 | model = argument.Argument(self.log, arguments) 40 | 41 | # parse the standard arguments (created with argparse) 42 | for group in parser._action_groups: 43 | group_title = group.title 44 | for group_argument in group._group_actions: 45 | arguments[group_argument.dest] = model.get_value(group_title, group_argument) 46 | 47 | if arguments["server_queues_discovery"] == True: 48 | arguments["server_queues"] = apiclient.ApiClient(self.log, arguments).get_queues() 49 | else: 50 | arguments["server_queues"] = arguments["server_queues"].split(",") 51 | 52 | if arguments["email_to"] is not None: 53 | arguments["email_to"] = arguments["email_to"].split(",") 54 | 55 | # parse non standard arguments on files 56 | arguments["queue_conditions"] = dict() 57 | for queue in arguments["server_queues"]: 58 | group_title = "Conditions:" + queue 59 | if not model.files_have_group(group_title): 60 | continue 61 | 62 | arguments["queue_conditions"][queue] = dict() 63 | for condition in QUEUE_CONDITIONS: 64 | group_argument = model.create_argument_object(condition, int, None) 65 | arguments["queue_conditions"][queue][condition] = model.get_value(group_title, group_argument) 66 | 67 | self.validate(arguments) 68 | 69 | conditions = self.format_conditions(arguments) 70 | arguments = dict(arguments.items() + conditions.items()) 71 | 72 | return arguments 73 | 74 | def validate(self, arguments): 75 | for argument in REQUIRED_ARGUMENTS: 76 | if argument in arguments and arguments[argument] is not None: 77 | continue 78 | 79 | name = "_".join(argument.split("_")[1:]) 80 | self.log.error("Required argument not defined: " + name) 81 | exit(1) 82 | 83 | @staticmethod 84 | def format_conditions(arguments): 85 | conditions = dict() 86 | 87 | # get the generic condition values from the "[Conditions]" section 88 | generic_conditions = dict() 89 | for key in GENERIC_CONDITIONS: 90 | generic_conditions[key] = arguments[key] 91 | 92 | for queue in arguments["server_queues"]: 93 | conditions[queue] = dict() 94 | 95 | if not queue in arguments["queue_conditions"]: 96 | for key in QUEUE_CONDITIONS: 97 | conditions[queue][key] = arguments[key] 98 | else: 99 | conditions[queue] = arguments["queue_conditions"][queue] 100 | 101 | return {"conditions": conditions, "generic_conditions": generic_conditions} 102 | -------------------------------------------------------------------------------- /rabbitmqalert/conditionchecker.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | class ConditionChecker: 6 | 7 | def __init__(self, log, client, notifier_object): 8 | self.log = log 9 | self.client = client 10 | self.notifier = notifier_object 11 | 12 | def check_queue_conditions(self, arguments): 13 | response = self.client.get_queue() 14 | if response is None: 15 | return 16 | 17 | messages_ready = response.get("messages_ready") 18 | messages_unacknowledged = response.get("messages_unacknowledged") 19 | messages = response.get("messages") 20 | consumers = response.get("consumers") 21 | 22 | queue = arguments["server_queue"] 23 | queue_conditions = arguments["conditions"][queue] 24 | ready_size = queue_conditions.get("conditions_ready_queue_size") 25 | unack_size = queue_conditions.get("conditions_unack_queue_size") 26 | total_size = queue_conditions.get("conditions_total_queue_size") 27 | consumers_connected_min = queue_conditions.get("conditions_queue_consumers_connected") 28 | 29 | if ready_size is not None and messages_ready > ready_size: 30 | self.notifier.send_notification("%s: messages_ready = %d > %d" % (queue, messages_ready, ready_size)) 31 | 32 | if unack_size is not None and messages_unacknowledged > unack_size: 33 | self.notifier.send_notification("%s: messages_unacknowledged = %d > %d" % (queue, messages_unacknowledged, unack_size)) 34 | 35 | if total_size is not None and messages > total_size: 36 | self.notifier.send_notification("%s: messages = %d > %d" % (queue, messages, total_size)) 37 | 38 | if consumers_connected_min is not None and consumers < consumers_connected_min: 39 | self.notifier.send_notification("%s: consumers_connected = %d < %d" % (queue, consumers, consumers_connected_min)) 40 | 41 | def check_consumer_conditions(self, arguments): 42 | response = self.client.get_consumers() 43 | if response is None: 44 | return 45 | 46 | consumers_connected = len(response) 47 | consumers_connected_min = arguments["generic_conditions"].get("conditions_consumers_connected") 48 | 49 | if consumers_connected is not None and consumers_connected < consumers_connected_min: 50 | self.notifier.send_notification("consumers_connected = %d < %d" % (consumers_connected, consumers_connected_min)) 51 | 52 | def check_connection_conditions(self, arguments): 53 | response = self.client.get_connections() 54 | if response is None: 55 | return 56 | 57 | open_connections = len(response) 58 | 59 | open_connections_min = arguments["generic_conditions"].get("conditions_open_connections") 60 | 61 | if open_connections is not None and open_connections < open_connections_min: 62 | self.notifier.send_notification("open_connections = %d < %d" % (open_connections, open_connections_min)) 63 | 64 | def check_node_conditions(self, arguments): 65 | response = self.client.get_nodes() 66 | if response is None: 67 | return 68 | 69 | nodes_running = len(response) 70 | 71 | conditions = arguments["generic_conditions"] 72 | nodes_run = conditions.get("conditions_nodes_running") 73 | node_memory = conditions.get("conditions_node_memory_used") 74 | 75 | if nodes_run is not None and nodes_running < nodes_run: 76 | self.notifier.send_notification("nodes_running = %d < %d" % (nodes_running, nodes_run)) 77 | 78 | for node in response: 79 | if node_memory is not None and node.get("mem_used") > (node_memory * pow(1024, 2)): 80 | self.notifier.send_notification("Node %s - node_memory_used = %d > %d MBs" % (node.get("name"), node.get("mem_used"), node_memory)) 81 | -------------------------------------------------------------------------------- /rabbitmqalert/config/config.ini.example: -------------------------------------------------------------------------------- 1 | [Server] 2 | scheme=http 3 | host=localhost 4 | port=15672 5 | host_alias=myserver 6 | username=guest 7 | password=guest 8 | vhost=%2f 9 | queues=test 10 | queues_discovery=False 11 | check_rate=10 12 | 13 | [Conditions] 14 | ready_queue_size=0 15 | unack_queue_size=0 16 | total_queue_size=0 17 | consumers_connected=1 18 | queue_consumers_connected=1 19 | open_connections=1 20 | nodes_running=1 21 | node_memory_used=100 22 | 23 | [Email] 24 | from=admin@example.com 25 | login=admin@example.com 26 | to=admin@example.com 27 | subject=RabbitMQ alert - Condition met at %s - %s 28 | host=localhost 29 | password=password 30 | ssl=False 31 | 32 | [Slack] 33 | url=https://hooks.slack.com/services/some-hashes-here 34 | channel=queues 35 | username=RabbitMQ 36 | 37 | [Telegram] 38 | bot_id=telegram-bot-id 39 | channel=telegram-channel 40 | -------------------------------------------------------------------------------- /rabbitmqalert/config/defaults.ini: -------------------------------------------------------------------------------- 1 | [Server] 2 | scheme=http 3 | queues_discovery=False 4 | 5 | [Email] 6 | ssl=False 7 | -------------------------------------------------------------------------------- /rabbitmqalert/config/service/rabbitmq-alert: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: skeleton 4 | # Required-Start: $remote_fs $syslog 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Example initscript 9 | # Description: This file should be used to construct scripts to be 10 | # placed in /etc/init.d. 11 | ### END INIT INFO 12 | 13 | 14 | # Author: Kostas Milonas 15 | 16 | # Do NOT "set -e" 17 | 18 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 19 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 20 | DESC="Sends notifications when predefined RabbitMQ conditions are met" 21 | NAME=rabbitmq-alert 22 | DAEMON=/usr/local/bin/$NAME 23 | DAEMON_ARGS="" 24 | PIDFILE=/var/run/$NAME.pid 25 | SCRIPTNAME=/etc/init.d/$NAME 26 | 27 | # Exit if the package is not installed 28 | [ -x "$DAEMON" ] || exit 0 29 | 30 | # Read configuration variable file if it is present 31 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 32 | 33 | # Load the VERBOSE setting and other rcS variables 34 | . /lib/init/vars.sh 35 | 36 | # Define LSB log_* functions. 37 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present 38 | # and status_of_proc is working. 39 | . /lib/lsb/init-functions 40 | 41 | # 42 | # Function that starts the daemon/service 43 | # 44 | do_start() 45 | { 46 | # Return 47 | # 0 if daemon has been started 48 | # 1 if daemon was already running 49 | # 2 if daemon could not be started 50 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ 51 | || return 1 52 | start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --background --make-pidfile -- \ 53 | $DAEMON_ARGS \ 54 | || return 2 55 | # Add code here, if necessary, that waits for the process to be ready 56 | # to handle requests from services started subsequently which depend 57 | # on this one. As a last resort, sleep for some time. 58 | } 59 | 60 | # 61 | # Function that stops the daemon/service 62 | # 63 | do_stop() 64 | { 65 | # Return 66 | # 0 if daemon has been stopped 67 | # 1 if daemon was already stopped 68 | # 2 if daemon could not be stopped 69 | # other if a failure occurred 70 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME 71 | RETVAL="$?" 72 | [ "$RETVAL" = 2 ] && return 2 73 | # Wait for children to finish too if this is a daemon that forks 74 | # and if the daemon is only ever run from this initscript. 75 | # If the above conditions are not satisfied then add some other code 76 | # that waits for the process to drop all resources that could be 77 | # needed by services started subsequently. A last resort is to 78 | # sleep for some time. 79 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON 80 | [ "$?" = 2 ] && return 2 81 | # Many daemons don't delete their pidfiles when they exit. 82 | rm -f $PIDFILE 83 | return "$RETVAL" 84 | } 85 | 86 | # 87 | # Function that sends a SIGHUP to the daemon/service 88 | # 89 | do_reload() { 90 | # 91 | # If the daemon can reload its configuration without 92 | # restarting (for example, when it is sent a SIGHUP), 93 | # then implement that here. 94 | # 95 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME 96 | return 0 97 | } 98 | 99 | case "$1" in 100 | start) 101 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" 102 | do_start 103 | case "$?" in 104 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 105 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 106 | esac 107 | ;; 108 | stop) 109 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 110 | do_stop 111 | case "$?" in 112 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 113 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 114 | esac 115 | ;; 116 | status) 117 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 118 | ;; 119 | #reload|force-reload) 120 | # 121 | # If do_reload() is not implemented then leave this commented out 122 | # and leave 'force-reload' as an alias for 'restart'. 123 | # 124 | #log_daemon_msg "Reloading $DESC" "$NAME" 125 | #do_reload 126 | #log_end_msg $? 127 | #;; 128 | restart|force-reload) 129 | # 130 | # If the "reload" option is implemented then remove the 131 | # 'force-reload' alias 132 | # 133 | log_daemon_msg "Restarting $DESC" "$NAME" 134 | do_stop 135 | case "$?" in 136 | 0|1) 137 | do_start 138 | case "$?" in 139 | 0) log_end_msg 0 ;; 140 | 1) log_end_msg 1 ;; # Old process is still running 141 | *) log_end_msg 1 ;; # Failed to start 142 | esac 143 | ;; 144 | *) 145 | # Failed to stop 146 | log_end_msg 1 147 | ;; 148 | esac 149 | ;; 150 | *) 151 | #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 152 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 153 | exit 3 154 | ;; 155 | esac 156 | 157 | : 158 | -------------------------------------------------------------------------------- /rabbitmqalert/config/service/rabbitmq-alert.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Send notifications when conditions are met on RabbitMQ 3 | After=multi-user.target 4 | 5 | [Service] 6 | Type=idle 7 | ExecStart=/usr/bin/rabbitmq-alert 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /rabbitmqalert/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging import handlers 3 | from sys import stdout 4 | 5 | LOGGING_PATH = "/var/log/rabbitmq-alert/rabbitmq-alert.log" 6 | 7 | 8 | class Logger: 9 | 10 | def __init__(self): 11 | self.logger = logging.getLogger() 12 | self.logger.setLevel(logging.DEBUG) 13 | 14 | rotate_handler = handlers.TimedRotatingFileHandler( 15 | filename=LOGGING_PATH, 16 | when="midnight" 17 | ) 18 | rotate_handler.suffix = "%Y%m%d" 19 | rotate_handler.setFormatter(logging.Formatter("%(asctime)s %(levelname)s %(message)s")) 20 | self.logger.addHandler(rotate_handler) 21 | 22 | stoud_handler = logging.StreamHandler(stdout) 23 | stoud_handler.setFormatter(logging.Formatter("%(asctime)s: %(levelname)s - %(message)s")) 24 | self.logger.addHandler(stoud_handler) 25 | 26 | def get_logger(self): 27 | return self.logger 28 | -------------------------------------------------------------------------------- /rabbitmqalert/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gfronza/rabbitmq-alert/e4ff11b2b653b74557ba68ddce4383c45725518a/rabbitmqalert/models/__init__.py -------------------------------------------------------------------------------- /rabbitmqalert/models/argument.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | import os 3 | 4 | CONFIG_FILE_PATH = "/etc/rabbitmq-alert/config.ini" 5 | 6 | 7 | class Argument: 8 | 9 | def __init__(self, logger, arguments): 10 | self.log = logger 11 | self.arguments = arguments 12 | 13 | self.defaults = self.load_defaults() 14 | self.file = self.load_file() 15 | 16 | def load_defaults(self): 17 | file_defaults = ConfigParser.ConfigParser() 18 | 19 | path = os.path.realpath(os.path.join(os.getcwd(), os.path.dirname(__file__))) 20 | defaults_file_path = os.path.join(path, "../config/defaults.ini") 21 | 22 | if os.path.isfile(defaults_file_path): 23 | file_defaults.read(defaults_file_path) 24 | self.log.info("Using defaults configuration file \"{0}\"".format(defaults_file_path)) 25 | else: 26 | self.log.error("Defaults configuration file \"{0}\" not found".format(defaults_file_path)) 27 | exit(1) 28 | 29 | return file_defaults 30 | 31 | def load_file(self): 32 | file = ConfigParser.ConfigParser() 33 | 34 | if os.path.isfile(CONFIG_FILE_PATH) and not self.arguments["config_file"]: 35 | file.read(CONFIG_FILE_PATH) 36 | self.log.info("Using configuration file \"{0}\"".format(CONFIG_FILE_PATH)) 37 | elif self.arguments["config_file"]: 38 | self.log.info("Using configuration file \"{0}\"".format(self.arguments["config_file"])) 39 | if not os.path.isfile(self.arguments["config_file"]): 40 | self.log.error("The provided configuration file \"{0}\" does not exist".format(self.arguments["config_file"])) 41 | exit(1) 42 | 43 | file.read(self.arguments["config_file"]) 44 | 45 | return file 46 | 47 | def get_type(self, argument): 48 | if argument.type is None and argument.const in [True, False]: 49 | return bool 50 | return argument.type 51 | 52 | def files_have_group(self, group): 53 | return self.file.has_section(group) or self.defaults.has_section(group) 54 | 55 | def create_argument_object(self, dest, object_type, const): 56 | group_argument = type('lamdbaobject', (object,), {})() 57 | group_argument.dest = dest 58 | group_argument.type = object_type 59 | group_argument.const = const 60 | return group_argument 61 | 62 | def get_value_from_file(self, file, group, argument): 63 | name = "_".join(argument.dest.split("_")[1:]) 64 | value = None 65 | 66 | if file.has_option(group, name): 67 | if self.get_type(argument) == str: 68 | value = file.get(group, name) 69 | elif self.get_type(argument) == int: 70 | value = file.getint(group, name) 71 | elif self.get_type(argument) == bool: 72 | value = file.getboolean(group, name) 73 | 74 | return value 75 | 76 | def get_value(self, group, argument): 77 | 78 | def foo(): 79 | # get value from cli arguments 80 | yield (self.arguments[argument.dest] if argument.dest in self.arguments else None) 81 | # get value from configuration file (given or global configuration file) 82 | yield self.get_value_from_file(self.file, group, argument) 83 | # get value from the defaults file 84 | yield self.get_value_from_file(self.defaults, group, argument) 85 | 86 | value = None 87 | try: 88 | # get the first not-None returned value of the above functions 89 | value = next(result for result in foo() if result is not None) 90 | except StopIteration: 91 | pass 92 | return value 93 | -------------------------------------------------------------------------------- /rabbitmqalert/notifier.py: -------------------------------------------------------------------------------- 1 | import smtplib 2 | import urllib2 3 | 4 | 5 | class Notifier: 6 | 7 | def __init__(self, logger, arguments): 8 | self.log = logger 9 | self.arguments = arguments 10 | 11 | def send_notification(self, body): 12 | text = "%s - %s" % (self.arguments["server_host_alias"] or self.arguments["server_host"], body) 13 | 14 | if self.arguments["email_to"]: 15 | self.log.info("Sending email notification: \"{0}\"".format(body)) 16 | 17 | server = smtplib.SMTP(self.arguments["email_server"], 25) 18 | 19 | if self.arguments["email_ssl"]: 20 | server = smtplib.SMTP_SSL(self.arguments["email_server"], 465) 21 | 22 | if self.arguments["email_password"]: 23 | if self.arguments["email_login"]: 24 | server.login(self.arguments["email_login"], self.arguments["email_password"]) 25 | else: 26 | server.login(self.arguments["email_from"], self.arguments["email_password"]) 27 | 28 | email_from = self.arguments["email_from"] 29 | recipients = self.arguments["email_to"] 30 | # add subject as header before message text 31 | subject_email = self.arguments["email_subject"] % (self.arguments["server_host_alias"] or self.arguments["server_host"], self.arguments["server_queue"]) 32 | text_email = "From: %s\nSubject: %s\n\n%s" % (email_from, subject_email, text) 33 | server.sendmail(email_from, recipients, text_email) 34 | server.quit() 35 | 36 | if self.arguments["slack_url"] and self.arguments["slack_channel"] and self.arguments["slack_username"]: 37 | self.log.info("Sending Slack notification: \"{0}\"".format(body)) 38 | 39 | # escape double quotes from possibly breaking the slack message payload 40 | text_slack = text.replace("\"", "\\\"") 41 | slack_payload = '{"channel": "#%s", "username": "%s", "text": "%s"}' % (self.arguments["slack_channel"], self.arguments["slack_username"], text_slack) 42 | 43 | request = urllib2.Request(self.arguments["slack_url"], slack_payload) 44 | response = urllib2.urlopen(request) 45 | response.close() 46 | 47 | if self.arguments["telegram_bot_id"] and self.arguments["telegram_channel"]: 48 | self.log.info("Sending Telegram notification: \"{0}\"".format(body)) 49 | 50 | text_telegram = "%s: %s" % (self.arguments["server_queue"], text) 51 | telegram_url = "https://api.telegram.org/bot%s/sendMessage?chat_id=%s&text=%s" % (self.arguments["telegram_bot_id"], self.arguments["telegram_channel"], text_telegram) 52 | 53 | request = urllib2.Request(telegram_url) 54 | response = urllib2.urlopen(request) 55 | response.close() 56 | -------------------------------------------------------------------------------- /rabbitmqalert/rabbitmqalert.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf-8 -*- 3 | 4 | import argparse 5 | import time 6 | 7 | import apiclient 8 | import argumentsparser 9 | import conditionchecker 10 | import logger 11 | import notifier 12 | 13 | 14 | def setup_arguments(): 15 | parser = argparse.ArgumentParser() 16 | 17 | generic = parser.add_argument_group("Generic") 18 | generic.add_argument("-c", "--config-file", dest="config_file", help="Path of the configuration file", type=str) 19 | 20 | server = parser.add_argument_group("Server") 21 | server.add_argument("--scheme", dest="server_scheme", help="RabbitMQ API scheme", type=str) 22 | server.add_argument("--host", dest="server_host", help="RabbitMQ API address", type=str) 23 | server.add_argument("--port", dest="server_port", help="RabbitMQ API port", type=str) 24 | server.add_argument("--host-alias", dest="server_host_alias", help="RabbitMQ API host alias (for display only)", type=str) 25 | server.add_argument("--username", dest="server_username", help="RabbitMQ API username", type=str) 26 | server.add_argument("--password", dest="server_password", help="RabbitMQ API password", type=str) 27 | server.add_argument("--vhost", dest="server_vhost", help="Name of the vhost to inspect", type=str) 28 | server.add_argument("--queues", dest="server_queues", help="List of comma-separated queue names to inspect", type=str) 29 | server.add_argument("--queues-discovery", dest="server_queues_discovery", help="Discover queues", default=None, action="store_true") 30 | server.add_argument("--check-rate", dest="server_check_rate", help="Conditions check frequency, in seconds.", type=int) 31 | 32 | conditions = parser.add_argument_group("Conditions") 33 | conditions.add_argument("--ready-queue-size", dest="conditions_ready_queue_size", help="Size of Ready messages on the queue to alert as warning", type=int) 34 | conditions.add_argument("--unacknowledged-queue-size", dest="conditions_unack_queue_size", help="Size of the Unacknowledged messages on the queue to alert as warning", type=int) 35 | conditions.add_argument("--total-queue-size", dest="conditions_total_queue_size", help="Size of the Total messages on the queue to alert as warning", type=int) 36 | conditions.add_argument("--queue-consumers-connected", dest="conditions_queue_consumers_connected", help="The number of consumers that should be connected to the queue", type=int) 37 | conditions.add_argument("--consumers-connected", dest="conditions_consumers_connected", help="The number of consumers that should be connected", type=int) 38 | conditions.add_argument("--open-connections", dest="conditions_open_connections", help="The number of open connections", type=int) 39 | conditions.add_argument("--nodes-running", dest="conditions_nodes_running", help="The number of nodes running", type=int) 40 | conditions.add_argument("--node-memory-used", dest="conditions_node_memory_used", help="Memory used by each node in MBs", type=int) 41 | 42 | email = parser.add_argument_group("Email") 43 | 44 | email.add_argument("--email-to", dest="email_to", help="List of comma-separated email addresses to send notification to", type=str) 45 | email.add_argument("--email-from", dest="email_from", help="The sender email address", type=str) 46 | email.add_argument("--email-login", dest="email_login", help="The sender email login", type=str) 47 | email.add_argument("--email-subject", dest="email_subject", help="The email subject", type=str) 48 | email.add_argument("--email-server", dest="email_server", help="The hostname or IP address of the mail server", type=str) 49 | email.add_argument("--email-password", dest="email_password", help="The password for the authentication on the mail server", type=str) 50 | email.add_argument("--email-ssl", dest="email_ssl", help="Use SSL to send email", default=None, action="store_true") 51 | 52 | slack = parser.add_argument_group("Slack") 53 | slack.add_argument("--slack-url", dest="slack_url", help="Slack hook URL", type=str) 54 | slack.add_argument("--slack-channel", dest="slack_channel", help="Slack channel to message to", type=str) 55 | slack.add_argument("--slack-username", dest="slack_username", help="Sender's Slack username", type=str) 56 | 57 | telegram = parser.add_argument_group("Telegram") 58 | telegram.add_argument("--telegram-bot-id", dest="telegram_bot_id", help="Telegram bot id", type=str) 59 | telegram.add_argument("--telegram-channel", dest="telegram_channel", help="Telegram channel", type=str) 60 | 61 | return parser 62 | 63 | 64 | def main(): 65 | log = logger.Logger().get_logger() 66 | log.info("Starting application...") 67 | 68 | arguments_parser = argumentsparser.ArgumentsParser(log) 69 | arguments = arguments_parser.parse(setup_arguments()) 70 | 71 | client = apiclient.ApiClient(log, arguments) 72 | notifier_object = notifier.Notifier(log, arguments) 73 | condition_checker = conditionchecker.ConditionChecker(log, client, notifier_object) 74 | 75 | while True: 76 | for queue in arguments["server_queues"]: 77 | arguments["server_queue"] = queue 78 | queue_conditions = arguments["conditions"][queue] 79 | 80 | if queue_conditions["conditions_ready_queue_size"] is not None \ 81 | or queue_conditions["conditions_unack_queue_size"] is not None \ 82 | or queue_conditions["conditions_total_queue_size"] is not None \ 83 | or queue_conditions["conditions_queue_consumers_connected"] is not None: 84 | condition_checker.check_queue_conditions(arguments) 85 | 86 | generic_conditions = arguments["generic_conditions"] 87 | if generic_conditions["conditions_nodes_running"] is not None \ 88 | or generic_conditions["conditions_node_memory_used"] is not None: 89 | condition_checker.check_node_conditions(arguments) 90 | if generic_conditions["conditions_open_connections"] is not None: 91 | condition_checker.check_connection_conditions(arguments) 92 | if generic_conditions["conditions_consumers_connected"] is not None: 93 | condition_checker.check_consumer_conditions(arguments) 94 | 95 | time.sleep(arguments["server_check_rate"]) 96 | 97 | 98 | if __name__ == "__main__": 99 | main() 100 | -------------------------------------------------------------------------------- /rabbitmqalert/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gfronza/rabbitmq-alert/e4ff11b2b653b74557ba68ddce4383c45725518a/rabbitmqalert/tests/__init__.py -------------------------------------------------------------------------------- /rabbitmqalert/tests/models/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gfronza/rabbitmq-alert/e4ff11b2b653b74557ba68ddce4383c45725518a/rabbitmqalert/tests/models/__init__.py -------------------------------------------------------------------------------- /rabbitmqalert/tests/models/test_argument.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf8 -*- 3 | 4 | import mock 5 | import unittest 6 | 7 | from rabbitmqalert import models 8 | from rabbitmqalert import argumentsparser 9 | from rabbitmqalert import rabbitmqalert 10 | 11 | 12 | class ArgumentTestCase(unittest.TestCase): 13 | 14 | def setUp(self): 15 | models.argument.ConfigParser_real = models.argument.ConfigParser 16 | models.argument.os_real = models.argument.os 17 | models.argument.Argument.load_defaults_real = models.argument.Argument.load_defaults 18 | models.argument.Argument.load_file_real = models.argument.Argument.load_file 19 | models.argument.Argument.get_type_real = models.argument.Argument.get_type 20 | models.argument.Argument.get_value_from_file_real = models.argument.Argument.get_value_from_file 21 | 22 | def tearDown(self): 23 | models.argument.ConfigParser = models.argument.ConfigParser_real 24 | models.argument.os = models.argument.os_real 25 | models.argument.Argument.load_defaults = models.argument.Argument.load_defaults_real 26 | models.argument.Argument.load_file = models.argument.Argument.load_file_real 27 | models.argument.Argument.get_type = models.argument.Argument.get_type_real 28 | models.argument.Argument.get_value_from_file = models.argument.Argument.get_value_from_file_real 29 | 30 | def test_init_loads_files(self): 31 | logger = mock.MagicMock() 32 | 33 | arguments = self.construct_argparse_parsed_arguments() 34 | 35 | models.argument.Argument.load_defaults = mock.MagicMock() 36 | models.argument.Argument.load_file = mock.MagicMock() 37 | argument = models.argument.Argument(logger, arguments) 38 | 39 | models.argument.Argument.load_defaults.assert_called_once() 40 | models.argument.Argument.load_file.assert_called_once() 41 | 42 | def test_load_defaults_loads_file_when_file_exists(self): 43 | logger = mock.MagicMock() 44 | 45 | arguments = {} 46 | 47 | models.argument.ConfigParser.ConfigParser.read = mock.MagicMock() 48 | models.argument.os.path.isfile = mock.MagicMock(return_value=True) 49 | # copy method and mock original to avoid calling during initialization of Argument 50 | models.argument.Argument.load_defaults_real = models.argument.Argument.load_defaults 51 | models.argument.Argument.load_defaults = mock.MagicMock() 52 | models.argument.Argument.load_file = mock.MagicMock() 53 | 54 | argument = models.argument.Argument(logger, arguments) 55 | # now that initilization is done, replace mocked load_defaults() with the original 56 | argument.load_defaults = argument.load_defaults_real 57 | 58 | argument.load_defaults() 59 | 60 | models.argument.ConfigParser.ConfigParser.read.assert_called_once() 61 | logger.info.assert_called_once() 62 | 63 | def test_load_defaults_exits_when_file_does_not_exist(self): 64 | logger = mock.MagicMock() 65 | 66 | arguments = {} 67 | 68 | models.argument.ConfigParser.ConfigParser.read = mock.MagicMock() 69 | models.argument.os.path.isfile = mock.MagicMock(return_value=False) 70 | # copy method and mock original to avoid calling during initialization of Argument 71 | models.argument.Argument.load_defaults_real = models.argument.Argument.load_defaults 72 | models.argument.Argument.load_defaults = mock.MagicMock() 73 | models.argument.Argument.load_file = mock.MagicMock() 74 | 75 | argument = models.argument.Argument(logger, arguments) 76 | # now that initilization is done, replace mocked load_defaults() with the original 77 | argument.load_defaults = argument.load_defaults_real 78 | 79 | with self.assertRaises(SystemExit) as se: 80 | argument.load_defaults() 81 | 82 | self.assertEqual(se.exception.code, 1) 83 | models.argument.ConfigParser.ConfigParser.read.assert_not_called() 84 | logger.error.assert_called_once() 85 | 86 | def test_load_file_loads_file_when_config_file_argument_given_and_file_exists(self): 87 | logger = mock.MagicMock() 88 | 89 | arguments = self.construct_argparse_parsed_arguments() 90 | arguments["config_file"] = "foo.ini" 91 | 92 | models.argument.ConfigParser.ConfigParser.read = mock.MagicMock() 93 | models.argument.os.path.isfile = mock.MagicMock(return_value=True) 94 | # copy method and mock original to avoid calling during initialization of Argument 95 | models.argument.Argument.load_file_real = models.argument.Argument.load_file 96 | models.argument.Argument.load_file = mock.MagicMock() 97 | models.argument.Argument.load_defaults = mock.MagicMock() 98 | 99 | argument = models.argument.Argument(logger, arguments) 100 | # now that initilization is done, replace mocked load_defaults() with the original 101 | argument.load_file = argument.load_file_real 102 | 103 | argument.load_file() 104 | 105 | models.argument.ConfigParser.ConfigParser.read.assert_called_once_with(arguments["config_file"]) 106 | logger.info.assert_called_once() 107 | 108 | def test_load_file_exits_when_config_file_argument_given_and_file_does_not_exist(self): 109 | logger = mock.MagicMock() 110 | 111 | arguments = self.construct_argparse_parsed_arguments() 112 | arguments["config_file"] = "foo.ini" 113 | 114 | models.argument.ConfigParser.ConfigParser.read = mock.MagicMock() 115 | models.argument.os.path.isfile = mock.MagicMock(return_value=False) 116 | # copy method and mock original to avoid calling during initialization of Argument 117 | models.argument.Argument.load_file_real = models.argument.Argument.load_file 118 | models.argument.Argument.load_file = mock.MagicMock() 119 | models.argument.Argument.load_defaults = mock.MagicMock() 120 | 121 | argument = models.argument.Argument(logger, arguments) 122 | # now that initilization is done, replace mocked load_file() with the original 123 | argument.load_file = argument.load_file_real 124 | 125 | with self.assertRaises(SystemExit) as se: 126 | argument.load_file() 127 | 128 | self.assertEqual(se.exception.code, 1) 129 | models.argument.ConfigParser.ConfigParser.read.assert_not_called() 130 | logger.error.assert_called_once() 131 | 132 | def test_load_file_loads_global_file_when_exists_and_no_config_file_argument_given(self): 133 | logger = mock.MagicMock() 134 | 135 | # no config_file argument given 136 | arguments = {"config_file": None} 137 | 138 | models.argument.ConfigParser.ConfigParser.read = mock.MagicMock() 139 | models.argument.os.path.isfile = mock.MagicMock(return_value=True) 140 | # copy method and mock original to avoid calling during initialization of Argument 141 | models.argument.Argument.load_file_real = models.argument.Argument.load_file 142 | models.argument.Argument.load_file = mock.MagicMock() 143 | models.argument.Argument.load_defaults = mock.MagicMock() 144 | 145 | argument = models.argument.Argument(logger, arguments) 146 | # now that initilization is done, replace mocked load_defaults() with the original 147 | argument.load_file = argument.load_file_real 148 | 149 | argument.load_file() 150 | 151 | models.argument.ConfigParser.ConfigParser.read.assert_called_once_with(models.argument.CONFIG_FILE_PATH) 152 | logger.info.assert_called_once_with("Using configuration file \"{0}\"".format(models.argument.CONFIG_FILE_PATH)) 153 | 154 | def test_get_type_returns_bool_type(self): 155 | argument_element = mock.MagicMock() 156 | argument_element.type = None 157 | argument_element.const = True 158 | 159 | logger = mock.MagicMock() 160 | arguments = self.construct_argparse_parsed_arguments() 161 | argument = models.argument.Argument(logger, arguments) 162 | 163 | self.assertEquals(bool, argument.get_type(argument_element)) 164 | 165 | argument_element.const = False 166 | self.assertEquals(bool, argument.get_type(argument_element)) 167 | 168 | def test_get_type_returns_int_type(self): 169 | argument_element = mock.MagicMock() 170 | argument_element.type = int 171 | 172 | logger = mock.MagicMock() 173 | arguments = self.construct_argparse_parsed_arguments() 174 | argument = models.argument.Argument(logger, arguments) 175 | 176 | self.assertEquals(int, argument.get_type(argument_element)) 177 | 178 | def test_get_type_returns_str_type(self): 179 | argument_element = mock.MagicMock() 180 | argument_element.type = str 181 | 182 | logger = mock.MagicMock() 183 | arguments = self.construct_argparse_parsed_arguments() 184 | argument = models.argument.Argument(logger, arguments) 185 | 186 | self.assertEquals(str, argument.get_type(argument_element)) 187 | 188 | def test_files_have_group_returns_false_when_group_does_not_exist(self): 189 | logger = mock.MagicMock() 190 | 191 | defaults_content = mock.MagicMock() 192 | defaults_content.has_section = mock.MagicMock(return_value=False) 193 | config_file_content = mock.MagicMock() 194 | config_file_content.has_section = mock.MagicMock(return_value=False) 195 | 196 | models.argument.Argument.load_defaults = mock.MagicMock(return_value=defaults_content) 197 | models.argument.Argument.load_file = mock.MagicMock(return_value=config_file_content) 198 | models.argument.Argument.get_value_from_file = mock.MagicMock() 199 | 200 | argument = models.argument.Argument(logger, {}) 201 | result = argument.files_have_group("foo") 202 | 203 | defaults_content.has_section.assert_called_once_with("foo") 204 | config_file_content.has_section.assert_called_once_with("foo") 205 | self.assertEquals(False, result) 206 | 207 | def test_files_have_group_returns_true_when_group_exists_on_config_file(self): 208 | logger = mock.MagicMock() 209 | 210 | defaults_content = mock.MagicMock() 211 | defaults_content.has_section = mock.MagicMock(return_value=False) 212 | config_file_content = mock.MagicMock() 213 | config_file_content.has_section = mock.MagicMock(return_value=True) 214 | 215 | models.argument.Argument.load_defaults = mock.MagicMock(return_value=defaults_content) 216 | models.argument.Argument.load_file = mock.MagicMock(return_value=config_file_content) 217 | models.argument.Argument.get_value_from_file = mock.MagicMock() 218 | 219 | argument = models.argument.Argument(logger, {}) 220 | result = argument.files_have_group("foo") 221 | 222 | defaults_content.has_section.assert_not_called() 223 | config_file_content.has_section.assert_called_once_with("foo") 224 | self.assertEquals(True, result) 225 | 226 | def test_files_have_group_returns_true_when_group_exists_on_defaults_file(self): 227 | logger = mock.MagicMock() 228 | 229 | defaults_content = mock.MagicMock() 230 | defaults_content.has_section = mock.MagicMock(return_value=True) 231 | config_file_content = mock.MagicMock() 232 | config_file_content.has_section = mock.MagicMock(return_value=False) 233 | 234 | models.argument.Argument.load_defaults = mock.MagicMock(return_value=defaults_content) 235 | models.argument.Argument.load_file = mock.MagicMock(return_value=config_file_content) 236 | models.argument.Argument.get_value_from_file = mock.MagicMock() 237 | 238 | argument = models.argument.Argument(logger, {}) 239 | result = argument.files_have_group("foo") 240 | 241 | defaults_content.has_section.assert_called_once_with("foo") 242 | config_file_content.has_section.assert_called_once_with("foo") 243 | self.assertEquals(True, result) 244 | 245 | def test_create_argument_object_returns_object(self): 246 | logger = mock.MagicMock() 247 | 248 | models.argument.Argument.load_defaults = mock.MagicMock() 249 | models.argument.Argument.load_file = mock.MagicMock() 250 | 251 | argument = models.argument.Argument(logger, {}) 252 | argument_object = argument.create_argument_object("foo", int, None) 253 | 254 | self.assertIsInstance(argument_object, object) 255 | self.assertEquals(argument_object.dest, "foo") 256 | self.assertEquals(argument_object.type, int) 257 | self.assertEquals(argument_object.const, None) 258 | 259 | def test_get_value_from_file_returns_none_when_file_has_no_such_argument(self): 260 | logger = mock.MagicMock() 261 | 262 | file = mock.MagicMock() 263 | file.has_option = mock.MagicMock(return_value=False) 264 | 265 | argument_element = mock.MagicMock() 266 | argument_element.dest = "server_host" 267 | argument_element.type = str 268 | 269 | models.argument.Argument.load_defaults = mock.MagicMock() 270 | models.argument.Argument.load_file = mock.MagicMock() 271 | models.argument.Argument.get_type = mock.MagicMock() 272 | 273 | argument = models.argument.Argument(logger, {}) 274 | value = argument.get_value_from_file(file, "foo-group", argument_element) 275 | 276 | self.assertEquals(None, value) 277 | file.has_option.assert_called_once() 278 | argument.get_type.assert_not_called() 279 | 280 | def test_get_value_from_file_gets_string_value(self): 281 | logger = mock.MagicMock() 282 | 283 | file = mock.MagicMock() 284 | file.has_option = mock.MagicMock(return_value=True) 285 | 286 | argument_element = mock.MagicMock() 287 | argument_element.dest = "server_host" 288 | argument_element.type = str 289 | 290 | models.argument.Argument.load_defaults = mock.MagicMock() 291 | models.argument.Argument.load_file = mock.MagicMock() 292 | models.argument.Argument.get_type = mock.MagicMock(return_value=str) 293 | 294 | argument = models.argument.Argument(logger, {}) 295 | argument.get_value_from_file(file, "foo-group", argument_element) 296 | 297 | file.has_option.assert_called_once() 298 | argument.get_type.assert_called() 299 | file.get.assert_called_once() 300 | file.getint.assert_not_called() 301 | file.getboolean.assert_not_called() 302 | 303 | def test_get_value_from_file_gets_integer_value(self): 304 | logger = mock.MagicMock() 305 | 306 | file = mock.MagicMock() 307 | file.has_option = mock.MagicMock(return_value=True) 308 | 309 | argument_element = mock.MagicMock() 310 | argument_element.dest = "server_port" 311 | argument_element.type = int 312 | 313 | models.argument.Argument.load_defaults = mock.MagicMock() 314 | models.argument.Argument.load_file = mock.MagicMock() 315 | models.argument.Argument.get_type = mock.MagicMock(return_value=int) 316 | 317 | argument = models.argument.Argument(logger, {}) 318 | argument.get_value_from_file(file, "foo-group", argument_element) 319 | 320 | file.has_option.assert_called_once() 321 | argument.get_type.assert_called() 322 | file.get.assert_not_called() 323 | file.getint.assert_called_once() 324 | file.getboolean.assert_not_called() 325 | 326 | def test_get_value_from_file_gets_boolean_value(self): 327 | logger = mock.MagicMock() 328 | 329 | file = mock.MagicMock() 330 | file.has_option = mock.MagicMock(return_value=True) 331 | 332 | argument_element = mock.MagicMock() 333 | argument_element.dest = "server_queues_discovery" 334 | argument_element.type = bool 335 | 336 | models.argument.Argument.load_defaults = mock.MagicMock() 337 | models.argument.Argument.load_file = mock.MagicMock() 338 | models.argument.Argument.get_type = mock.MagicMock(return_value=bool) 339 | 340 | argument = models.argument.Argument(logger, {}) 341 | argument.get_value_from_file(file, "foo-group", argument_element) 342 | 343 | file.has_option.assert_called_once() 344 | argument.get_type.assert_called() 345 | file.get.assert_not_called() 346 | file.getint.assert_not_called() 347 | file.getboolean.assert_called_once() 348 | 349 | def test_get_value_returns_value_from_cli(self): 350 | logger = mock.MagicMock() 351 | 352 | argument_element = mock.MagicMock() 353 | argument_element.dest = "server_queues_discovery" 354 | argument_element.type = bool 355 | 356 | models.argument.Argument.load_defaults = mock.MagicMock() 357 | models.argument.Argument.load_file = mock.MagicMock() 358 | models.argument.Argument.get_value_from_file = mock.MagicMock() 359 | 360 | argument = models.argument.Argument(logger, self.construct_argparse_parsed_arguments()) 361 | value = argument.get_value("server", argument_element) 362 | 363 | argument.get_value_from_file.assert_not_called() 364 | self.assertEquals(False, value) 365 | 366 | def test_get_value_returns_value_from_config_file_when_no_cli_argument_given(self): 367 | logger = mock.MagicMock() 368 | 369 | defaults_content = mock.MagicMock() 370 | config_file_content = mock.MagicMock() 371 | 372 | argument_element = mock.MagicMock() 373 | argument_element.dest = "server_queues_discovery" 374 | argument_element.type = bool 375 | 376 | models.argument.Argument.load_defaults = mock.MagicMock(return_value=defaults_content) 377 | models.argument.Argument.load_file = mock.MagicMock(return_value=config_file_content) 378 | models.argument.Argument.get_value_from_file = mock.MagicMock() 379 | 380 | argument = models.argument.Argument(logger, {}) 381 | value = argument.get_value("server", argument_element) 382 | 383 | argument.get_value_from_file.assert_called_once_with(config_file_content, "server", argument_element) 384 | 385 | def test_get_value_returns_value_from_defaults_file_when_no_cli_argument_and_config_file_argument_does_not_exist(self): 386 | logger = mock.MagicMock() 387 | 388 | defaults_content = mock.MagicMock() 389 | config_file_content = mock.MagicMock() 390 | 391 | argument_element = mock.MagicMock() 392 | argument_element.dest = "server_queues_discovery" 393 | argument_element.type = bool 394 | 395 | models.argument.Argument.load_defaults = mock.MagicMock(return_value=defaults_content) 396 | models.argument.Argument.load_file = mock.MagicMock(return_value=config_file_content) 397 | models.argument.Argument.get_value_from_file = mock.MagicMock() 398 | models.argument.Argument.get_value_from_file.side_effect = [None, True] 399 | 400 | argument = models.argument.Argument(logger, {}) 401 | value = argument.get_value("server", argument_element) 402 | 403 | argument.get_value_from_file.assert_any_call(config_file_content, "server", argument_element) 404 | argument.get_value_from_file.assert_any_call(defaults_content, "server", argument_element) 405 | 406 | def test_get_value_returns_none_when_group_not_found_in_config_file(self): 407 | logger = mock.MagicMock() 408 | 409 | file = mock.MagicMock() 410 | file.has_option = mock.MagicMock(return_value=False) 411 | 412 | argument_element = mock.MagicMock() 413 | argument_element.dest = "server_host" 414 | argument_element.type = str 415 | 416 | models.argument.Argument.load_defaults = mock.MagicMock() 417 | models.argument.Argument.load_file = mock.MagicMock() 418 | models.argument.Argument.get_type = mock.MagicMock() 419 | models.argument.Argument.get_value_from_file = mock.MagicMock(return_value=None) 420 | 421 | argument = models.argument.Argument(logger, {}) 422 | value = argument.get_value("foo-group", argument_element) 423 | 424 | self.assertEquals(None, value) 425 | self.assertEquals(2, argument.get_value_from_file.call_count) 426 | 427 | @staticmethod 428 | def construct_argparse_parsed_arguments(): 429 | return { 430 | "config_file": None, 431 | "server_scheme": "http", 432 | "server_host": "foo-host", 433 | "server_port": 1, 434 | "server_host_alias": "bar-host", 435 | "server_username": "user", 436 | "server_password": "pass", 437 | "server_vhost": "foo", 438 | "server_queue": "foo", 439 | "server_queues": ["foo"], 440 | "server_queues_discovery": False, 441 | "conditions_consumers_connected": 1, 442 | "conditions_open_connections": 1, 443 | "conditions_nodes_running": 1, 444 | "conditions_node_memory_used": 1, 445 | "conditions_ready_queue_size": 0, 446 | "conditions_unack_queue_size": 0, 447 | "conditions_total_queue_size": 0, 448 | "conditions_queue_consumers_connected": 0, 449 | "email_to": ["foo@foobar.com"], 450 | "email_from": "bar@foobar.com", 451 | "email_subject": "foo %s %s", 452 | "email_server": "mail.foobar.com", 453 | "email_password": "", 454 | "email_ssl": False, 455 | "slack_url": "http://foo.com", 456 | "slack_channel": "channel", 457 | "slack_username": "username", 458 | "telegram_bot_id": "foo_bot", 459 | "telegram_channel": "foo_channel" 460 | } 461 | 462 | 463 | if __name__ == "__main__": 464 | unittest.main() 465 | -------------------------------------------------------------------------------- /rabbitmqalert/tests/test_apiclient.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf8 -*- 3 | 4 | import json 5 | import mock 6 | import unittest 7 | import urllib2 8 | 9 | from rabbitmqalert import apiclient 10 | 11 | 12 | class ApiClientTestCase(unittest.TestCase): 13 | 14 | def setUp(self): 15 | apiclient.json_real = apiclient.json 16 | apiclient.urllib2_real = apiclient.urllib2 17 | 18 | def tearDown(self): 19 | apiclient.json = apiclient.json_real 20 | apiclient.urllib2 = apiclient.urllib2_real 21 | 22 | def test_get_queue_returns_result_when_no_error(self): 23 | logger = mock.MagicMock() 24 | arguments = self.construct_arguments() 25 | 26 | client = apiclient.ApiClient(logger, arguments) 27 | client.send_request = mock.MagicMock(return_value=self.construct_response_queue()) 28 | response = client.get_queue() 29 | 30 | uri = "/api/queues/%s/%s" % (arguments["server_vhost"], arguments["server_queue"]) 31 | client.send_request.assert_called_once_with(uri) 32 | self.assertEquals(self.construct_response_queue(), response) 33 | 34 | def test_get_queue_returns_none_when_error(self): 35 | logger = mock.MagicMock() 36 | arguments = self.construct_arguments() 37 | 38 | client = apiclient.ApiClient(logger, arguments) 39 | client.send_request = mock.MagicMock(return_value=None) 40 | apiclient.urllib2.build_opener.open = mock.MagicMock(side_effect=urllib2.HTTPError) 41 | response = client.get_queue() 42 | 43 | uri = "/api/queues/%s/%s" % (arguments["server_vhost"], arguments["server_queue"]) 44 | client.send_request.assert_called_once_with(uri) 45 | self.assertEquals(None, response) 46 | 47 | def test_get_queues_returns_queues_when_exist(self): 48 | logger = mock.MagicMock() 49 | arguments = self.construct_arguments() 50 | 51 | client = apiclient.ApiClient(logger, arguments) 52 | client.send_request = mock.MagicMock(return_value=self.construct_response_queues()) 53 | queues = client.get_queues() 54 | 55 | logger.info.assert_called_once() 56 | logger.error.assert_not_called() 57 | client.send_request.assert_called_once() 58 | self.assertEquals(["foo", "bar"], queues) 59 | 60 | def test_get_queues_returns_empty_list_when_no_queues_exist(self): 61 | logger = mock.MagicMock() 62 | arguments = self.construct_arguments() 63 | 64 | client = apiclient.ApiClient(logger, arguments) 65 | client.send_request = mock.MagicMock(return_value=self.construct_response_queues_empty()) 66 | queues = client.get_queues() 67 | 68 | logger.info.assert_not_called() 69 | logger.error.assert_called_once() 70 | client.send_request.assert_called_once() 71 | self.assertEquals([], queues) 72 | 73 | def test_get_queues_returns_empty_list_when_error(self): 74 | logger = mock.MagicMock() 75 | arguments = self.construct_arguments() 76 | 77 | client = apiclient.ApiClient(logger, arguments) 78 | client.send_request = mock.MagicMock(return_value=None) 79 | apiclient.urllib2.build_opener.open = mock.MagicMock(side_effect=urllib2.HTTPError) 80 | response = client.get_queues() 81 | 82 | client.send_request.assert_called_once() 83 | self.assertEquals([], response) 84 | 85 | def test_get_connections_returns_result_when_no_error(self): 86 | logger = mock.MagicMock() 87 | arguments = self.construct_arguments() 88 | 89 | client = apiclient.ApiClient(logger, arguments) 90 | client.send_request = mock.MagicMock(return_value=self.construct_response_connections()) 91 | response = client.get_connections() 92 | 93 | client.send_request.assert_called_once() 94 | self.assertEquals(self.construct_response_connections(), response) 95 | 96 | def test_get_connections_returns_none_when_error(self): 97 | logger = mock.MagicMock() 98 | arguments = self.construct_arguments() 99 | 100 | client = apiclient.ApiClient(logger, arguments) 101 | client.send_request = mock.MagicMock(return_value=None) 102 | apiclient.urllib2.build_opener.open = mock.MagicMock(side_effect=urllib2.HTTPError) 103 | response = client.get_connections() 104 | 105 | client.send_request.assert_called_once() 106 | self.assertEquals(None, response) 107 | 108 | def test_get_consumers_returns_result_when_no_error(self): 109 | logger = mock.MagicMock() 110 | arguments = self.construct_arguments() 111 | 112 | client = apiclient.ApiClient(logger, arguments) 113 | client.send_request = mock.MagicMock(return_value=self.construct_response_consumers()) 114 | response = client.get_consumers() 115 | 116 | client.send_request.assert_called_once() 117 | self.assertEquals(self.construct_response_consumers(), response) 118 | 119 | def test_get_consumers_returns_none_when_error(self): 120 | logger = mock.MagicMock() 121 | arguments = self.construct_arguments() 122 | 123 | client = apiclient.ApiClient(logger, arguments) 124 | client.send_request = mock.MagicMock(return_value=None) 125 | apiclient.urllib2.build_opener.open = mock.MagicMock(side_effect=urllib2.HTTPError) 126 | response = client.get_consumers() 127 | 128 | client.send_request.assert_called_once() 129 | self.assertEquals(None, response) 130 | 131 | def test_get_nodes_returns_result_when_no_error(self): 132 | logger = mock.MagicMock() 133 | arguments = self.construct_arguments() 134 | 135 | client = apiclient.ApiClient(logger, arguments) 136 | client.send_request = mock.MagicMock(return_value=self.construct_response_nodes()) 137 | response = client.get_nodes() 138 | 139 | client.send_request.assert_called_once() 140 | self.assertEquals(self.construct_response_nodes(), response) 141 | 142 | def test_get_nodes_returns_none_when_error(self): 143 | logger = mock.MagicMock() 144 | arguments = self.construct_arguments() 145 | 146 | client = apiclient.ApiClient(logger, arguments) 147 | client.send_request = mock.MagicMock(return_value=None) 148 | apiclient.urllib2.build_opener.open = mock.MagicMock(side_effect=urllib2.HTTPError) 149 | response = client.get_nodes() 150 | 151 | client.send_request.assert_called_once() 152 | self.assertEquals(None, response) 153 | 154 | def test_send_request_returns_result_when_no_error(self): 155 | logger = mock.MagicMock() 156 | arguments = self.construct_arguments() 157 | 158 | uri = "/api/nodes" 159 | url = "%s://%s:%s%s" % (arguments["server_scheme"], arguments["server_host"], arguments["server_port"], uri) 160 | 161 | client = apiclient.ApiClient(logger, arguments) 162 | apiclient.urllib2.HTTPPasswordMgrWithDefaultRealm = mock.MagicMock() 163 | apiclient.urllib2.build_opener = mock.MagicMock() 164 | apiclient.json.loads = mock.MagicMock(return_value=self.construct_response_nodes()) 165 | response = client.send_request(uri) 166 | 167 | apiclient.urllib2.HTTPPasswordMgrWithDefaultRealm().add_password.assert_called_once_with(None, url, arguments["server_username"], arguments["server_password"]) 168 | apiclient.urllib2.build_opener(mock.MagicMock()).open.assert_called_with(url) 169 | apiclient.json.loads.assert_called_once() 170 | self.assertEqual(self.construct_response_nodes(), response) 171 | logger.error.assert_not_called() 172 | 173 | def test_send_request_returns_none_when_error(self): 174 | logger = mock.MagicMock() 175 | arguments = self.construct_arguments() 176 | 177 | uri = "/api/nodes" 178 | url = "%s://%s:%s%s" % (arguments["server_scheme"], arguments["server_host"], arguments["server_port"], uri) 179 | 180 | client = apiclient.ApiClient(logger, arguments) 181 | apiclient.urllib2.build_opener.open = mock.MagicMock(side_effect=urllib2.HTTPError) 182 | apiclient.json.loads = mock.MagicMock(return_value=self.construct_response_nodes()) 183 | response = client.send_request(uri) 184 | 185 | apiclient.json.loads.assert_not_called() 186 | logger.error.assert_called_once() 187 | self.assertEquals(None, response) 188 | 189 | @staticmethod 190 | def construct_arguments(): 191 | arguments = { 192 | "server_scheme": "http", 193 | "server_host": "foo-host", 194 | "server_port": 1, 195 | "server_host_alias": "bar-host", 196 | "server_username": "user", 197 | "server_password": "pass", 198 | "server_vhost": "foo", 199 | "server_queue": "foo", 200 | "server_queues": ["foo"], 201 | "server_queues_discovery": False, 202 | "generic_conditions": { 203 | "conditions_consumers_connected": 1, 204 | "conditions_open_connections": 1, 205 | "conditions_nodes_running": 1, 206 | "conditions_node_memory_used": 1 207 | }, 208 | "conditions": { 209 | "foo": { 210 | "conditions_ready_queue_size": 0, 211 | "conditions_unack_queue_size": 0, 212 | "conditions_total_queue_size": 0, 213 | "conditions_queue_consumers_connected": 0, 214 | } 215 | }, 216 | "email_to": ["foo@foobar.com"], 217 | "email_from": "bar@foobar.com", 218 | "email_subject": "foo %s %s", 219 | "email_server": "mail.foobar.com", 220 | "email_password": "", 221 | "email_ssl": False, 222 | "slack_url": "http://foo.com", 223 | "slack_channel": "channel", 224 | "slack_username": "username", 225 | "telegram_bot_id": "foo_bot", 226 | "telegram_channel": "foo_channel" 227 | } 228 | 229 | return arguments 230 | 231 | @staticmethod 232 | def construct_response_queue(): 233 | return { 234 | "messages_ready": 0, 235 | "messages_unacknowledged": 0, 236 | "messages": 0, 237 | "consumers": 0 238 | } 239 | 240 | @staticmethod 241 | def construct_response_queues(): 242 | return { 243 | "page_count": 1, 244 | "page_size": 300, 245 | "page": 1, 246 | "filtered_count": 2, 247 | "item_count": 2, 248 | "total_count": 2, 249 | "items": [ 250 | { "name": "foo" }, 251 | { "name": "bar" } 252 | ] 253 | } 254 | 255 | @staticmethod 256 | def construct_response_queues_empty(): 257 | return { 258 | "page_count": 1, 259 | "page_size": 300, 260 | "page": 1, 261 | "filtered_count": 0, 262 | "item_count": 0, 263 | "total_count": 0, 264 | "items": [] 265 | } 266 | 267 | @staticmethod 268 | def construct_response_connections(): 269 | return { 270 | "connection_foo": {}, 271 | "connection_bar": {} 272 | } 273 | 274 | @staticmethod 275 | def construct_response_consumers(): 276 | return { 277 | "consumer_foo": {}, 278 | "consumer_bar": {} 279 | } 280 | 281 | @staticmethod 282 | def construct_response_nodes(): 283 | return [ 284 | { "mem_used": 500000 }, 285 | { "mem_used": 500000 } 286 | ] 287 | 288 | 289 | if __name__ == "__main__": 290 | unittest.main() 291 | -------------------------------------------------------------------------------- /rabbitmqalert/tests/test_argumentsparser.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf8 -*- 3 | 4 | from collections import namedtuple 5 | import mock 6 | import unittest 7 | 8 | from rabbitmqalert import argumentsparser 9 | from rabbitmqalert.models import argument 10 | from rabbitmqalert import rabbitmqalert 11 | 12 | 13 | class ArgumentsParserTestCase(unittest.TestCase): 14 | 15 | def setUp(self): 16 | argumentsparser.os_real = argumentsparser.os 17 | argumentsparser.apiclient.ApiClient_real = argumentsparser.apiclient.ApiClient 18 | argumentsparser.argument.Argument_real = argumentsparser.argument.Argument 19 | argumentsparser.argument.Argument.files_have_group_real = argumentsparser.argument.Argument.files_have_group 20 | argumentsparser.argument.ConfigParser.ConfigParser_real = argumentsparser.argument.ConfigParser.ConfigParser 21 | argumentsparser.argument.os_real = argumentsparser.argument.os 22 | 23 | rabbitmqalert.argparse_real = rabbitmqalert.argparse 24 | 25 | def tearDown(self): 26 | argumentsparser.os = argumentsparser.os_real 27 | argumentsparser.apiclient.ApiClient = argumentsparser.apiclient.ApiClient_real 28 | argumentsparser.argument.Argument = argumentsparser.argument.Argument_real 29 | argumentsparser.argument.Argument.files_have_group = argumentsparser.argument.Argument.files_have_group_real 30 | argumentsparser.argument.ConfigParser.ConfigParser = argumentsparser.argument.ConfigParser.ConfigParser_real 31 | argumentsparser.argument.os = argumentsparser.argument.os_real 32 | 33 | rabbitmqalert.argparse = rabbitmqalert.argparse_real 34 | 35 | def test_parse_calls_get_value_for_every_group_argument(self): 36 | logger = mock.MagicMock() 37 | 38 | # setup the argparse argument parser with fake cli arguments 39 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + self.arguments_dict_to_list(self.construct_arguments()) 40 | argparse_parser = rabbitmqalert.setup_arguments() 41 | 42 | argumentsparser.argument.Argument = mock.MagicMock() 43 | argumentsparser.apiclient.ApiClient.get_queues = mock.MagicMock() 44 | 45 | parser = argumentsparser.ArgumentsParser(logger) 46 | parser.validate = mock.MagicMock() 47 | parser.format_conditions = mock.MagicMock() 48 | 49 | parser.parse(argparse_parser) 50 | 51 | # count the number of arguments 52 | group_arguments_count = 0 53 | for group in argparse_parser._action_groups: 54 | for group_argument in group._group_actions: 55 | group_arguments_count += 1 56 | 57 | argumentsparser.argument.Argument.get_value.call_count == group_arguments_count 58 | 59 | def test_parse_returns_discovered_queues_when_argument_set(self): 60 | logger = mock.MagicMock() 61 | 62 | # edit the cli arguments to look like queues discovery was requested 63 | arguments = self.construct_arguments() 64 | arguments["--queues-discovery"] = True 65 | arguments_list = self.arguments_dict_to_list(arguments) 66 | 67 | # setup the argparse argument parser with fake cli arguments 68 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 69 | argparse_parser = rabbitmqalert.setup_arguments() 70 | 71 | argumentsparser.apiclient.ApiClient.get_queues = mock.MagicMock(return_value=["foo-queue", "bar-queue"]) 72 | 73 | parser = argumentsparser.ArgumentsParser(logger) 74 | parser.validate = mock.MagicMock() 75 | parser.format_conditions = mock.MagicMock() 76 | 77 | parser.parse(argparse_parser) 78 | 79 | # create a copy of the arguments in the form they would look like after parsing them (behore calling validate) 80 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 81 | arguments_dict["server_queues"] = ["foo-queue", "bar-queue"] 82 | arguments_dict["email_to"] = arguments_dict["email_to"].split(",") 83 | arguments_dict["help"] = None 84 | arguments_dict["queue_conditions"] = dict() 85 | arguments_dict["email_ssl"] = False 86 | 87 | argumentsparser.apiclient.ApiClient.get_queues.assert_called_once() 88 | parser.validate.assert_called_once_with(arguments_dict) 89 | 90 | def test_parse_returns_emails_split(self): 91 | logger = mock.MagicMock() 92 | 93 | # edit the cli arguments to look like multiple email address were given 94 | arguments = self.construct_arguments() 95 | arguments["--email-to"] = "foo-email-to,bar-email-to" 96 | arguments_list = self.arguments_dict_to_list(arguments) 97 | 98 | # setup the argparse argument parser with fake cli arguments 99 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 100 | argparse_parser = rabbitmqalert.setup_arguments() 101 | 102 | parser = argumentsparser.ArgumentsParser(logger) 103 | parser.validate = mock.MagicMock() 104 | parser.format_conditions = mock.MagicMock() 105 | 106 | parser.parse(argparse_parser) 107 | 108 | # create a copy of the arguments in the form they would look like after parsing them (behore calling validate) 109 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 110 | arguments_dict["server_queues"] = arguments_dict["server_queues"].split(",") 111 | arguments_dict["email_to"] = arguments_dict["email_to"].split(",") 112 | arguments_dict["help"] = None 113 | arguments_dict["queue_conditions"] = dict() 114 | arguments_dict["server_queues_discovery"] = False 115 | arguments_dict["email_ssl"] = False 116 | 117 | parser.validate.assert_called_once_with(arguments_dict) 118 | 119 | def test_parse_skips_queue_conditions_when_non_standard_groups_do_not_exist(self): 120 | logger = mock.MagicMock() 121 | argumentsparser.argument.Argument.files_have_group = mock.MagicMock(return_value=False) 122 | 123 | arguments_list = self.arguments_dict_to_list(self.construct_arguments()) 124 | 125 | # setup the argparse argument parser with fake cli arguments 126 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 127 | argparse_parser = rabbitmqalert.setup_arguments() 128 | 129 | parser = argumentsparser.ArgumentsParser(logger) 130 | parser.validate = mock.MagicMock() 131 | parser.format_conditions = mock.MagicMock() 132 | 133 | parser.parse(argparse_parser) 134 | 135 | # create a copy of the arguments in the form they would look like after parsing them (behore calling validate) 136 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 137 | arguments_dict["server_queues"] = arguments_dict["server_queues"].split(",") 138 | arguments_dict["email_to"] = arguments_dict["email_to"].split(",") 139 | arguments_dict["help"] = None 140 | arguments_dict["queue_conditions"] = dict() 141 | arguments_dict["server_queues_discovery"] = False 142 | arguments_dict["email_ssl"] = False 143 | 144 | # checks for non-standard group for queue specific conditions 145 | argumentsparser.argument.Argument.files_have_group.assert_called_once_with("Conditions:foo-queue") 146 | # validate called with empty queue_conditions 147 | parser.validate.assert_called_once_with(arguments_dict) 148 | 149 | def test_parse_constructs_queue_conditions_when_non_standard_groups_exist(self): 150 | logger = mock.MagicMock() 151 | 152 | argumentsparser.argument.Argument.files_have_group = mock.MagicMock(return_value=True) 153 | 154 | arguments_dict = self.construct_arguments() 155 | arguments_dict["--queues"] = "foo-queue,bar-queue" 156 | arguments_list = self.arguments_dict_to_list(arguments_dict) 157 | 158 | # setup the argparse argument parser with fake cli arguments 159 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 160 | argparse_parser = rabbitmqalert.setup_arguments() 161 | 162 | parser = argumentsparser.ArgumentsParser(logger) 163 | parser.validate = mock.MagicMock() 164 | parser.format_conditions = mock.MagicMock() 165 | 166 | parser.parse(argparse_parser) 167 | 168 | # create a copy of the arguments in the form they would look like after parsing them (behore calling validate) 169 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 170 | arguments_dict["server_queues"] = arguments_dict["server_queues"].split(",") 171 | arguments_dict["email_to"] = arguments_dict["email_to"].split(",") 172 | arguments_dict["help"] = None 173 | arguments_dict["server_queues_discovery"] = False 174 | arguments_dict["email_ssl"] = False 175 | arguments_dict["queue_conditions"] = { 176 | "foo-queue": { 177 | "conditions_total_queue_size": 40, 178 | "conditions_ready_queue_size": 20, 179 | "conditions_queue_consumers_connected": 52, 180 | "conditions_unack_queue_size": 30 181 | }, 182 | "bar-queue": { 183 | "conditions_total_queue_size": 40, 184 | "conditions_ready_queue_size": 20, 185 | "conditions_queue_consumers_connected": 52, 186 | "conditions_unack_queue_size": 30 187 | } 188 | } 189 | 190 | # checks for non-standard group for queue specific conditions 191 | argumentsparser.argument.Argument.files_have_group.assert_any_call("Conditions:foo-queue") 192 | argumentsparser.argument.Argument.files_have_group.assert_any_call("Conditions:bar-queue") 193 | # validate called with empty queue_conditions 194 | parser.validate.assert_called_once_with(arguments_dict) 195 | 196 | def test_parse_returns_merged_arguments_and_conditions(self): 197 | logger = mock.MagicMock() 198 | 199 | arguments = self.construct_arguments() 200 | arguments_list = self.arguments_dict_to_list(arguments) 201 | 202 | # setup the argparse argument parser with fake cli arguments 203 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 204 | argparse_parser = rabbitmqalert.setup_arguments() 205 | 206 | parser = argumentsparser.ArgumentsParser(logger) 207 | result = parser.parse(argparse_parser) 208 | 209 | # create a copy of the arguments in the form they would look like after parsing them 210 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 211 | arguments_dict["server_queues"] = arguments_dict["server_queues"].split(",") 212 | arguments_dict["email_to"] = arguments_dict["email_to"].split(",") 213 | arguments_dict["help"] = None 214 | arguments_dict["server_queues_discovery"] = False 215 | arguments_dict["email_ssl"] = False 216 | arguments_dict["queue_conditions"] = dict() 217 | 218 | arguments_dict = dict(arguments_dict.items() + parser.format_conditions(arguments_dict).items()) 219 | 220 | self.assertEquals(arguments_dict, result) 221 | 222 | def test_validate_exits_when_required_argument_is_missing(self): 223 | logger = mock.MagicMock() 224 | 225 | arguments = self.construct_arguments() 226 | del arguments["--host"] 227 | arguments_list = self.arguments_dict_to_list(arguments) 228 | 229 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 230 | argparse_parser = rabbitmqalert.setup_arguments() 231 | 232 | parser = argumentsparser.ArgumentsParser(logger) 233 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 234 | 235 | with self.assertRaises(SystemExit) as ex: 236 | parser.validate(arguments_dict) 237 | 238 | self.assertEqual(ex.exception.code, 1) 239 | logger.error.assert_called_once_with("Required argument not defined: host") 240 | 241 | def test_validate_does_not_exit_when_all_required_arguments_exist(self): 242 | logger = mock.MagicMock() 243 | 244 | arguments = self.construct_arguments() 245 | arguments_list = self.arguments_dict_to_list(arguments) 246 | 247 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 248 | argparse_parser = rabbitmqalert.setup_arguments() 249 | 250 | parser = argumentsparser.ArgumentsParser(logger) 251 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 252 | 253 | parser.validate(arguments_dict) 254 | 255 | logger.error.assert_not_called() 256 | 257 | def test_format_conditions_returns_generic_and_queue_conditions(self): 258 | logger = mock.MagicMock() 259 | 260 | arguments = self.construct_arguments() 261 | arguments_list = self.arguments_dict_to_list(arguments) 262 | 263 | # setup the argparse argument parser with fake cli arguments 264 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 265 | argparse_parser = rabbitmqalert.setup_arguments() 266 | 267 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 268 | arguments_dict["server_queues"] = ["foo-queue"] 269 | arguments_dict["email_to"] = arguments_dict["email_to"].split(",") 270 | arguments_dict["help"] = None 271 | arguments_dict["queue_conditions"] = dict() 272 | 273 | parser = argumentsparser.ArgumentsParser(logger) 274 | results = parser.format_conditions(arguments_dict) 275 | 276 | self.assertTrue("conditions" in results) 277 | self.assertTrue("generic_conditions" in results) 278 | # generic conditions 279 | self.assertEquals(arguments_dict["conditions_consumers_connected"], results["generic_conditions"]["conditions_consumers_connected"]) 280 | self.assertEquals(arguments_dict["conditions_open_connections"], results["generic_conditions"]["conditions_open_connections"]) 281 | self.assertEquals(arguments_dict["conditions_nodes_running"], results["generic_conditions"]["conditions_nodes_running"]) 282 | self.assertEquals(arguments_dict["conditions_node_memory_used"], results["generic_conditions"]["conditions_node_memory_used"]) 283 | # queue conditions 284 | self.assertEquals(arguments_dict["conditions_ready_queue_size"], results["conditions"]["foo-queue"]["conditions_ready_queue_size"]) 285 | self.assertEquals(arguments_dict["conditions_unack_queue_size"], results["conditions"]["foo-queue"]["conditions_unack_queue_size"]) 286 | self.assertEquals(arguments_dict["conditions_total_queue_size"], results["conditions"]["foo-queue"]["conditions_total_queue_size"]) 287 | self.assertEquals(arguments_dict["conditions_queue_consumers_connected"], results["conditions"]["foo-queue"]["conditions_queue_consumers_connected"]) 288 | 289 | def test_format_conditions_returns_queue_conditions_when_exist(self): 290 | logger = mock.MagicMock() 291 | 292 | arguments = self.construct_arguments() 293 | arguments_list = self.arguments_dict_to_list(arguments) 294 | 295 | # setup the argparse argument parser with fake cli arguments 296 | rabbitmqalert.argparse._sys.argv = ['rabbitmqalert.py'] + arguments_list 297 | argparse_parser = rabbitmqalert.setup_arguments() 298 | 299 | arguments_dict = vars(argparse_parser.parse_args(arguments_list)) 300 | arguments_dict["server_queues"] = ["foo-queue", "bar-queue"] 301 | arguments_dict["email_to"] = arguments_dict["email_to"].split(",") 302 | arguments_dict["help"] = None 303 | arguments_dict["queue_conditions"] = { 304 | "foo-queue": { 305 | "conditions_total_queue_size": 40, 306 | "conditions_ready_queue_size": 20, 307 | "conditions_queue_consumers_connected": 52, 308 | "conditions_unack_queue_size": 30 309 | }, 310 | "bar-queue": { 311 | "conditions_total_queue_size": 40, 312 | "conditions_ready_queue_size": 20, 313 | "conditions_queue_consumers_connected": 52, 314 | "conditions_unack_queue_size": 30 315 | } 316 | } 317 | 318 | parser = argumentsparser.ArgumentsParser(logger) 319 | results = parser.format_conditions(arguments_dict) 320 | 321 | self.assertTrue("conditions" in results) 322 | self.assertTrue("generic_conditions" in results) 323 | # generic conditions 324 | self.assertEquals(arguments_dict["conditions_consumers_connected"], results["generic_conditions"]["conditions_consumers_connected"]) 325 | self.assertEquals(arguments_dict["conditions_open_connections"], results["generic_conditions"]["conditions_open_connections"]) 326 | self.assertEquals(arguments_dict["conditions_nodes_running"], results["generic_conditions"]["conditions_nodes_running"]) 327 | self.assertEquals(arguments_dict["conditions_node_memory_used"], results["generic_conditions"]["conditions_node_memory_used"]) 328 | # queue conditions 329 | self.assertTrue(arguments_dict["conditions_ready_queue_size"], results["conditions"]["foo-queue"]["conditions_ready_queue_size"]) 330 | self.assertEquals(arguments_dict["conditions_unack_queue_size"], results["conditions"]["foo-queue"]["conditions_unack_queue_size"]) 331 | self.assertEquals(arguments_dict["conditions_total_queue_size"], results["conditions"]["foo-queue"]["conditions_total_queue_size"]) 332 | self.assertEquals(arguments_dict["conditions_queue_consumers_connected"], results["conditions"]["foo-queue"]["conditions_queue_consumers_connected"]) 333 | self.assertTrue(arguments_dict["conditions_ready_queue_size"], results["conditions"]["bar-queue"]["conditions_ready_queue_size"]) 334 | self.assertEquals(arguments_dict["conditions_unack_queue_size"], results["conditions"]["bar-queue"]["conditions_unack_queue_size"]) 335 | self.assertEquals(arguments_dict["conditions_total_queue_size"], results["conditions"]["bar-queue"]["conditions_total_queue_size"]) 336 | self.assertEquals(arguments_dict["conditions_queue_consumers_connected"], results["conditions"]["bar-queue"]["conditions_queue_consumers_connected"]) 337 | 338 | @staticmethod 339 | def construct_arguments(): 340 | return { 341 | "--config-file": None, 342 | "--scheme": "foo-scheme", 343 | "--host": "foo-host", 344 | "--port": "foo-port", 345 | "--host-alias": "bar-host", 346 | "--username": "foo-username", 347 | "--password": "foo-password", 348 | "--vhost": "foo-vhost", 349 | "--queues": "foo-queue", 350 | "--queues-discovery": False, 351 | "--check-rate": "10", 352 | "--ready-queue-size": "20", 353 | "--unacknowledged-queue-size": "30", 354 | "--total-queue-size": "40", 355 | "--queue-consumers-connected": "52", 356 | "--consumers-connected": "50", 357 | "--open-connections": "51", 358 | "--nodes-running": "60", 359 | "--node-memory-used": "70", 360 | "--email-to": "foo-email-to", 361 | "--email-from": "foo-email-from", 362 | "--email-subject": "foo-email-subject", 363 | "--email-server": "foo-email-server", 364 | "--email-password": "foo-email-password", 365 | "--email-ssl": False, 366 | "--slack-url": "foo-slack-url", 367 | "--slack-channel": "foo-slack-channel", 368 | "--slack-username": "foo-slack-username", 369 | "--telegram-bot-id": "foo-telegram-bot-id", 370 | "--telegram-channel": "foo-telegram-channel" 371 | } 372 | 373 | @staticmethod 374 | def arguments_dict_to_list(dict): 375 | result = [] 376 | 377 | for key, value in dict.iteritems(): 378 | if value not in [False, None]: 379 | result.append(key) 380 | # arguments of store_true or store_false action must not have a value 381 | result.append(value) if type(value) is not bool else None 382 | 383 | return result 384 | 385 | 386 | if __name__ == "__main__": 387 | unittest.main() 388 | -------------------------------------------------------------------------------- /rabbitmqalert/tests/test_conditionchecker.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf8 -*- 3 | 4 | import argparse 5 | import mock 6 | import unittest 7 | 8 | from rabbitmqalert import apiclient 9 | from rabbitmqalert import argumentsparser 10 | from rabbitmqalert import conditionchecker 11 | 12 | 13 | class ConditionCheckerTestCase(unittest.TestCase): 14 | 15 | def test_check_queue_conditions_exits_when_no_response_from_client(self): 16 | logger = mock.MagicMock() 17 | client = mock.MagicMock() 18 | notifier = mock.MagicMock() 19 | client.get_queue = mock.MagicMock(return_value=None) 20 | 21 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 22 | rmqa.check_queue_conditions(self.construct_arguments()) 23 | 24 | notifier.send_notification.assert_not_called() 25 | 26 | def test_check_queue_conditions_not_sends_notification_when_no_conditions_met(self): 27 | logger = mock.MagicMock() 28 | client = mock.MagicMock() 29 | notifier = mock.MagicMock() 30 | client.get_queue = mock.MagicMock(return_value=self.construct_response_queue()) 31 | 32 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 33 | rmqa.check_queue_conditions(self.construct_arguments()) 34 | 35 | notifier.send_notification.assert_not_called() 36 | 37 | def test_check_queue_conditions_sends_notification_when_exceeding_messages_ready(self): 38 | response = self.construct_response_queue() 39 | response["messages_ready"] = 2 40 | 41 | logger = mock.MagicMock() 42 | client = mock.MagicMock() 43 | notifier = mock.MagicMock() 44 | client.get_queue = mock.MagicMock(return_value=response) 45 | 46 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 47 | rmqa.check_queue_conditions(self.construct_arguments()) 48 | 49 | notifier.send_notification.assert_called_once() 50 | 51 | def test_check_queue_conditions_sends_notification_when_exceeding_messages_unacknowledged(self): 52 | response = self.construct_response_queue() 53 | response["messages_unacknowledged"] = 2 54 | 55 | logger = mock.MagicMock() 56 | client = mock.MagicMock() 57 | notifier = mock.MagicMock() 58 | client.get_queue = mock.MagicMock(return_value=response) 59 | 60 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 61 | rmqa.check_queue_conditions(self.construct_arguments()) 62 | 63 | notifier.send_notification.assert_called_once() 64 | 65 | def test_check_queue_conditions_sends_notification_when_exceeding_messages(self): 66 | response = self.construct_response_queue() 67 | response["messages"] = 2 68 | 69 | logger = mock.MagicMock() 70 | client = mock.MagicMock() 71 | notifier = mock.MagicMock() 72 | client.get_queue = mock.MagicMock(return_value=response) 73 | 74 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 75 | rmqa.check_queue_conditions(self.construct_arguments()) 76 | 77 | notifier.send_notification.assert_called_once() 78 | 79 | def test_check_queue_conditions_sends_notification_when_beneath_consumers(self): 80 | response = self.construct_response_queue() 81 | response["consumers"] = 0 82 | arguments = self.construct_arguments() 83 | arguments["generic_conditions"]["conditions_queue_consumers_connected"] = 1 84 | arguments["conditions"]["foo"]["conditions_queue_consumers_connected"] = 1 85 | 86 | logger = mock.MagicMock() 87 | client = mock.MagicMock() 88 | notifier = mock.MagicMock() 89 | client.get_queue = mock.MagicMock(return_value=response) 90 | 91 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 92 | rmqa.check_queue_conditions(arguments) 93 | 94 | notifier.send_notification.assert_called_once() 95 | 96 | def test_check_consumer_conditions_exits_when_no_response_from_client(self): 97 | logger = mock.MagicMock() 98 | client = mock.MagicMock() 99 | notifier = mock.MagicMock() 100 | client.get_consumers = mock.MagicMock(return_value=None) 101 | 102 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 103 | rmqa.check_consumer_conditions(self.construct_arguments()) 104 | 105 | notifier.send_notification.assert_not_called() 106 | 107 | def test_check_consumer_conditions_not_sends_notification_when_exceeding_consumers_connected(self): 108 | logger = mock.MagicMock() 109 | client = mock.MagicMock() 110 | notifier = mock.MagicMock() 111 | client.get_consumers = mock.MagicMock(return_value=self.construct_response_consumers()) 112 | 113 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 114 | rmqa.check_consumer_conditions(self.construct_arguments()) 115 | 116 | notifier.send_notification.assert_not_called() 117 | 118 | def test_check_consumer_conditions_sends_notification_when_beneath_consumers(self): 119 | response = {} 120 | 121 | logger = mock.MagicMock() 122 | client = mock.MagicMock() 123 | notifier = mock.MagicMock() 124 | client.get_consumers = mock.MagicMock(return_value=response) 125 | 126 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 127 | rmqa.check_consumer_conditions(self.construct_arguments()) 128 | 129 | notifier.send_notification.assert_called_once() 130 | 131 | def test_check_connection_conditions_exits_when_no_response_from_client(self): 132 | logger = mock.MagicMock() 133 | client = mock.MagicMock() 134 | notifier = mock.MagicMock() 135 | client.get_connections = mock.MagicMock(return_value=None) 136 | 137 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 138 | rmqa.check_connection_conditions(self.construct_arguments()) 139 | 140 | notifier.send_notification.assert_not_called() 141 | 142 | def test_check_connection_conditions_not_sends_notification_when_exceeding_connections_connected(self): 143 | logger = mock.MagicMock() 144 | client = mock.MagicMock() 145 | notifier = mock.MagicMock() 146 | client.get_connections = mock.MagicMock(return_value=self.construct_response_connections()) 147 | 148 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 149 | rmqa.check_connection_conditions(self.construct_arguments()) 150 | 151 | notifier.send_notification.assert_not_called() 152 | 153 | def test_check_connection_conditions_sends_notification_when_beneath_connections(self): 154 | response = {} 155 | 156 | logger = mock.MagicMock() 157 | client = mock.MagicMock() 158 | notifier = mock.MagicMock() 159 | client.get_connections = mock.MagicMock(return_value=response) 160 | 161 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 162 | rmqa.check_connection_conditions(self.construct_arguments()) 163 | 164 | notifier.send_notification.assert_called_once() 165 | 166 | def test_check_node_conditions_exits_when_no_response_from_client(self): 167 | logger = mock.MagicMock() 168 | client = mock.MagicMock() 169 | notifier = mock.MagicMock() 170 | client.get_nodes = mock.MagicMock(return_value=None) 171 | 172 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 173 | rmqa.check_node_conditions(self.construct_arguments()) 174 | 175 | notifier.send_notification.assert_not_called() 176 | 177 | def test_check_node_conditions_not_sends_notification_when_no_conditions_met(self): 178 | logger = mock.MagicMock() 179 | client = mock.MagicMock() 180 | notifier = mock.MagicMock() 181 | client.get_nodes = mock.MagicMock(return_value=self.construct_response_nodes()) 182 | 183 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 184 | rmqa.check_node_conditions(self.construct_arguments()) 185 | 186 | notifier.send_notification.assert_not_called() 187 | 188 | def test_check_node_conditions_sends_notification_when_beneath_nodes_running(self): 189 | response = {} 190 | 191 | logger = mock.MagicMock() 192 | client = mock.MagicMock() 193 | notifier = mock.MagicMock() 194 | client.get_nodes = mock.MagicMock(return_value=response) 195 | 196 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 197 | rmqa.check_node_conditions(self.construct_arguments()) 198 | 199 | notifier.send_notification.assert_called_once() 200 | 201 | def test_check_node_conditions_sends_notification_when_exceeding_mem_used(self): 202 | response = self.construct_response_nodes() 203 | response[0]["mem_used"] = 2 * pow(1024, 2) 204 | 205 | logger = mock.MagicMock() 206 | client = mock.MagicMock() 207 | notifier = mock.MagicMock() 208 | client.get_nodes = mock.MagicMock(return_value=response) 209 | 210 | rmqa = conditionchecker.ConditionChecker(logger, client, notifier) 211 | rmqa.check_node_conditions(self.construct_arguments()) 212 | 213 | notifier.send_notification.assert_called_once() 214 | 215 | @staticmethod 216 | def construct_arguments(): 217 | arguments = { 218 | "config_file": None, 219 | "server_scheme": "http", 220 | "server_host": "foo-host", 221 | "server_port": 1, 222 | "server_host_alias": "bar-host", 223 | "server_vhost": "foo", 224 | "server_queue": "foo", 225 | "server_queues": ["foo"], 226 | "server_queues_discovery": False, 227 | "server_check_rate": 1, 228 | "generic_conditions": { 229 | "conditions_consumers_connected": 1, 230 | "conditions_open_connections": 1, 231 | "conditions_nodes_running": 1, 232 | "conditions_node_memory_used": 1 233 | }, 234 | "conditions": { 235 | "foo": { 236 | "conditions_ready_queue_size": 0, 237 | "conditions_unack_queue_size": 0, 238 | "conditions_total_queue_size": 0, 239 | "conditions_queue_consumers_connected": 0, 240 | } 241 | }, 242 | "email_to": ["foo@foobar.com"], 243 | "email_from": "bar@foobar.com", 244 | "email_subject": "foo %s %s", 245 | "email_server": "mail.foobar.com", 246 | "email_password": "", 247 | "email_ssl": False, 248 | "slack_url": "http://foo.com", 249 | "slack_channel": "channel", 250 | "slack_username": "username", 251 | "telegram_bot_id": "foo_bot", 252 | "telegram_channel": "foo_channel" 253 | } 254 | 255 | return arguments 256 | 257 | @staticmethod 258 | def construct_response_queue(): 259 | return { 260 | "messages_ready": 0, 261 | "messages_unacknowledged": 0, 262 | "messages": 0, 263 | "consumers": 0 264 | } 265 | 266 | @staticmethod 267 | def construct_response_consumers(): 268 | return { 269 | "consumer_foo": {}, 270 | "consumer_bar": {} 271 | } 272 | 273 | @staticmethod 274 | def construct_response_connections(): 275 | return { 276 | "connection_foo": {}, 277 | "connection_bar": {} 278 | } 279 | 280 | @staticmethod 281 | def construct_response_nodes(): 282 | return [ 283 | { "mem_used": 500000 }, 284 | { "mem_used": 500000 } 285 | ] 286 | 287 | 288 | if __name__ == "__main__": 289 | unittest.main() 290 | -------------------------------------------------------------------------------- /rabbitmqalert/tests/test_logger.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf8 -*- 3 | 4 | import logging 5 | import mock 6 | import unittest 7 | 8 | from rabbitmqalert import logger 9 | 10 | 11 | class LoggerTestCase(unittest.TestCase): 12 | 13 | def setUp(self): 14 | logger.handlers_real = logger.handlers 15 | 16 | def tearDown(self): 17 | logger.handlers = logger.handlers_real 18 | 19 | def test_get_logger_returns_logger(self): 20 | logger.handlers = mock.MagicMock() 21 | 22 | the_logger = logger.Logger() 23 | self.assertIsInstance(the_logger.get_logger(), type(logging.getLogger())) 24 | 25 | def test_get_logger_returns_same_logger_instance_after_consecutive_calls(self): 26 | logger.handlers = mock.MagicMock() 27 | 28 | # same instances from same logger instance 29 | the_logger = logger.Logger() 30 | self.assertIs(the_logger.get_logger(), the_logger.get_logger()) 31 | 32 | # same instances from different logger instances 33 | the_logger2 = logger.Logger() 34 | self.assertIs(the_logger.get_logger(), the_logger2.get_logger()) 35 | 36 | 37 | if __name__ == "__main__": 38 | unittest.main() 39 | -------------------------------------------------------------------------------- /rabbitmqalert/tests/test_notifier.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf8 -*- 3 | 4 | import mock 5 | import unittest 6 | 7 | from rabbitmqalert import rabbitmqalert 8 | from rabbitmqalert import notifier 9 | 10 | 11 | class NotifierTestCase(unittest.TestCase): 12 | 13 | def setUp(self): 14 | notifier.urllib2_real = notifier.urllib2 15 | 16 | def tearDown(self): 17 | notifier.urllib2 = notifier.urllib2_real 18 | 19 | def test_send_notification_does_not_send_email_when_email_to_not_set(self): 20 | logger = mock.MagicMock() 21 | client = mock.MagicMock() 22 | 23 | arguments = self.construct_arguments() 24 | arguments["email_to"] = None 25 | 26 | notifier_object = notifier.Notifier(logger, arguments) 27 | 28 | notifier.smtplib = mock.MagicMock() 29 | notifier.urllib2 = mock.MagicMock() 30 | notifier_object.send_notification("") 31 | 32 | notifier.smtplib.SMTP().sendmail.assert_not_called() 33 | 34 | def test_send_notification_sends_email_when_email_to_is_set(self): 35 | logger = mock.MagicMock() 36 | client = mock.MagicMock() 37 | 38 | notifier_object = notifier.Notifier(logger, self.construct_arguments()) 39 | 40 | notifier.smtplib = mock.MagicMock() 41 | notifier.urllib2 = mock.MagicMock() 42 | notifier_object.send_notification("") 43 | 44 | notifier.smtplib.SMTP().sendmail.assert_called_once() 45 | 46 | def test_send_notification_calls_login_when_email_password_is_set(self): 47 | logger = mock.MagicMock() 48 | client = mock.MagicMock() 49 | 50 | arguments = self.construct_arguments() 51 | arguments["email_password"] = "password" 52 | 53 | notifier_object = notifier.Notifier(logger, arguments) 54 | 55 | notifier.smtplib = mock.MagicMock() 56 | notifier.urllib2 = mock.MagicMock() 57 | notifier_object.send_notification("") 58 | 59 | notifier.smtplib.SMTP().login.assert_called_once() 60 | 61 | def test_send_notification_does_not_call_login_when_email_password_not_set(self): 62 | logger = mock.MagicMock() 63 | client = mock.MagicMock() 64 | 65 | notifier_object = notifier.Notifier(logger, self.construct_arguments()) 66 | 67 | notifier.smtplib = mock.MagicMock() 68 | notifier.urllib2 = mock.MagicMock() 69 | notifier_object.send_notification("") 70 | 71 | notifier.smtplib.SMTP().login.assert_not_called() 72 | 73 | def test_send_notification_logs_in_with_email_login_when_set(self): 74 | logger = mock.MagicMock() 75 | client = mock.MagicMock() 76 | 77 | arguments = self.construct_arguments() 78 | arguments["email_login"] = "email_login@foobar.com" 79 | arguments["email_password"] = "password" 80 | 81 | notifier_object = notifier.Notifier(logger, arguments) 82 | 83 | notifier.smtplib = mock.MagicMock() 84 | notifier.urllib2 = mock.MagicMock() 85 | notifier_object.send_notification("") 86 | 87 | notifier.smtplib.SMTP().login.assert_called_once_with("email_login@foobar.com", "password") 88 | 89 | def test_send_notification_logs_in_with_email_from_when_email_login_not_set(self): 90 | logger = mock.MagicMock() 91 | client = mock.MagicMock() 92 | 93 | arguments = self.construct_arguments() 94 | arguments["email_from"] = "test@foobar.com" 95 | arguments["email_login"] = "" 96 | arguments["email_password"] = "password" 97 | 98 | notifier_object = notifier.Notifier(logger, arguments) 99 | 100 | notifier.smtplib = mock.MagicMock() 101 | notifier.urllib2 = mock.MagicMock() 102 | notifier_object.send_notification("") 103 | 104 | notifier.smtplib.SMTP().login.assert_called_once_with("test@foobar.com", "password") 105 | 106 | def test_send_notification_sends_email_with_ssl_when_email_ssl_is_set(self): 107 | logger = mock.MagicMock() 108 | client = mock.MagicMock() 109 | 110 | arguments = self.construct_arguments() 111 | arguments["email_ssl"] = True 112 | 113 | notifier_object = notifier.Notifier(logger, arguments) 114 | 115 | notifier.smtplib = mock.MagicMock() 116 | notifier.urllib2 = mock.MagicMock() 117 | notifier_object.send_notification("") 118 | 119 | notifier.smtplib.SMTP_SSL().sendmail.assert_called_once() 120 | 121 | def test_send_notification_does_not_send_email_with_ssl_when_email_ssl_not_set(self): 122 | logger = mock.MagicMock() 123 | client = mock.MagicMock() 124 | 125 | arguments = self.construct_arguments() 126 | arguments["email_ssl"] = False 127 | 128 | notifier_object = notifier.Notifier(logger, arguments) 129 | 130 | notifier.smtplib = mock.MagicMock() 131 | notifier.urllib2 = mock.MagicMock() 132 | notifier_object.send_notification("") 133 | 134 | notifier.smtplib.SMTP_SSL().sendmail.assert_not_called() 135 | 136 | def test_send_notification_sends_to_slack_and_telegram_when_arguments_are_set(self): 137 | logger = mock.MagicMock() 138 | client = mock.MagicMock() 139 | 140 | notifier_object = notifier.Notifier(logger, self.construct_arguments()) 141 | 142 | notifier.smtplib = mock.MagicMock() 143 | notifier.urllib2 = mock.MagicMock() 144 | notifier_object.send_notification("") 145 | 146 | self.assertEquals(2, notifier.urllib2.urlopen.call_count) 147 | 148 | def test_send_notification_does_not_send_to_slack_when_any_argument_not_set(self): 149 | logger = mock.MagicMock() 150 | client = mock.MagicMock() 151 | 152 | arguments = self.construct_arguments() 153 | arguments["slack_url"] = None 154 | 155 | notifier_object = notifier.Notifier(logger, arguments) 156 | 157 | notifier.smtplib = mock.MagicMock() 158 | notifier.urllib2 = mock.MagicMock() 159 | notifier_object.send_notification("") 160 | 161 | # only telegram is called 162 | notifier.urllib2.urlopen.assert_called_once() 163 | 164 | def test_send_notification_does_not_send_to_telegram_when_any_argument_not_set(self): 165 | logger = mock.MagicMock() 166 | client = mock.MagicMock() 167 | 168 | arguments = self.construct_arguments() 169 | arguments["telegram_bot_id"] = None 170 | 171 | notifier_object = notifier.Notifier(logger, arguments) 172 | 173 | notifier.smtplib = mock.MagicMock() 174 | notifier.urllib2 = mock.MagicMock() 175 | notifier_object.send_notification("") 176 | 177 | # only slack is called 178 | notifier.urllib2.urlopen.assert_called_once() 179 | 180 | def test_send_notification_uses_host_alias_when_set(self): 181 | logger = mock.MagicMock() 182 | client = mock.MagicMock() 183 | 184 | arguments = self.construct_arguments() 185 | arguments["server_host_alias"] = "bar-host" 186 | 187 | notifier_object = notifier.Notifier(logger, arguments) 188 | 189 | notifier.smtplib = mock.MagicMock() 190 | notifier.urllib2 = mock.MagicMock() 191 | notifier_object.send_notification("") 192 | 193 | notifier.smtplib.SMTP().sendmail.assert_called_once_with("bar@foobar.com", ["foo@foobar.com"], "From: bar@foobar.com\nSubject: foo bar-host foo\n\nbar-host - ") 194 | 195 | def test_send_notification_does_not_use_host_alias_when_not_set(self): 196 | logger = mock.MagicMock() 197 | client = mock.MagicMock() 198 | 199 | arguments = self.construct_arguments() 200 | arguments["server_host_alias"] = None 201 | 202 | notifier_object = notifier.Notifier(logger, arguments) 203 | 204 | notifier.smtplib = mock.MagicMock() 205 | notifier.urllib2 = mock.MagicMock() 206 | notifier_object.send_notification("") 207 | 208 | notifier.smtplib.SMTP().sendmail.assert_called_once_with("bar@foobar.com", ["foo@foobar.com"], "From: bar@foobar.com\nSubject: foo foo-host foo\n\nfoo-host - ") 209 | 210 | def test_send_notification_logs_info_when_email_is_sent(self): 211 | logger = mock.MagicMock() 212 | client = mock.MagicMock() 213 | 214 | arguments = self.construct_arguments() 215 | arguments["slack_url"] = None 216 | arguments["telegram_bot_id"] = None 217 | 218 | notifier_object = notifier.Notifier(logger, arguments) 219 | 220 | notifier.smtplib = mock.MagicMock() 221 | notifier.urllib2 = mock.MagicMock() 222 | notifier_object.send_notification("") 223 | 224 | logger.info.assert_called_once() 225 | 226 | def test_send_notification_does_not_log_info_when_email_not_sent(self): 227 | logger = mock.MagicMock() 228 | client = mock.MagicMock() 229 | 230 | arguments = self.construct_arguments() 231 | arguments["email_to"] = None 232 | arguments["slack_url"] = None 233 | arguments["telegram_bot_id"] = None 234 | 235 | notifier_object = notifier.Notifier(logger, arguments) 236 | 237 | notifier.smtplib = mock.MagicMock() 238 | notifier.urllib2 = mock.MagicMock() 239 | notifier_object.send_notification("") 240 | 241 | logger.info.assert_not_called() 242 | 243 | def test_send_notification_logs_info_when_sending_to_slack(self): 244 | logger = mock.MagicMock() 245 | client = mock.MagicMock() 246 | 247 | arguments = self.construct_arguments() 248 | arguments["email_to"] = None 249 | arguments["telegram_bot_id"] = None 250 | 251 | notifier_object = notifier.Notifier(logger, arguments) 252 | 253 | notifier.smtplib = mock.MagicMock() 254 | notifier.urllib2 = mock.MagicMock() 255 | notifier_object.send_notification("") 256 | 257 | logger.info.assert_called_once() 258 | 259 | def test_send_notification_does_not_log_info_when_not_sending_to_slack(self): 260 | logger = mock.MagicMock() 261 | client = mock.MagicMock() 262 | 263 | arguments = self.construct_arguments() 264 | arguments["email_to"] = None 265 | arguments["slack_url"] = None 266 | arguments["telegram_bot_id"] = None 267 | 268 | notifier_object = notifier.Notifier(logger, arguments) 269 | 270 | notifier.smtplib = mock.MagicMock() 271 | notifier.urllib2 = mock.MagicMock() 272 | notifier_object.send_notification("") 273 | 274 | logger.info.assert_not_called() 275 | 276 | def test_send_notification_logs_info_when_sending_to_telegram(self): 277 | logger = mock.MagicMock() 278 | client = mock.MagicMock() 279 | 280 | arguments = self.construct_arguments() 281 | arguments["email_to"] = None 282 | arguments["slack_url"] = None 283 | 284 | notifier_object = notifier.Notifier(logger, arguments) 285 | 286 | notifier.smtplib = mock.MagicMock() 287 | notifier.urllib2 = mock.MagicMock() 288 | notifier_object.send_notification("") 289 | 290 | logger.info.assert_called_once() 291 | 292 | def test_send_notification_does_not_log_info_when_not_sending_to_telegram(self): 293 | logger = mock.MagicMock() 294 | client = mock.MagicMock() 295 | 296 | arguments = self.construct_arguments() 297 | arguments["email_to"] = None 298 | arguments["slack_url"] = None 299 | arguments["telegram_bot_id"] = None 300 | 301 | notifier_object = notifier.Notifier(logger, arguments) 302 | 303 | notifier.smtplib = mock.MagicMock() 304 | notifier.urllib2 = mock.MagicMock() 305 | notifier_object.send_notification("") 306 | 307 | logger.info.assert_not_called() 308 | 309 | @staticmethod 310 | def construct_arguments(): 311 | arguments = { 312 | "config_file": None, 313 | "server_scheme": "http", 314 | "server_host": "foo-host", 315 | "server_port": 1, 316 | "server_host_alias": "bar-host", 317 | "server_vhost": "foo", 318 | "server_queue": "foo", 319 | "server_queues": ["foo"], 320 | "server_queues_discovery": False, 321 | "server_check_rate": 1, 322 | "generic_conditions": { 323 | "conditions_consumers_connected": 1, 324 | "conditions_open_connections": 1, 325 | "conditions_nodes_running": 1, 326 | "conditions_node_memory_used": 1 327 | }, 328 | "conditions": { 329 | "foo": { 330 | "conditions_ready_queue_size": 0, 331 | "conditions_unack_queue_size": 0, 332 | "conditions_total_queue_size": 0, 333 | "conditions_queue_consumers_connected": 0, 334 | } 335 | }, 336 | "email_to": ["foo@foobar.com"], 337 | "email_from": "bar@foobar.com", 338 | "email_login": "bar@foobar.com", 339 | "email_subject": "foo %s %s", 340 | "email_server": "mail.foobar.com", 341 | "email_password": "", 342 | "email_ssl": False, 343 | "slack_url": "http://foo.com", 344 | "slack_channel": "channel", 345 | "slack_username": "username", 346 | "telegram_bot_id": "foo_bot", 347 | "telegram_channel": "foo_channel" 348 | } 349 | 350 | return arguments 351 | 352 | 353 | if __name__ == "__main__": 354 | unittest.main() 355 | -------------------------------------------------------------------------------- /rabbitmqalert/tests/test_rabbitmqalert.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | # -*- coding: utf8 -*- 3 | 4 | import argparse 5 | import mock 6 | import unittest 7 | 8 | from rabbitmqalert import apiclient 9 | from rabbitmqalert import argumentsparser 10 | from rabbitmqalert import rabbitmqalert 11 | 12 | 13 | class RabbitMQAlertTestCase(unittest.TestCase): 14 | 15 | def setUp(self): 16 | rabbitmqalert.argumentsparser_real = rabbitmqalert.argumentsparser 17 | rabbitmqalert.conditionchecker_real = rabbitmqalert.conditionchecker 18 | rabbitmqalert.logger_real = rabbitmqalert.logger 19 | 20 | def tearDown(self): 21 | rabbitmqalert.argumentsparser = rabbitmqalert.argumentsparser_real 22 | rabbitmqalert.conditionchecker = rabbitmqalert.conditionchecker_real 23 | rabbitmqalert.logger = rabbitmqalert.logger_real 24 | 25 | def test_setup_arguments_returns_parser(self): 26 | parser = rabbitmqalert.setup_arguments() 27 | self.assertIsInstance(parser, argparse.ArgumentParser) 28 | 29 | def test_main_runs_check_queue_conditions_when_ready_queue_size_in_queue_conditions(self): 30 | client = mock.MagicMock() 31 | notifier = mock.MagicMock() 32 | rabbitmqalert.logger = mock.MagicMock() 33 | rabbitmqalert.conditionchecker = mock.MagicMock() 34 | # throw exception to escape the "while True" loop 35 | rabbitmqalert.time.sleep = mock.MagicMock(side_effect=ValueError) 36 | 37 | arguments = self.construct_arguments() 38 | arguments["conditions"]["foo"]["conditions_ready_queue_size"] = 1 39 | rabbitmqalert.argumentsparser = mock.MagicMock() 40 | rabbitmqalert.argumentsparser.ArgumentsParser(mock.MagicMock()).parse = mock.MagicMock(return_value=arguments) 41 | 42 | try: 43 | rabbitmqalert.main() 44 | except ValueError: 45 | pass 46 | 47 | rabbitmqalert.conditionchecker.ConditionChecker().check_queue_conditions.assert_called() 48 | 49 | def test_main_runs_check_queue_conditions_when_unack_queue_size_in_queue_conditions(self): 50 | client = mock.MagicMock() 51 | notifier = mock.MagicMock() 52 | rabbitmqalert.logger = mock.MagicMock() 53 | rabbitmqalert.conditionchecker = mock.MagicMock() 54 | # throw exception to escape the "while True" loop 55 | rabbitmqalert.time.sleep = mock.MagicMock(side_effect=ValueError) 56 | 57 | arguments = self.construct_arguments() 58 | arguments["conditions"]["foo"]["conditions_unack_queue_size"] = 1 59 | rabbitmqalert.argumentsparser = mock.MagicMock() 60 | rabbitmqalert.argumentsparser.ArgumentsParser(mock.MagicMock()).parse = mock.MagicMock(return_value=arguments) 61 | 62 | try: 63 | rabbitmqalert.main() 64 | except ValueError: 65 | pass 66 | 67 | rabbitmqalert.conditionchecker.ConditionChecker().check_queue_conditions.assert_called() 68 | 69 | def test_main_runs_check_queue_conditions_when_total_queue_size_in_queue_conditions(self): 70 | client = mock.MagicMock() 71 | notifier = mock.MagicMock() 72 | rabbitmqalert.logger = mock.MagicMock() 73 | rabbitmqalert.conditionchecker = mock.MagicMock() 74 | # throw exception to escape the "while True" loop 75 | rabbitmqalert.time.sleep = mock.MagicMock(side_effect=ValueError) 76 | 77 | arguments = self.construct_arguments() 78 | arguments["conditions"]["foo"]["conditions_total_queue_size"] = 1 79 | rabbitmqalert.argumentsparser = mock.MagicMock() 80 | rabbitmqalert.argumentsparser.ArgumentsParser(mock.MagicMock()).parse = mock.MagicMock(return_value=arguments) 81 | 82 | try: 83 | rabbitmqalert.main() 84 | except ValueError: 85 | pass 86 | 87 | rabbitmqalert.conditionchecker.ConditionChecker().check_queue_conditions.assert_called() 88 | 89 | def test_main_runs_check_queue_conditions_when_queue_consumers_connected_in_queue_conditions(self): 90 | client = mock.MagicMock() 91 | notifier = mock.MagicMock() 92 | rabbitmqalert.logger = mock.MagicMock() 93 | rabbitmqalert.conditionchecker = mock.MagicMock() 94 | # throw exception to escape the "while True" loop 95 | rabbitmqalert.time.sleep = mock.MagicMock(side_effect=ValueError) 96 | 97 | arguments = self.construct_arguments() 98 | arguments["conditions"]["foo"]["conditions_queue_consumers_connected"] = 1 99 | rabbitmqalert.argumentsparser = mock.MagicMock() 100 | rabbitmqalert.argumentsparser.ArgumentsParser(mock.MagicMock()).parse = mock.MagicMock(return_value=arguments) 101 | 102 | try: 103 | rabbitmqalert.main() 104 | except ValueError: 105 | pass 106 | 107 | rabbitmqalert.conditionchecker.ConditionChecker().check_queue_conditions.assert_called() 108 | 109 | def test_main_runs_check_node_conditions_when_nodes_running_in_generic_conditions(self): 110 | client = mock.MagicMock() 111 | notifier = mock.MagicMock() 112 | rabbitmqalert.logger = mock.MagicMock() 113 | rabbitmqalert.conditionchecker = mock.MagicMock() 114 | # throw exception to escape the "while True" loop 115 | rabbitmqalert.time.sleep = mock.MagicMock(side_effect=ValueError) 116 | 117 | arguments = self.construct_arguments() 118 | arguments["generic_conditions"]["conditions_nodes_running"] = 1 119 | rabbitmqalert.argumentsparser = mock.MagicMock() 120 | rabbitmqalert.argumentsparser.ArgumentsParser(mock.MagicMock()).parse = mock.MagicMock(return_value=arguments) 121 | 122 | try: 123 | rabbitmqalert.main() 124 | except ValueError: 125 | pass 126 | 127 | rabbitmqalert.conditionchecker.ConditionChecker().check_node_conditions.assert_called() 128 | 129 | def test_main_runs_check_node_conditions_when_node_memory_used_in_generic_conditions(self): 130 | client = mock.MagicMock() 131 | notifier = mock.MagicMock() 132 | rabbitmqalert.logger = mock.MagicMock() 133 | rabbitmqalert.conditionchecker = mock.MagicMock() 134 | # throw exception to escape the "while True" loop 135 | rabbitmqalert.time.sleep = mock.MagicMock(side_effect=ValueError) 136 | 137 | arguments = self.construct_arguments() 138 | arguments["generic_conditions"]["conditions_node_memory_used"] = 1 139 | rabbitmqalert.argumentsparser = mock.MagicMock() 140 | rabbitmqalert.argumentsparser.ArgumentsParser(mock.MagicMock()).parse = mock.MagicMock(return_value=arguments) 141 | 142 | try: 143 | rabbitmqalert.main() 144 | except ValueError: 145 | pass 146 | 147 | rabbitmqalert.conditionchecker.ConditionChecker().check_node_conditions.assert_called() 148 | 149 | def test_main_runs_check_connection_conditions_when_open_connections_in_generic_conditions(self): 150 | client = mock.MagicMock() 151 | notifier = mock.MagicMock() 152 | rabbitmqalert.logger = mock.MagicMock() 153 | rabbitmqalert.conditionchecker = mock.MagicMock() 154 | # throw exception to escape the "while True" loop 155 | rabbitmqalert.time.sleep = mock.MagicMock(side_effect=ValueError) 156 | 157 | arguments = self.construct_arguments() 158 | arguments["generic_conditions"]["conditions_open_connections"] = 1 159 | rabbitmqalert.argumentsparser = mock.MagicMock() 160 | rabbitmqalert.argumentsparser.ArgumentsParser(mock.MagicMock()).parse = mock.MagicMock(return_value=arguments) 161 | 162 | try: 163 | rabbitmqalert.main() 164 | except ValueError: 165 | pass 166 | 167 | rabbitmqalert.conditionchecker.ConditionChecker().check_connection_conditions.assert_called() 168 | 169 | def test_main_runs_check_consumer_conditions_when_consumers_connected_in_generic_conditions(self): 170 | client = mock.MagicMock() 171 | notifier = mock.MagicMock() 172 | rabbitmqalert.logger = mock.MagicMock() 173 | rabbitmqalert.conditionchecker = mock.MagicMock() 174 | # throw exception to escape the "while True" loop 175 | rabbitmqalert.time.sleep = mock.MagicMock(side_effect=ValueError) 176 | 177 | arguments = self.construct_arguments() 178 | arguments["generic_conditions"]["conditions_consumers_connected"] = 1 179 | rabbitmqalert.argumentsparser = mock.MagicMock() 180 | rabbitmqalert.argumentsparser.ArgumentsParser(mock.MagicMock()).parse = mock.MagicMock(return_value=arguments) 181 | 182 | try: 183 | rabbitmqalert.main() 184 | except ValueError: 185 | pass 186 | 187 | rabbitmqalert.conditionchecker.ConditionChecker().check_consumer_conditions.assert_called() 188 | 189 | @staticmethod 190 | def construct_arguments(): 191 | arguments = { 192 | "config_file": None, 193 | "server_scheme": "http", 194 | "server_host": "foo-host", 195 | "server_port": 1, 196 | "server_host_alias": "bar-host", 197 | "server_vhost": "foo", 198 | "server_queue": "foo", 199 | "server_queues": ["foo"], 200 | "server_queues_discovery": False, 201 | "server_check_rate": 1, 202 | "generic_conditions": { 203 | "conditions_consumers_connected": 1, 204 | "conditions_open_connections": 1, 205 | "conditions_nodes_running": 1, 206 | "conditions_node_memory_used": 1 207 | }, 208 | "conditions": { 209 | "foo": { 210 | "conditions_ready_queue_size": 0, 211 | "conditions_unack_queue_size": 0, 212 | "conditions_total_queue_size": 0, 213 | "conditions_queue_consumers_connected": 0, 214 | } 215 | }, 216 | "email_to": ["foo@foobar.com"], 217 | "email_from": "bar@foobar.com", 218 | "email_subject": "foo %s %s", 219 | "email_server": "mail.foobar.com", 220 | "email_password": "", 221 | "email_ssl": False, 222 | "slack_url": "http://foo.com", 223 | "slack_channel": "channel", 224 | "slack_username": "username", 225 | "telegram_bot_id": "foo_bot", 226 | "telegram_channel": "foo_channel" 227 | } 228 | 229 | return arguments 230 | 231 | 232 | if __name__ == "__main__": 233 | unittest.main() 234 | -------------------------------------------------------------------------------- /requirements_dev: -------------------------------------------------------------------------------- 1 | mock==2.0.0 2 | twine==1.9.1 3 | docutils==0.14 4 | collective.checkdocs==0.2 5 | pylint==2.3.0 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/python2 2 | 3 | from setuptools import setup, find_packages 4 | from os import path 5 | 6 | # remember to push a new tag after changing this! 7 | VERSION = "1.9.0" 8 | 9 | DIST_CONFIG_PATH = "rabbitmqalert/config" 10 | 11 | DATA_FILES = [ 12 | ("/etc/rabbitmq-alert/", [DIST_CONFIG_PATH + "/config.ini.example"]), 13 | ("/var/log/rabbitmq-alert/", []) 14 | ] 15 | 16 | 17 | def generate_readme(): 18 | return open("README.rst").read() 19 | 20 | 21 | def generate_data_files(): 22 | if path.isdir("/etc/systemd/system/"): 23 | DATA_FILES.append(("/etc/systemd/system/", [DIST_CONFIG_PATH + "/service/rabbitmq-alert.service"])) 24 | if path.isdir("/etc/init.d/"): 25 | DATA_FILES.append(("/etc/init.d/", [DIST_CONFIG_PATH + "/service/rabbitmq-alert"])) 26 | 27 | return DATA_FILES 28 | 29 | 30 | setup( 31 | name="rabbitmq-alert", 32 | version=VERSION, 33 | long_description=generate_readme(), 34 | packages=find_packages(exclude=["*tests*"]), 35 | description="Send notifications when predefined conditions are met", 36 | author="Germano Fronza (gfronza), Kostas Milonas (mylk), velika12, Robert Kopaczewski (23doors), Ivan Timeev (TeslA1402), Anderson Diego Kulpa Fachini (anderson-fachini), Dawid Deregowski (Venomen)", 37 | author_email="germano.inf@gmail.com", 38 | url="https://github.com/gfronza/rabbitmq-alert", 39 | download_url="https://github.com/gfronza/rabbitmq-alert/tarball/" + VERSION, 40 | keywords=["rabbitmq", "alert", "monitor"], 41 | classifiers=[], 42 | entry_points={ 43 | "console_scripts": [ 44 | "rabbitmq-alert = rabbitmqalert:rabbitmqalert.main" 45 | ] 46 | }, 47 | data_files=generate_data_files() 48 | ) 49 | --------------------------------------------------------------------------------