"Fossies" - the Fresh Open Source Software Archive

Member "salt-3002.2/salt/returners/appoptics_return.py" (18 Nov 2020, 6255 Bytes) of package /linux/misc/salt-3002.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "appoptics_return.py" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3002.1_vs_3002.2.

    1 """Salt returner to return highstate stats to AppOptics Metrics
    2 
    3 To enable this returner the minion will need the AppOptics Metrics
    4 client importable on the Python path and the following
    5 values configured in the minion or master config.
    6 
    7 The AppOptics python client can be found at:
    8 
    9 https://github.com/appoptics/python-appoptics-metrics
   10 
   11 .. code-block:: yaml
   12 
   13     appoptics.api_token: abc12345def
   14 
   15 An example configuration that returns the total number of successes
   16 and failures for your salt highstate runs (the default) would look
   17 like this:
   18 
   19 .. code-block:: yaml
   20 
   21     return: appoptics
   22     appoptics.api_token: <token string here>
   23 
   24 
   25 The returner publishes the following metrics to AppOptics:
   26 
   27 - saltstack.failed
   28 - saltstack.passed
   29 - saltstack.retcode
   30 - saltstack.runtime
   31 - saltstack.total
   32 
   33 
   34 You can add a tags section to specify which tags should be attached to
   35 all metrics created by the returner.
   36 
   37 .. code-block:: yaml
   38 
   39     appoptics.tags:
   40       host_hostname_alias: <the minion ID - matches @host>
   41       tier: <the tier/etc. of this node>
   42       cluster: <the cluster name, etc.>
   43 
   44 
   45 If no tags are explicitly configured, then the tag key ``host_hostname_alias``
   46 will be set, with the minion's ``id`` grain being the value.
   47 
   48 In addition to the requested tags, for a highstate run each of these
   49 will be tagged with the ``key:value`` of ``state_type: highstate``.
   50 
   51 In order to return metrics for ``state.sls`` runs (distinct from highstates), you can
   52 specify a list of state names to the key ``appoptics.sls_states`` like so:
   53 
   54 .. code-block:: yaml
   55 
   56     appoptics.sls_states:
   57       - role_salt_master.netapi
   58       - role_redis.config
   59       - role_smarty.dummy
   60 
   61 
   62 This will report success and failure counts on runs of the
   63 ``role_salt_master.netapi``, ``role_redis.config``, and
   64 ``role_smarty.dummy`` states in addition to highstates.
   65 
   66 This will report the same metrics as above, but for these runs the
   67 metrics will be tagged with ``state_type: sls`` and ``state_name`` set to
   68 the name of the state that was invoked, e.g. ``role_salt_master.netapi``.
   69 
   70 """
   71 
   72 
   73 import logging
   74 
   75 import salt.returners
   76 import salt.utils.jid
   77 
   78 try:
   79     import appoptics_metrics
   80 
   81     HAS_APPOPTICS = True
   82 except ImportError:
   83     HAS_APPOPTICS = False
   84 
   85 # Define the module's Virtual Name
   86 __virtualname__ = "appoptics"
   87 
   88 log = logging.getLogger(__name__)
   89 
   90 
   91 def __virtual__():
   92     if not HAS_APPOPTICS:
   93         return (
   94             False,
   95             "Could not import appoptics_metrics module; "
   96             "appoptics-metrics python client is not installed.",
   97         )
   98     return __virtualname__
   99 
  100 
  101 def _get_options(ret=None):
  102     """
  103     Get the appoptics options from salt.
  104     """
  105     attrs = {
  106         "api_token": "api_token",
  107         "api_url": "api_url",
  108         "tags": "tags",
  109         "sls_states": "sls_states",
  110     }
  111 
  112     _options = salt.returners.get_returner_options(
  113         __virtualname__, ret, attrs, __salt__=__salt__, __opts__=__opts__
  114     )
  115 
  116     _options["api_url"] = _options.get("api_url", "api.appoptics.com")
  117     _options["sls_states"] = _options.get("sls_states", [])
  118     _options["tags"] = _options.get(
  119         "tags", {"host_hostname_alias": __salt__["grains.get"]("id")}
  120     )
  121 
  122     log.debug("Retrieved appoptics options: %s", _options)
  123     return _options
  124 
  125 
  126 def _get_appoptics(options):
  127     """
  128     Return an appoptics connection object.
  129     """
  130     conn = appoptics_metrics.connect(
  131         options.get("api_token"),
  132         sanitizer=appoptics_metrics.sanitize_metric_name,
  133         hostname=options.get("api_url"),
  134     )
  135     log.info("Connected to appoptics.")
  136     return conn
  137 
  138 
  139 def _calculate_runtimes(states):
  140     results = {"runtime": 0.00, "num_failed_states": 0, "num_passed_states": 0}
  141 
  142     for state, resultset in states.items():
  143         if isinstance(resultset, dict) and "duration" in resultset:
  144             # Count the pass vs failures
  145             if resultset["result"]:
  146                 results["num_passed_states"] += 1
  147             else:
  148                 results["num_failed_states"] += 1
  149 
  150             # Count durations
  151             results["runtime"] += resultset["duration"]
  152 
  153     log.debug("Parsed state metrics: %s", results)
  154     return results
  155 
  156 
  157 def _state_metrics(ret, options, tags):
  158     # Calculate the runtimes and number of failed states.
  159     stats = _calculate_runtimes(ret["return"])
  160     log.debug("Batching Metric retcode with %s", ret["retcode"])
  161     appoptics_conn = _get_appoptics(options)
  162     q = appoptics_conn.new_queue(tags=tags)
  163 
  164     q.add("saltstack.retcode", ret["retcode"])
  165     log.debug("Batching Metric num_failed_jobs with %s", stats["num_failed_states"])
  166     q.add("saltstack.failed", stats["num_failed_states"])
  167 
  168     log.debug("Batching Metric num_passed_states with %s", stats["num_passed_states"])
  169     q.add("saltstack.passed", stats["num_passed_states"])
  170 
  171     log.debug("Batching Metric runtime with %s".stats["runtime"])
  172     q.add("saltstack.runtime", stats["runtime"])
  173 
  174     log.debug(
  175         "Batching with Metric total states %s",
  176         stats["num_failed_states"] + stats["num_passed_states"],
  177     )
  178     q.add(
  179         "saltstack.highstate.total_states",
  180         (stats["num_failed_states"] + stats["num_passed_states"]),
  181     )
  182     log.info("Sending metrics to appoptics.")
  183     q.submit()
  184 
  185 
  186 def returner(ret):
  187     """
  188     Parse the return data and return metrics to AppOptics.
  189 
  190     For each state that's provided in the configuration, return tagged metrics for
  191     the result of that state if it's present.
  192     """
  193 
  194     options = _get_options(ret)
  195     states_to_report = ["state.highstate"]
  196     if options.get("sls_states"):
  197         states_to_report.append("state.sls")
  198     if ret["fun"] in states_to_report:
  199         tags = options.get("tags", {}).copy()
  200         tags["state_type"] = ret["fun"]
  201         log.info("Tags for this run are %s", str(tags))
  202         matched_states = set(ret["fun_args"]).intersection(
  203             set(options.get("sls_states", []))
  204         )
  205         # What can I do if a run has multiple states that match?
  206         # In the mean time, find one matching state name and use it.
  207         if matched_states:
  208             tags["state_name"] = sorted(matched_states)[0]
  209             log.debug("Found returned data from %s.", tags["state_name"])
  210         _state_metrics(ret, options, tags)