#!/opt/slapos/parts/python2.7/bin/python2.7

import os
import sys
import subprocess
import time
import getopt
import sqlite3
import ssl
import urllib2
from xml.dom import minidom
import json
import hashlib

def fmt_date():
  return time.strftime("%Y%m%d")

# get all of the installed software types by checking the SR urls
# return a list, and run routine on all of them
def discover_software():
  conn = sqlite3.connect("/opt/slapos/slapproxy.db")
  cur = conn.cursor()
  qry = cur.execute("SELECT DISTINCT url FROM software14")
  return [row[0] for row in qry if row[0]]

def get_connection_information(software_release):
  conn = sqlite3.connect("/opt/slapos/slapproxy.db")
  cur = conn.cursor()

  if 'software/apache-frontend' not in software_release:
    partition_reference = 'instance-of-%'
  elif 'software/apache-frontend' in software_release:
    partition_reference = 'apache-frontend-1'
  else:
    raise ValueError

  qry = cur.execute("SELECT connection_xml FROM partition14 WHERE connection_xml IS NOT NULL AND software_release=? AND partition_reference LIKE ?", (software_release, partition_reference) )
  xml = None
  for row in qry:
    xml = str(row[0])
    break

  if xml is None:
    return (None, None)

  instance = minidom.parseString(xml)
  if partition_reference.startswith('instance-of-'):
    try:
      el = instance.getElementsByTagName('parameter')[0]
      value = el.childNodes[0].nodeValue
    except:
      return "error"

    # remove leading and trailing '"' character
    # from serialized partition information inside SQlite DB
    if value.startswith('"') and value.endswith('"'):
      value = value[1:-1].replace('\\"', '"')

    if not value.startswith("{"):
      value = "\"" + value + "\""
    json_text = json.loads(value)

    if 'family-admin' in json_text:
      return (json_text['family-admin'], json_text['inituser-password'])
    elif 'insecure' in json_text:
      return (json_text, None)
    else:
      return (None, None)
  elif partition_reference == 'apache-frontend-1':
    ip = None
    for el in instance.getElementsByTagName('parameter'):
      if el.getAttribute('id') == 'private-ipv4':
        if len(el.childNodes) > 0:
          ip = str(el.childNodes[0].nodeValue) or None
          break
    return (ip, None)
  else:
    raise ValueError

def check_tables():
  conn = sqlite3.connect("/opt/slapos/slapproxy.db")
  cur = conn.cursor()
  qry = cur.execute("SELECT CASE WHEN tbl_name = 'partition14' THEN 1 ELSE 0 END FROM sqlite_master WHERE tbl_name = 'partition14' AND type = 'table'")

  if qry is None:
    print "tables aren't ready yet, your build may have failed, check logs in /opt/slapos/log/"
    sys.exit(0)

def get_build_status(software_release):
  sr_hash = hashlib.md5(software_release).hexdigest()
  try:
    os.stat(os.path.join('/opt/slapgrid', sr_hash, '.completed'))
  except OSError:
    try:
      f = open("/opt/slapos/log/slapos-node-software-" + fmt_date() + ".log")
    except Exception:
      try:
        f = open("/opt/slapos/log/slapos-node-software.log")
      except Exception:
        return False
    lines = f.readlines()
    if "ERROR" in lines[-3]:
      return "error"
    return False
  return True

# Check if the last two lines show the software finished building.
# If an error came just before this, we'll report failure.
# Otherwise it passed and we can move on.
# We want to open today's log, as it is most up to date

def status():
  software_release_list = discover_software()
  software_release_list_status = [get_build_status(software_release)
          for software_release in software_release_list]
  if len(software_release_list) == 2 and \
      software_release_list_status == [True, True]:
    build = True
  else:
    build = False

  if build:
    erp5_sr = [q for q in software_release_list if 'software/apache-frontend' not in q][0]
    frn_sr = [q for q in software_release_list
        if 'software/apache-frontend' in q][0]
    zope_ip, pw = get_connection_information(erp5_sr)

    try:
      # by default try new apporach (domain based)...
      hostname = open('/tmp/playbook-frontend-custom-domain').read().strip()
    except Exception:
      try:
        # ...and fall back to old one (IP based)
        hostname = open('/tmp/playbook-public-ipv4').read().strip()
      except Exception:
        frontend = None
    else:
      if len(hostname) == 0:
        frontend = zope_ip
      else:
        frontend = 'https://' + hostname
    frontend_ip, _ = get_connection_information(frn_sr)

    connected = False
    if frontend is not None:
      # as frontend is already up, check if all is ready to serve requests
      # before providing the access url to the user
      ctx = ssl.create_default_context()
      ctx.check_hostname = False
      ctx.verify_mode = ssl.CERT_NONE

      try:
        r1 = urllib2.urlopen(frontend, context=ctx)
        if 'Zope Management Interface' in r1.read():
          connected = True
        else:
          print 'URL %s ready, but does not reply with Zope' % frontend
      except urllib2.URLError, e:
        print "Zope not available yet: %s" % e

    if zope_ip is None or frontend is None or frontend_ip is None or not connected:
      print "Build successful, please wait for instantiation"
      sys.exit(2)

    print ("Build successful, connect to:\n"
           "  " + frontend)
    if pw is not None:
      print (" with\n"
             "  username: zope  password: " + pw)
  elif not build:
    if 'error' in [software_release_list_status]:
      print "An error occurred while building, check /opt/slapos/log/slapos-node-software-" + \
            fmt_date() + ".log for details"
      sys.exit(2)
    else:
      print "Your software is still building, be patient it can take a while"
      sys.exit(2)

  ipv6 = None
  # check if the services are actually running (run slapos node and parse output)
  if pw is None:
    zope_ip = "https://" + zope_ip[zope_ip.index("@")+1:]

  original_zope_ip = zope_ip
  if "[" in zope_ip and "]" in zope_ip:
    ipv6 = zope_ip[zope_ip.index("[")+1:zope_ip.index("]")]
    with open("/etc/hosts", "ra+") as f:
      if " erp5-instance" not in f.read():
        f.write("\n%s   erp5-instance\n" % ipv6)
    zope_ip = zope_ip.replace("[" + ipv6 + "]", "erp5-instance")

  ctx = ssl.create_default_context()
  ctx.check_hostname = False
  ctx.verify_mode = ssl.CERT_NONE

  try:
    r1 = urllib2.urlopen(zope_ip, context=ctx)
  except urllib2.URLError, e:
    print "At least one of your services isn't running! Check with slapos node"
    print "restart a service with slapos node restart slappart:service"
    print ""
    print "DEBUG information: %s" % e
    sys.exit(2)

  if r1.getcode() != 200:
    print "At least one of your services isn't running! Check with slapos node"
    print "restart a service with slapos node restart slappart:service"
    sys.exit(2)

  if ipv6:
    print ""
    print "The URL above may require extra configuration if you want to access it"
    print "from another machine. You can install an apache locally and include the"
    print "the follow rewrite rule (http version):"
    print """
  RewriteRule ^/(.*) %s/VirtualHostBase/http/%%{HTTP_HOST}/VirtualHostRoot/$1 [L,P]

or (https version):

  RewriteRule ^/(.*) %s/VirtualHostBase/https/%%{HTTP_HOST}/VirtualHostRoot/$1 [L,P]

""" % (original_zope_ip, original_zope_ip)


def info(software_release):
  if get_build_status(software_release):
    print get_connection_information(software_release)
  else:
    print "Information unavailable at this time, run " + sys.argv[0] + " -s for details"

def usage():
  print ("Get the status and information of your ERP5 build\n"
         "Usage:")
  print ("  --help    (-h):  Print this message and exit\n"
         "  --status  (-s):  Print the status of your ERP5 build\n"
         "  --info    (-i):  Print the technical information about partition tables\n"
         "  --dump    (-d):  Dump the entire database (alias for slapos proxy show)\n")

def dump():
  subprocess.call(["slapos", "proxy", "show", "-u", "/opt/slapos/slapproxy.db"])

def main(argv):
  # parse command line options
  try:
    opts, args = getopt.getopt(argv, "sihd", ["status", "info", "help", "dump"])
  except getopt.error, msg:
    usage()
    sys.exit(2)
  if len(opts) == 0:
    usage()
    sys.exit(2)
  # process arguments
  for opt, arg in opts:
    if opt in ("-h", "--help"):
       usage()
       sys.exit()
    elif opt in ("-s", "--status"):
      check_tables()
      status()
    elif opt in ("-i", "--info"):
      check_tables()
      for sr in discover_software():
        info(sr)
    elif opt in ("-d", "--dump"):
      dump()

if __name__ == "__main__":
  main(sys.argv[1:])
