{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Processing results\n", "\n", "In this tutorial we are going to see how we can process the results of running tasks.\n", "\n", "Let's start with some code similar to what we have seen already:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import logging\n", "\n", "from nornir import InitNornir\n", "from nornir.core.task import Task, Result\n", "\n", "# instantiate the nr object\n", "nr = InitNornir(config_file=\"config.yaml\")\n", "# let's filter it down to simplify the output\n", "cmh = nr.filter(site=\"cmh\", type=\"host\")\n", "\n", "def count(task: Task, number: int) -> Result:\n", " return Result(\n", " host=task.host,\n", " result=f\"{[n for n in range(0, number)]}\"\n", " )\n", "\n", "def say(task: Task, text: str) -> Result:\n", " if task.host.name == \"host2.cmh\":\n", " raise Exception(\"I can't say anything right now\")\n", " return Result(\n", " host=task.host,\n", " result=f\"{task.host.name} says {text}\"\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nothing new so far, we have hardcoded an error though in the `say` task, this will help us illustrate some concepts. Now let's create a variant of the grouped task we saw in an earlier tutorial:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "def greet_and_count(task: Task, number: int):\n", " task.run(\n", " name=\"Greeting is the polite thing to do\",\n", " severity_level=logging.DEBUG,\n", " task=say,\n", " text=\"hi!\",\n", " )\n", " \n", " task.run(\n", " name=\"Counting beans\",\n", " task=count,\n", " number=number,\n", " )\n", " task.run(\n", " name=\"We should say bye too\",\n", " severity_level=logging.DEBUG, \n", " task=say,\n", " text=\"bye!\",\n", " )\n", "\n", " # let's inform if we counted even or odd times\n", " even_or_odds = \"even\" if number % 2 == 1 else \"odd\"\n", " return Result(\n", " host=task.host,\n", " result=f\"{task.host} counted {even_or_odds} times!\",\n", " )" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "It is pretty much the same grouped task as before with the difference we are passing `severity_level=logging.DEBUG` to the first and last subtasks. We will see what that means later on.\n", "\n", "Now, let's call the task group so we can start inspecting the result object:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "result = cmh.run(\n", " task=greet_and_count,\n", " number=5,\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## The easy way\n", "\n", "Most of the time you will just want to provide some feedback on what's going on. For that you can use the [print_result](https://nornir.tech/nornir_utils/html/tutorials/function_print_result.html) function that comes with the package `nornir_utils`:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m\u001b[36mgreet_and_count*****************************************************************\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[34m* host1.cmh ** changed : False *************************************************\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32mvvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO\u001b[0m\n", "\u001b[0mhost1.cmh counted even times!\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32m---- Counting beans ** changed : False ----------------------------------------- INFO\u001b[0m\n", "\u001b[0m[0, 1, 2, 3, 4]\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32m^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[34m* host2.cmh ** changed : False *************************************************\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[31mvvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ERROR\u001b[0m\n", "\u001b[0mSubtask: Greeting is the polite thing to do (failed)\n", "\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[31m---- Greeting is the polite thing to do ** changed : False --------------------- ERROR\u001b[0m\n", "\u001b[0mTraceback (most recent call last):\n", " File \"/nornir/core/task.py\", line 98, in start\n", " r = self.task(self, **self.params)\n", " File \"/tmp/ipykernel_8460/3588580986.py\", line 19, in say\n", " raise Exception(\"I can't say anything right now\")\n", "Exception: I can't say anything right now\n", "\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[31m^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n", "\u001b[0m" ] } ], "source": [ "from nornir_utils.plugins.functions import print_result\n", "\n", "print_result(result)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You should also be able to print a single host:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m\u001b[32mvvvv host1.cmh: greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO\u001b[0m\n", "\u001b[0mhost1.cmh counted even times!\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32m---- Counting beans ** changed : False ----------------------------------------- INFO\u001b[0m\n", "\u001b[0m[0, 1, 2, 3, 4]\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32m^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n", "\u001b[0m" ] } ], "source": [ "print_result(result[\"host1.cmh\"])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Or even a single task:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m\u001b[32m---- host1.cmh: Counting beans ** changed : False ------------------------------ INFO\u001b[0m\n", "\u001b[0m[0, 1, 2, 3, 4]\u001b[0m\n", "\u001b[0m" ] } ], "source": [ "print_result(result[\"host1.cmh\"][2])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you probably noticed, not all the tasks were printed. This is due to the `severity_level` argument we passed. This let's us flag tasks with any of the logging levels. Then `print_result` is able to follow logging rules to print the results. By default only tasks marked as `INFO` will be printed (this is also the default for the tasks if none is specified).\n", "\n", "A failed task will always have its severity level changed to `ERROR` regardless of the one specified by the user. You can see that in the task `Greeting is the polite thing to do` for `host2.cmh`.\n", "\n", "Now let's tell `print_result` to print tasks marked as `DEBUG`." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\u001b[1m\u001b[36mgreet_and_count*****************************************************************\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[34m* host1.cmh ** changed : False *************************************************\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32mvvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv INFO\u001b[0m\n", "\u001b[0mhost1.cmh counted even times!\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32m---- Greeting is the polite thing to do ** changed : False --------------------- DEBUG\u001b[0m\n", "\u001b[0mhost1.cmh says hi!\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32m---- Counting beans ** changed : False ----------------------------------------- INFO\u001b[0m\n", "\u001b[0m[0, 1, 2, 3, 4]\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32m---- We should say bye too ** changed : False ---------------------------------- DEBUG\u001b[0m\n", "\u001b[0mhost1.cmh says bye!\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[32m^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[34m* host2.cmh ** changed : False *************************************************\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[31mvvvv greet_and_count ** changed : False vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv ERROR\u001b[0m\n", "\u001b[0mSubtask: Greeting is the polite thing to do (failed)\n", "\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[31m---- Greeting is the polite thing to do ** changed : False --------------------- ERROR\u001b[0m\n", "\u001b[0mTraceback (most recent call last):\n", " File \"/nornir/core/task.py\", line 98, in start\n", " r = self.task(self, **self.params)\n", " File \"/tmp/ipykernel_8460/3588580986.py\", line 19, in say\n", " raise Exception(\"I can't say anything right now\")\n", "Exception: I can't say anything right now\n", "\u001b[0m\n", "\u001b[0m\u001b[1m\u001b[31m^^^^ END greet_and_count ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\u001b[0m\n", "\u001b[0m" ] } ], "source": [ "print_result(result, severity_level=logging.DEBUG)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we got all tasks printed. You can also see the severity level at the rightmost column of the output.\n", "\n", "## The programmatic way\n", "\n", "We have hinted at how to deal with result objects already, but let's elaborate on that. To begin with, task groups will return an [AggregatedResult](../api/nornir/core/task.html#nornir.core.task.AggregatedResult). This object is a dict-like object you can use to iterate over or access hosts directly:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "AggregatedResult (greet_and_count): {'host1.cmh': MultiResult: [Result: \"greet_and_count\", Result: \"Greeting is the polite thing to do\", Result: \"Counting beans\", Result: \"We should say bye too\"], 'host2.cmh': MultiResult: [Result: \"greet_and_count\", Result: \"Greeting is the polite thing to do\"]}" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "dict_keys(['host1.cmh', 'host2.cmh'])" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result.keys()" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "MultiResult: [Result: \"greet_and_count\", Result: \"Greeting is the polite thing to do\", Result: \"Counting beans\", Result: \"We should say bye too\"]" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result[\"host1.cmh\"]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You probably noticed that inside each key in [AggregatedResult](../api/nornir/core/task.html#nornir.core.task.AggregatedResult) there is a [MultiResult](../api/nornir/core/task.html#nornir.core.task.MultiResult) object. This object is a list-like object you can use to iterate over or access any [Result](../api/nornir/core/task.html#nornir.core.task.Result) you want:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "Result: \"greet_and_count\"" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "result[\"host1.cmh\"][0]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Both `MultiResult` and `Result` should clearly indicate if there was some error or change in the system:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "changed: \u001b[0m \u001b[0mFalse\u001b[0m\n", "\u001b[0mfailed: \u001b[0m \u001b[0mFalse\u001b[0m\n", "\u001b[0m" ] } ], "source": [ "print(\"changed: \", result[\"host1.cmh\"].changed)\n", "print(\"failed: \", result[\"host1.cmh\"].failed)" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "changed: \u001b[0m \u001b[0mFalse\u001b[0m\n", "\u001b[0mfailed: \u001b[0m \u001b[0mTrue\u001b[0m\n", "\u001b[0m" ] } ], "source": [ "print(\"changed: \", result[\"host2.cmh\"][0].changed)\n", "print(\"failed: \", result[\"host2.cmh\"][0].failed)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 2 }