From 1c5059cd5443579de1397e32307dc494bc4c2a45 Mon Sep 17 00:00:00 2001 From: Roman Mogylatov Date: Sat, 8 Aug 2020 14:22:23 -0400 Subject: [PATCH] Finish the tutorial --- docs/tutorials/asyncio.rst | 133 +++++++++++++++++++--- docs/tutorials/asyncio_images/class_2.png | Bin 0 -> 31136 bytes 2 files changed, 120 insertions(+), 13 deletions(-) create mode 100644 docs/tutorials/asyncio_images/class_2.png diff --git a/docs/tutorials/asyncio.rst b/docs/tutorials/asyncio.rst index a5a9605e..3d7e830d 100644 --- a/docs/tutorials/asyncio.rst +++ b/docs/tutorials/asyncio.rst @@ -100,7 +100,7 @@ Prepare the environment In this section we are going to prepare the environment for running our daemon. -First, we need to specify the project requirements. We will use next packages: +First we need to specify the project requirements. We will use next packages: - ``dependency-injector`` - the dependency injection framework - ``aiohttp`` - the web framework (we need only http client) @@ -485,10 +485,24 @@ Everything works properly. Dispatcher starts up and exits because there are no m By the end of this section we have the application skeleton ready. In the next section will will add first monitoring task. -HTTP monitor ------------- +Example.com monitor +------------------- -Create ``http.py`` module in the ``monitoringdaemon`` package: +In this section we will add the monitoring task that will check the availability of the +`http://example.com `_. + +We will start from the extending of our class model with a new type of the monitoring check, the +``HttpMonitor``. + +The ``HttpMonitor`` is a subclass of the ``Monitor``. We will implement the ``check()`` method that +will send the HTTP request to the specified URL. The http request sending will be delegated to +the ``HttpClient``. + +.. image:: asyncio_images/class_2.png + +First, we need to create the ``HttpClient``. + +Create ``http.py`` in the ``monitoringdaemon`` package: .. code-block:: bash :emphasize-lines: 7 @@ -522,6 +536,8 @@ and put next into it: async with session.request(method, url) as response: return response +Now we need to add the ``HttpClient`` to the container. + Edit ``containers.py``: .. code-block:: python @@ -558,12 +574,12 @@ Edit ``containers.py``: ), ) -Add the http monitor. +Now we're ready to add the ``HttpMonitor``. We will add it to the ``monitors`` module. Edit ``monitors.py``: .. code-block:: python - :emphasize-lines: 4-5,7,24-58 + :emphasize-lines: 4-5,7,20-54 """Monitors module.""" @@ -620,6 +636,12 @@ Edit ``monitors.py``: round(time_took, 3) ) +We have everything ready to add the `http://example.com `_ monitoring check. +We make two changes in the container: + +- Add the factory provider ``example_monitor``. +- Inject the ``example_monitor`` into the dispatcher. + Edit ``containers.py``: .. code-block:: python @@ -662,6 +684,9 @@ Edit ``containers.py``: ), ) +Provider ``example_monitor`` has a dependency on the configuration options. Let's define these +options. + Edit ``config.yml``: .. code-block:: yaml @@ -679,22 +704,44 @@ Edit ``config.yml``: timeout: 5 check_every: 5 + +All set. Start the daemon to check that all works. + Run in the terminal: .. code-block:: bash docker-compose up -You will see: +You should see: .. code-block:: bash - [INFO] [Dispatcher]: Starting up - [INFO] [HttpMonitor]: GET http://example.com, response code: 200, content length: 648, request took: 0.083 seconds - [INFO] [HttpMonitor]: GET http://example.com, response code: 200, content length: 648, request took: 0.062 seconds + Starting monitoring-daemon-tutorial_monitor_1 ... done + Attaching to monitoring-daemon-tutorial_monitor_1 + monitor_1 | [2020-08-08 17:06:41,965] [INFO] [Dispatcher]: Starting up + monitor_1 | [2020-08-08 17:06:42,033] [INFO] [HttpMonitor]: Check + monitor_1 | GET http://example.com + monitor_1 | response code: 200 + monitor_1 | content length: 648 + monitor_1 | request took: 0.067 seconds + monitor_1 | + monitor_1 | [2020-08-08 17:06:47,040] [INFO] [HttpMonitor]: Check + monitor_1 | GET http://example.com + monitor_1 | response code: 200 + monitor_1 | content length: 648 + monitor_1 | request took: 0.073 seconds -More monitors -------------- +Our daemon can monitor `http://example.com `_ availability. + +Let's add the monitor for the `http://httpbin.org `_. + +Httpbin.org monitor +------------------- + +Adding of the monitor for the `httpbin.org`_ will be much easier because we have all the +components ready. We just need to create a new provider in the container and update the +configuration. Edit ``containers.py``: @@ -768,10 +815,59 @@ Edit ``config.yml``: timeout: 5 check_every: 5 +Let's start the daemon and check the logs. + +Run in the terminal: + +.. code-block:: bash + + docker-compose up + +You should see: + +.. code-block:: bash + + Starting monitoring-daemon-tutorial_monitor_1 ... done + Attaching to monitoring-daemon-tutorial_monitor_1 + monitor_1 | [2020-08-08 18:09:08,540] [INFO] [Dispatcher]: Starting up + monitor_1 | [2020-08-08 18:09:08,618] [INFO] [HttpMonitor]: Check + monitor_1 | GET http://example.com + monitor_1 | response code: 200 + monitor_1 | content length: 648 + monitor_1 | request took: 0.077 seconds + monitor_1 | + monitor_1 | [2020-08-08 18:09:08,722] [INFO] [HttpMonitor]: Check + monitor_1 | GET https://httpbin.org/get + monitor_1 | response code: 200 + monitor_1 | content length: 310 + monitor_1 | request took: 0.18 seconds + monitor_1 | + monitor_1 | [2020-08-08 18:09:13,619] [INFO] [HttpMonitor]: Check + monitor_1 | GET http://example.com + monitor_1 | response code: 200 + monitor_1 | content length: 648 + monitor_1 | request took: 0.066 seconds + monitor_1 | + monitor_1 | [2020-08-08 18:09:13,681] [INFO] [HttpMonitor]: Check + monitor_1 | GET https://httpbin.org/get + monitor_1 | response code: 200 + monitor_1 | content length: 310 + monitor_1 | request took: 0.126 seconds + +The functional part is done. Daemon monitors `http://example.com `_ and +`https://httpbin.org `_. + +In the next section we will add some tests. + Tests ----- -Create ``tests.py`` module in the ``monitoringdaemon`` package: +It would be nice to add some tests. Let's do it. + +We will use `pytest `_ and +`coverage `_. + +Create ``tests.py`` in the ``monitoringdaemon`` package: .. code-block:: bash :emphasize-lines: 9 @@ -793,6 +889,7 @@ Create ``tests.py`` module in the ``monitoringdaemon`` package: and put next into it: .. code-block:: python + :emphasize-lines: 54,70-71 """Tests module.""" @@ -904,6 +1001,16 @@ You should see: ---------------------------------------------------- TOTAL 129 18 86% +.. note:: + + Take a look at the highlights in the ``tests.py``. + + In the ``test_example_monitor`` it emphasizes the overriding of the ``HttpClient``. The real + HTTP calls are mocked. + + In the ``test_dispatcher`` we override both monitors with the mocks. + + Conclusion ---------- diff --git a/docs/tutorials/asyncio_images/class_2.png b/docs/tutorials/asyncio_images/class_2.png new file mode 100644 index 0000000000000000000000000000000000000000..68759cc36ff3a035ae4315ffe90e71b1f58b58e1 GIT binary patch literal 31136 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
%}Gl&{e@;f3B`2GC8zR4h7N4Drp_DGU8 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 Rp>Qa)7JCou$v5-(;qQiqt%3jm literal 0 HcmV?d00001