#!/usr/bin/python
#
# Copyright 2015 The Cluster-Insight Authors. All Rights Reserved
#
# 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.


"""Runs the cluster insight data collector.

Collects context metadata from the Kubernetes API and computes a graph from it.
"""

import argparse
import sys

import flask
from flask_cors import CORS

# local imports
import collector_error
import constants
import context
import global_state
import kubernetes
import utilities

app = flask.Flask(__name__)

# enable cross-origin resource sharing (CORS) HTTP headers on all routes
cors = CORS(app)


def return_elapsed(gs):
  """Returns a description of the elapsed time of recent operations.

  Args:
    gs: global state.

  Returns:
  A dictionary containing the count, minimum elapsed time,
  maximum elapsed time, average elapsed time, and list of elapsed time
  records.
  """
  assert isinstance(gs, global_state.GlobalState)
  elapsed_list = []
  elapsed_sum = 0.0
  elapsed_min = None
  elapsed_max = None
  for elapsed_record in gs.get_elapsed():
    duration = elapsed_record.elapsed_seconds
    elapsed_list.append(
        {'start_time': utilities.seconds_to_timestamp(
            elapsed_record.start_time),
         'what': elapsed_record.what,
         'threadIdentifier': elapsed_record.thread_identifier,
         'elapsed_seconds': duration})
    elapsed_sum += duration
    if (elapsed_min is None) or (elapsed_max is None):
      elapsed_min = duration
      elapsed_max = duration
    else:
      elapsed_min = min(elapsed_min, duration)
      elapsed_max = max(elapsed_max, duration)

  return {'count': len(elapsed_list),
          'min': elapsed_min,
          'max': elapsed_max,
          'average': elapsed_sum / len(elapsed_list) if elapsed_list else None,
          'items': elapsed_list}


@app.route('/', methods=['GET'])
def home():
  """Returns the response of the '/' endpoint.

  Returns:
    The home page of the Cluster-Insight data collector.
  """
  return flask.send_from_directory('static', 'home.html')


@app.route('/cluster/resources/nodes', methods=['GET'])
def get_nodes():
  """Computes the response of the '/cluster/resources/nodes' endpoint.

  Returns:
    The nodes of the context graph.
  """
  gs = app.context_graph_global_state
  try:
    nodes_list = kubernetes.get_nodes_with_metrics(gs)
  except collector_error.CollectorError as e:
    return flask.jsonify(utilities.make_error(str(e)))

  return flask.jsonify(utilities.make_response(nodes_list, 'resources'))


@app.route('/cluster/resources/services', methods=['GET'])
def get_services():
  """Computes the response of the '/cluster/resources/services' endpoint.

  Returns:
    The services of the context graph.
  """
  gs = app.context_graph_global_state
  try:
    services_list = kubernetes.get_services(gs)
  except collector_error.CollectorError as e:
    return flask.jsonify(utilities.make_error(str(e)))

  return flask.jsonify(utilities.make_response(services_list, 'resources'))


@app.route('/cluster/resources/rcontrollers', methods=['GET'])
def get_rcontrollers():
  """Computes the response of accessing the '/cluster/resources/rcontrollers'.

  Returns:
    The replication controllers of the context graph.
  """
  gs = app.context_graph_global_state
  try:
    rcontrollers_list = kubernetes.get_rcontrollers(gs)
  except collector_error.CollectorError as e:
    return flask.jsonify(utilities.make_error(str(e)))

  return flask.jsonify(utilities.make_response(rcontrollers_list, 'resources'))


@app.route('/cluster/resources/pods', methods=['GET'])
def get_pods():
  """Computes the response of the '/cluster/resources/pods' endpoint.

  Returns:
    The pods of the context graph.
  """
  gs = app.context_graph_global_state
  try:
    pods_list = kubernetes.get_pods(gs)
  except collector_error.CollectorError as e:
    return flask.jsonify(utilities.make_error(str(e)))

  return flask.jsonify(utilities.make_response(pods_list, 'resources'))


@app.route('/debug', methods=['GET'])
def get_debug():
  """Computes the response of the '/cluster/resources/debug' endpoint.

  Returns:
    The DOT graph depicting the context graph.
  """
  gs = app.context_graph_global_state
  try:
    return context.compute_graph(gs, 'dot')
  except collector_error.CollectorError as e:
    return flask.jsonify(utilities.make_error(str(e)))


@app.route('/cluster/resources', methods=['GET'])
def get_resources():
  """Computes the response of the '/cluster/resources' endpoint.

  Returns:
    The 'resources' section of the context graph.
  """
  gs = app.context_graph_global_state
  try:
    response = context.compute_graph(gs, 'resources')
    return flask.jsonify(response)
  except collector_error.CollectorError as e:
    return flask.jsonify(utilities.make_error(str(e)))


@app.route('/cluster', methods=['GET'])
def get_cluster():
  """Computes the response of the '/cluster' endpoint.

  Returns:
    The entire context graph.
  """
  gs = app.context_graph_global_state
  try:
    response = context.compute_graph(gs, 'context_graph')
    return flask.jsonify(response)
  except collector_error.CollectorError as e:
    return flask.jsonify(utilities.make_error(str(e)))


@app.route('/elapsed', methods=['GET'])
def get_elapsed():
  """Computes the response of the '/elapsed' endpoint.

  Returns:
  A successful response containing the list of elapsed time records of the
  most recent Kubernetes API invocations since the previous call to the
  '/elapsed' endpoint. Never returns more than constants.MAX_ELAPSED_QUEUE_SIZE
  elapsed time records.
  """
  gs = app.context_graph_global_state
  result = return_elapsed(gs)
  return flask.jsonify(utilities.make_response(result, 'elapsed'))


@app.route('/healthz', methods=['GET'])
def get_health():
  """Computes the response of the '/healthz' endpoint.

  Returns:
  A successful response containing the attribute 'health' and the value 'OK'.
  """
  return flask.jsonify(utilities.make_response('OK', 'health'))


def main():
  """Starts the web server."""
  parser = argparse.ArgumentParser(description='Cluster-Insight data collector')
  parser.add_argument('-d', '--debug', action='store_true',
                      help='enable debug mode')
  parser.add_argument('--host', action='store', type=str,
                      default='0.0.0.0',
                      help='hostname to listen on [default=all interfaces]')
  parser.add_argument('-p', '--port', action='store', type=int,
                      default=constants.DATA_COLLECTOR_PORT,
                      help='data collector port number [default=%(default)d]')
  args = parser.parse_args()

  g_state = global_state.GlobalState()
  g_state.init_caches_and_synchronization()
  app.context_graph_global_state = g_state

  app.run(host=args.host, port=args.port, debug=args.debug)


if __name__ == '__main__':
  main()