==================
Grafana datasource
==================

Synopsis
--------

Grafana can interface with many different types of storage backends that
Grafana calls datasources_. Since the term datasources causes significant
confusion by overlapping definitions used in Watcher these **datasources are
called projects instead**. Some examples of supported projects are InfluxDB
or Elasticsearch while others might be more familiar such as Monasca or
Gnocchi. The Grafana datasource provides the functionality to retrieve metrics
from Grafana for different projects. This functionality is achieved by using
the proxy interface exposed in Grafana to communicate with Grafana projects
directly.

Background
**********

Since queries to retrieve metrics from Grafana are proxied to the project the
format of these queries will change significantly depending on the type of
project. The structure of the projects themselves will also change
significantly as they are structured by users and administrators. For instance,
some developers might decide to store metrics about compute_nodes in MySQL and
use the UUID as primary key while others use InfluxDB and use the hostname as
primary key. Furthermore, datasources in Watcher should return metrics in
specific units strictly defined in the baseclass_ depending on how the units
are stored in the projects they might require conversion before being returned.
The flexible configuration parameters of the Grafana datasource allow to
specify exactly how the deployment is configured and this will enable to
correct retrieval of metrics and with the correct units.

.. _datasources: https://grafana.com/plugins?type=datasource
.. _baseclass: https://github.com/openstack/watcher/blob/584eeefdc8/watcher/datasources/base.py

Requirements
------------

The use of the Grafana datasource requires a reachable Grafana endpoint and an
authentication token for access to the desired projects. The projects behind
Grafana will need to contain the metrics for compute_nodes_ or instances_ and
these need to be identifiable by an attribute of the Watcher datamodel_ for
instance hostname or UUID.

.. _compute_nodes: https://opendev.org/openstack/watcher/src/branch/master/watcher/decision_engine/model/element/node.py
.. _instances: https://opendev.org/openstack/watcher/src/branch/master/watcher/decision_engine/model/element/instance.py
.. _datamodel: https://opendev.org/openstack/watcher/src/branch/master/watcher/decision_engine/model/element

Limitations
***********

* Only the InfluxDB project is currently supported [#f1]_.
* All metrics must be retrieved from the same Grafana endpoint (same URL).
* All metrics must be retrieved with the same authentication token.

.. [#f1] A base class for projects is available_ and easily extensible.
.. _available: https://review.opendev.org/#/c/649341/24/watcher/datasources/grafana_translator/base.py

Configuration
-------------

Several steps are required in order to use the Grafana datasource, Most steps
are related configuring Watcher to match the deployed Grafana setup such as
queries proxied to the project or the type of project for any given metric.
Most of the configuration can either be supplied via the traditional
configuration file or in a `special yaml`_ file.

.. _special yaml: https://specs.openstack.org/openstack/watcher-specs/specs/train/approved/file-based-metricmap.html

token
*****

First step is to generate an access token with access to the required projects.
This can be done from the api_ or from the web interface_. Tokens generated
from the web interface will have the same access to projects as the user that
created them while using the cli allows to generate a key for a specific
role.The token will only be displayed once so store it well. This token will go
into the configuration file later and this parameter can not be placed in the
yaml.

.. _api: https://grafana.com/docs/http_api/auth/#create-api-key
.. _interface: https://grafana.com/docs/http_api/auth/#create-api-token

base_url
********

Next step is supplying the base url of the Grafana endpoint. The base url
parameter will need to specify the type of http protocol and the use of
plain text http is strongly discouraged due to the transmission of the access
token. Additionally the path to the proxy interface needs to be supplied as
well in case Grafana is placed in a sub directory of the web server. An example
would be: `https://mygrafana.org/api/datasource/proxy/` were
`/api/datasource/proxy` is the default path without any subdirectories.
Likewise, this parameter can not be placed in the yaml.

To prevent many errors from occurring and potentially filing the logs files it
is advised to specify the desired datasource in the configuration as it would
prevent the datasource manager from having to iterate and try possible
datasources with the launch of each audit. To do this specify `datasources` in
the `[watcher_datasources]` group.

The current configuration that is required to be placed in the traditional
configuration file would look like the following:

.. code-block:: shell

    [grafana_client]
    token = 0JLbF0oB4R3Q2Fl337Gh4Df5VN12D3adBE3f==
    base_url = https://mygranfa.org/api/datasource/proxy

    [watcher_datasources]
    datasources = grafana

metric parameters
*****************

The last five remaining configuration parameters can all be placed both in the
traditional configuration file or in the yaml, however, it is not advised to
mix and match but in the case it does occur the yaml would override the
settings from the traditional configuration file. All five of these parameters
are dictionaries mapping specific metrics to a configuration parameter. For
instance the `project_id_map` will specify the specific project id in Grafana
to be used. The parameters are named as follow:

* project_id_map
* database_map
* translator_map
* attribute_map
* query_map

These five parameters are named differently if configured using the yaml
configuration file. The parameters are named as follows and are in
identical order as to the list of the traditional configuration file:

* project
* db
* translator
* attribute
* query

When specified in the yaml the parameters are no longer dictionaries instead
each parameter needs to be defined per metric as sub-parameters. Examples of
these parameters configured for both the yaml and traditional configuration
are described at the end of this document.

project_id
**********

The project id's can only be determined by someone with the admin role in
Grafana as that role is required to open the list of projects. The list of
projects can be found on `/datasources` in the web interface but
unfortunately it does not immediately display the project id. To display
the id one can best hover the mouse over the projects and the url will show the
project id's for example `/datasources/edit/7563`. Alternatively the entire
list of projects can be retrieved using the `REST api`_. To easily make
requests to the REST api a tool such as Postman can be used.

.. _REST api: https://grafana.com/docs/http_api/data_source/#get-all-datasources

database
********

The database is the parameter for the schema / database that is actually
defined in the project. For instance, if the project would be based on MySQL
this is were the name of schema used within the MySQL server would be
specified. For many different projects it is possible to list all the databases
currently available. Tools like Postman can be used to list all the available
databases per project. For InfluxDB based projects this would be with the
following path and query, however be sure to construct these request in Postman
as the header needs to contain the authorization token:

.. code-block:: shell

    https://URL.DOMAIN/api/datasources/proxy/PROJECT_ID/query?q=SHOW%20DATABASES

translator
**********

Each translator is for a specific type of project will have a uniquely
identifiable name and the baseclass allows to easily support new types of
projects such as elasticsearch or prometheus. Currently only InfluxDB based
projects are supported as a result the only valid value for this parameter is `
influxdb`.

attribute
*********

The attribute parameter specifies which attribute to use from Watcher's
data model in order to construct the query. The available attributes differ
per type of object in the data model but the following table shows the
attributes for ComputeNodes, Instances and IronicNodes.

+-----------------+-----------------+--------------------+
| ComputeNode     | Instance        | IronicNode         |
+=================+=================+====================+
| uuid            | uuid            | uuid               |
+-----------------+-----------------+--------------------+
| id              | name            | human_id           |
+-----------------+-----------------+--------------------+
| hostname        | project_id      | power_state        |
+-----------------+-----------------+--------------------+
| status          | watcher_exclude | maintenance        |
+-----------------+-----------------+--------------------+
| disabled_reason | locked          | maintenance_reason |
+-----------------+-----------------+--------------------+
| state           | metadata        | extra              |
+-----------------+-----------------+--------------------+
| memory          | state           |                    |
+-----------------+-----------------+--------------------+
| disk            | memory          |                    |
+-----------------+-----------------+--------------------+
| disk_capacity   | disk            |                    |
+-----------------+-----------------+--------------------+
| vcpus           | disk_capacity   |                    |
+-----------------+-----------------+--------------------+
|                 | vcpus           |                    |
+-----------------+-----------------+--------------------+

Many if not all of these attributes map to attributes of the objects that are
fetched from clients such as Nova. To see how these attributes are put into the
data model the following source files can be analyzed for Nova_ and Ironic_.

.. _Nova: https://opendev.org/openstack/watcher/src/branch/master/watcher/decision_engine/model/collector/nova.py#L304
.. _Ironic: https://opendev.org/openstack/watcher/src/branch/master/watcher/decision_engine/model/collector/ironic.py#L85

query
*****

The query is the single most important parameter it will be passed to the
project and should return the desired metric for the specific host and return
the value in the correct unit. The units for all available metrics are
documented in the `datasource baseclass`_. This might mean the query specified
in this parameter is responsible for converting the unit. The following query
demonstrates how such a conversion could be achieved and demonstrates the
conversion from bytes to megabytes.

.. code-block:: shell

    SELECT value/1000000 FROM memory...

Queries will be formatted using the .format string method within Python. This
format will currently have give attributes exposed to it labeled `{0}` to
`{4}`. Every occurrence of these characters within the string will be replaced
with the specific attribute.

- {0} is the aggregate typically `mean`, `min`, `max` but `count` is also
  supported.
- {1} is the attribute as specified in the attribute parameter.
- {2} is the period of time to aggregate data over in seconds.
- {3} is the granularity or the interval between data points in seconds.
- {4} is translator specific and in the case of InfluxDB it will be used for
  retention_periods.

**InfluxDB**

Constructing the queries or rather anticipating how the results should look to
be correctly interpreted by Watcher can be a challenge. The following json
example demonstrates how what the result should look like and the query used to
get this result.

.. code-block:: json

    {
    "results": [
        {
            "statement_id": 0,
            "series": [
                {
                    "name": "vmstats",
                    "tags": {
                        "host": "autoserver01"
                    },
                    "columns": [
                        "time",
                        "mean"
                    ],
                    "values": [
                        [
                            1560848284284,
                            7680000
                        ]
                    ]
                }
            ]
        }
    ]
    }

.. code-block:: shell

    SELECT {0}("{0}_value") FROM "vmstats" WHERE host =~ /^{1}$/ AND
    "type_instance" =~ /^mem$/ AND time >= now() - {2}s GROUP BY host

.. _datasource baseclass: https://opendev.org/openstack/watcher/src/branch/master/watcher/datasources/base.py

Example configuration
---------------------

The example configurations will show both how to achieve the entire
configuration in the config file or use a combination of the regular file and
yaml. Using yaml to define all the parameters for each metric is recommended
since it has better human readability and supports mutli-line option
definitions.

Configuration file
******************

**It is important to note that the line breaks shown in between assignments of
parameters can not be used in the actual configuration and these are simply here
for readability reasons.**

.. code-block:: shell

    [grafana_client]
    # Authentication token to gain access (string value)
    # Note: This option can be changed without restarting.
    token = eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk==

    # first part of the url (including https:// or http://) up until project id
    # part. Example: https://secure.org/api/datasource/proxy/ (string value)
    # Note: This option can be changed without restarting.
    base_url = https://monitoring-grafana.com/api/datasources/proxy/

    # Project id as in url (integer value)
    # Note: This option can be changed without restarting.
    project_id_map = host_cpu_usage:1337,host_ram_usage:6969,
    instance_cpu_usage:1337,instance_ram_usage:9696

    # Mapping of grafana databases to datasource metrics. (dict value)
    # Note: This option can be changed without restarting.
    database_map = host_cpu_usage:monit_production,
    host_ram_usage:monit_production,instance_cpu_usage:prod_cloud,
    instance_ram_usage:prod_cloud

    translator_map = host_cpu_usage:influxdb,host_ram_usage:influxdb,
    instance_cpu_usage:influxdb,instance_ram_usage:influxdb

    attribute_map = host_cpu_usage:hostname,host_ram_usage:hostname,
    instance_cpu_usage:name,instance_ram_usage:name

    query_map = host_cpu_usage:SELECT 100-{0}("{0}_value") FROM {4}.cpu WHERE
    ("host" =~ /^{1}$/ AND "type_instance" =~/^idle$/ AND time > now()-{2}s),
    host_ram_usage:SELECT {0}("{0}_value")/1000000 FROM {4}.memory WHERE
     ("host" =~ /^{1}$/) AND "type_instance" =~ /^used$/ AND time >= now()-{2}s
     GROUP BY "type_instance",instance_cpu_usage:SELECT {0}("{0}_value") FROM
     "vmstats" WHERE host =~ /^{1}$/ AND "type_instance" =~ /^cpu$/ AND time >=
     now() - {2}s GROUP BY host,instance_ram_usage:SELECT {0}("{0}_value") FROM
     "vmstats" WHERE host =~ /^{1}$/ AND "type_instance" =~ /^mem$/ AND time >=
     now() - {2}s GROUP BY host

    [grafana_translators]

    retention_periods = one_week:10080,one_month:302400,five_years:525600

    [watcher_datasources]
    datasources = grafana

yaml
****

When using the yaml configuration file some parameters still need to be defined
using the regular configuration such as the path for the yaml file these
parameters are detailed below:

.. code-block:: shell

    [grafana_client]
    token = eyJrIjoiT0tTcG1pUlY2RnVKZTFVaDFsNFZXdE9ZWmNrMkZYbk==

    base_url = https://monitoring-grafana.com/api/datasources/proxy/

    [watcher_datasources]
    datasources = grafana

    [watcher_decision_engine]
    metric_map_path = /etc/watcher/metric_map.yaml

Using the yaml allows to more effectively define the parameters per metric with
greater human readability due to the availability of multi line options. These
multi line options are demonstrated in the query parameters.

.. code-block:: yaml

  grafana:
    host_cpu_usage:
      project: 1337
      db: monit_production
      translator: influxdb
      attribute: hostname
      query: >
          SELECT 100-{0}("{0}_value") FROM {4}.cpu
          WHERE ("host" =~ /^{1}$/ AND "type_instance" =~/^idle$/ AND
          time > now()-{2}s)
    host_ram_usage:
      project: 6969
      db: monit_production
      translator: influxdb
      attribute: hostname
      query: >
          SELECT {0}("{0}_value")/1000000 FROM {4}.memory WHERE
           ("host" =~ /^{1}$/) AND "type_instance" =~ /^used$/ AND time >=
           now()-{2}s GROUP BY "type_instance"
    instance_cpu_usage:
      project: 1337
      db: prod_cloud
      translator: influxdb
      attribute: name
      query: >
          SELECT {0}("{0}_value") FROM
           "vmstats" WHERE host =~ /^{1}$/ AND "type_instance" =~ /^cpu$/ AND
           time >= now() - {2}s GROUP BY host
    instance_ram_usage:
      project: 9696
      db: prod_cloud
      translator: influxdb
      attribute: name
      query: >
          SELECT {0}("{0}_value") FROM
           "vmstats" WHERE host =~ /^{1}$/ AND "type_instance" =~ /^mem$/ AND
           time >= now() - {2}s GROUP BY host

External Links
--------------

- `List of Grafana datasources <https://grafana.com/plugins?type=datasource>`_
