Source code for faucet.fctl

#!/usr/bin/env python

"""Report state based on FAUCET/Gauge/Prometheus variables."""

# TODO: this script and is usage is experimental and its output
# is expected to change significantly.
# TODO: add control functionality.

# Copyright (C) 2015 Brad Cowie, Christopher Lorier and Joe Stringer.
# Copyright (C) 2015 Research and Education Advanced Network New Zealand Ltd.
# Copyright (C) 2015--2017 The Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

# pytype: disable=pyi-error
# pytype: disable=import-error
import getopt
import sys
import time
import urllib.request, urllib.parse, urllib.error
import requests
from prometheus_client import parser

# TODO: byte/packet counters could be per second (given multiple samples)
VAL_DECODE = {
    'learned_macs': lambda mac: ':'.join(format(octet, '02x') for octet in int(mac).to_bytes(6, byteorder='big')) # pytype: disable=attribute-error
}


[docs]def scrape_prometheus(endpoints, retries=3): """Scrape a list of Prometheus/FAUCET/Gauge endpoints and aggregate results.""" metrics = [] for endpoint in endpoints: content = None err = None for _ in range(retries): try: if endpoint.startswith('http'): response = requests.get(endpoint) if response.status_code == requests.status_codes.codes.ok: # pylint: disable=no-member content = response.content.decode('utf-8', 'strict') break else: response = urllib.request.urlopen(endpoint) # pytype: disable=module-attr content = response.read().decode('utf-8', 'strict') break except requests.exceptions.ConnectionError as exception: err = exception time.sleep(1) if err is not None: print(err) sys.exit(1) endpoint_metrics = parser.text_string_to_metric_families( content) metrics.extend(endpoint_metrics) return metrics
[docs]def report_label_match_metrics(report_metrics, metrics, nonzero_only=False, delim='\t', label_matches=None): """Text report on a list of Prometheus metrics.""" for metric in metrics: if not report_metrics or metric.name in report_metrics: for _, labels, value in metric.samples: if label_matches is None or \ (label_matches and set(label_matches.items()).issubset(set(labels.items()))): if nonzero_only and int(value) == 0: continue try: value = VAL_DECODE[metric.name](value) except KeyError: pass sorted_labels = [(k, v) for k, v in sorted(labels.items())] print((delim.join((metric.name, str(sorted_labels), str(value)))))
[docs]def usage(): usage_vars = {'self': sys.argv[0]} print((""" Retrieve FAUCET/Gauge state using Prometheus variables. {self} [-n] <-e|--endpoints=http://server:port> [-m|--metrics=prometheus_metrics,] [-l|--labels=name:value,] -n: Don't report 0 values -e|--endpoints: list of Prometheus endpoints to query (comma separated) -m|--metrics: list of Prometheus variables to query (comma separated) -l|--labels: filter list of Prometheus variables by labels that must be present (comma separated) Examples: MACs learned on a DP. {self} -n --endpoints=http://172.17.0.1:9302 --metrics=learned_macs --labels=dp_id:0xb827eb608918 Status of all DPs {self} -n --endpoints=http://172.17.0.1:9302 --metrics=dp_status """.format(**usage_vars))) # pytype: disable=duplicate-keyword-argument sys.exit(-1)
[docs]def main(): try: opts, _ = getopt.getopt( sys.argv[1:], 'ne:m:l:', ['nonzero', 'endpoints=', 'metrics=', 'labels=']) except getopt.GetoptError: usage() endpoints = [] report_metrics = [] label_matches = None nonzero_only = False for opt, arg in opts: if opt in ('-n', '--nonzero'): nonzero_only = True elif opt in ('-e', '--endpoints'): endpoints = arg.split(',') elif opt in ('-m', '--metrics'): report_metrics = arg.split(',') elif opt in ('-l', '--labels'): for label_value in arg.split(','): label, value = label_value.split(':') if label_matches is None: label_matches = {} label_matches[label] = value else: usage() metrics = scrape_prometheus(endpoints) report_label_match_metrics( report_metrics, metrics, nonzero_only=nonzero_only, label_matches=label_matches)
if __name__ == '__main__': main()