#!/usr/bin/env python

#--
# Copyright 2010-2014 Red Hat, Inc.
#
# 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.
#++


from datetime import datetime
from subprocess import Popen, PIPE
from collections import defaultdict

import optparse
import os
import sys
import re

STATE_OK = 0
STATE_WARNING = 1
STATE_CRITICAL = 2
STATE_UNKNOWN = 3
STATE_DEPENDENT = 4


def get_options():
    """ command-line options """

    usage = "usage: %prog [options]"
    OptionParser = optparse.OptionParser
    parser = OptionParser(usage)

    parser.add_option("-v", "--verbose", action="store_true", default=False,
                      dest="verbose", help="Print additional details.")
    parser.add_option("--validate", action="store_true", default=False,
                      dest="validate", help="Perform additional sanity checks.")

    opts, args = parser.parse_args()
    return opts, args


def proclist():
    """ fetches a process list and their uids """
    # returns lines of "uid cmd"
    ret, out = run('/bin/ps axwo uid=,args=')
    arr = []
    for line in out:
        if line.strip() == '':
            continue
        l = line.lstrip(" ").split(" ")
        uid = l.pop(0)
        cmd = " ".join(l)
        if uid.strip() == '':
            print line
        d = {'uid': uid, 'cmd': cmd}
        arr.append(d)
    return arr


all_idled_apps = None


def is_idled(appid):
    """ returns a boolean saying if the app is currently idled """
    global all_idled_apps
    if all_idled_apps is None:
        ret, all_idled_apps = run("/usr/sbin/oo-admin-ctl-gears listidle | /bin/cut -d' ' -f1")
        if ret != 0:
            return False  # an error occurred, bail
        all_idled_apps.remove('')

    return appid in all_idled_apps


dict_uid_count = None


def num_procs_with_uid(uid, proclist, opts):
    """ return count of the number of apache or java processes associated with a given uid. """
    global dict_uid_count
    if dict_uid_count is None:
        dict_uid_count = defaultdict(int)
        for d in proclist:
            dict_uid_count[d['uid']] += 1
    return dict_uid_count[uid]


def user_list():
    """ fetches a list of usernames and their uids """
    # returns lines of "username:uid"
    ret, out = run('grep ":OpenShift guest:" /etc/passwd | cut -d: -f"1 3"')
    dct = {}
    for line in out:
        if line:
            l = line.split(":")
            dct[l[1]] = l[0]
    return dct


def run(cmd, workdir=None):
    """ run commands in a subprocess and wait for the return code.
        optional: provide a working dir for the command.
    """
    cwd = None
    if workdir:
        cwd = os.getcwd()
        os.chdir(workdir)

    proc = Popen(cmd, shell=True, stdin=PIPE, stdout=PIPE, stderr=PIPE)
    output = proc.communicate()
    output = output[0].split('\n')

    if cwd:
        os.chdir(cwd)

    return proc.returncode, output


def percent(a, b):
    return float(a) / float(b) * 100.0


def cartridge_types(homedir):
    return_code, types = run("ls |egrep -v '-|git'", homedir)
    return types


def get_app_type(app_path):
    """Return Primary Cartridge for application type"""

    try:
        with open(app_path + "/.env/OPENSHIFT_PRIMARY_CARTRIDGE_DIR") as f:
            return (f.read()).split('/')[-2]
    except:
        return "unknown"


def validate(uuid):
    """ given a user's uuid, verify an app exists. """
    app_path = "/var/lib/openshift/%s" % uuid

    # Is there a /git directory?
    if not os.path.isdir(app_path + "/git"):
        return "Path %s/git not found." % app_path

    # Is there something inside the /git directory?
    dirs = os.listdir(app_path + "/git")
    if len(dirs) == 0:
        return "Path %s/git is empty." % app_path

    # Under /git should be appname.git
    app_name = None
    for d in dirs:
        if re.search('\.git$', d):
            app_name = d.replace(".git", "")

    # Is there stuff inside git/appname.git?
    gitfiles = os.listdir(app_path + "/git/" + app_name + ".git")
    if len(gitfiles) == 0:
        return "Path %s/git/%s.git is empty." % (app_path, app_name)

    # Chances are good that this is a git repo. What's the last commit?
    last_commit = run('git show HEAD | grep Date | sed "s/Date:   //"',
                      app_path + "/git/" + app_name + ".git")[1][0]

    # do any cartridges exists?
    types = cartridge_types(app_path)
    if 0 >= len(types):
        return "Path %s does not contain any cartridges." % app_path

    # is there stuff in cartridges?
    for cartridge in types:
        cartridge_path = app_path + "/" + cartridge
        app_files = os.listdir(cartridge_path)
        if len(app_files) == 0:
            return "Path %s is empty." % cartridge_path

    # great! what is it?
    app_type = get_app_type(app_path)

    # tell the world what we've found!
    return """
    Last updated: %s
    %s (%s) seems okay.
""" % (last_commit, app_name, app_type)


def status_output(up, idle, half_idled, tot, pct, state):
    st = "UNK"
    if state == STATE_OK:
        st = "OK"
    elif state == STATE_WARNING:
        st = "WARN"
    elif state == STATE_CRITICAL:
        st = "CRIT"
    else:
        pass  # else is for completeness.

    print "%s running, %s idled, %s half-idled for a total %s of %s (%.2f %%)" % (
        up, idle, half_idled, (up + idle), tot, pct)
    sys.exit(0)


if "__main__" in __name__:
    opts, args = get_options()

    procs = proclist()
    users = user_list()
    num_apps = len(users)

    if num_apps == 0:
        print "OK: No apps found. Nothing to monitor."
        sys.exit(STATE_OK)

    running_apps = 0
    idled_apps = 0
    half_idled_apps = 0
    for uid in users:
        num_procs = num_procs_with_uid(uid, procs, opts)
        if opts.verbose:
            print "Found %s processes for uid %s, username %s." % (num_procs, uid, users[uid])
        if opts.validate:
            print "Validation for uid %s (%s): %s" % (uid, users[uid],
                                                      str(validate(users[uid])))
        is_running = False
        if num_procs > 0:
            running_apps += 1
            is_running = True

        if is_idled(users[uid]):
            if is_running:
                half_idled_apps += 1
            else:
                idled_apps += 1

    pct = percent(running_apps + idled_apps, num_apps)

    #XXX: this could probably be made to be user-definable via cli options.
    if num_apps < 5:
        # never show problems if there's fewer than 5 apps found.
        status_output(running_apps, idled_apps, half_idled_apps, num_apps, pct, STATE_OK)
    else:
        if pct >= 80.0:
            status_output(running_apps, idled_apps, half_idled_apps, num_apps, pct, STATE_OK)
        elif 80.0 > pct >= 50.0:
            status_output(running_apps, idled_apps, half_idled_apps, num_apps, pct, STATE_WARNING)
        else:
            status_output(running_apps, idled_apps, half_idled_apps, num_apps, pct, STATE_CRITICAL)
