#!/usr/bin/python
#

# Copyright (C) 2006, 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


"""Ganeti master script

Exit codes, for both start and stop:
  - 0: master setup successful
  - 1: some generic error (this exit code can also be thrown by exceptions)
  - 11: node is not master, nothing to do
  - 12: node setup incomplete, cannot start
  - 13: node should be master, but someone has the ip address already

Only exit codes 0 and 11 represent an ok state. Code 1 was left for
generic errors as other python code can cause exit with code 1.

"""

import os
import sys

from optparse import OptionParser

from ganeti import constants
from ganeti import errors
from ganeti import ssconf
from ganeti import utils

EXIT_OK = 0
EXIT_SOME_ERROR = 1
EXIT_NOTMASTER = constants.EXIT_NOTMASTER
EXIT_NODESETUP_ERROR = constants.EXIT_NODESETUP_ERROR
EXIT_DUPLICATE_IP = 13
EXIT_ARGS_ERROR = 14


def ParseOptions():
  """Parse the command line options.

  Returns:
    (options, args) as from OptionParser.parse_args()

  """
  parser = OptionParser(description="Ganeti master",
                        usage="%prog [-d]",
                        version="%%prog (ganeti) %s" %
                        constants.RELEASE_VERSION)

  parser.add_option("-d", "--debug", dest="debug",
                    help="Enable some debug messages",
                    default=False, action="store_true")
  options, args = parser.parse_args()

  if len(args) != 1 or args[0] not in ("start", "stop"):
    sys.stderr.write("Usage: %s [-d] start|stop\n" % sys.argv[0])
    sys.exit(EXIT_ARGS_ERROR)

  return options, args


def CheckNodeSetup(debug):
  """Checks the node setup.

  If the node setup if ok, this function will return the tuple
  (master_hostname, master_netdev, master_ip). Otherwise the return
  value will be None.

  """
  for fname in (constants.SSL_CERT_FILE,):
    if not os.path.isfile(fname):
      if debug:
        sys.stderr.write("Missing config file %s.\n" % fname)
      return None
  try:
    ss = ssconf.SimpleStore()
    port = ss.GetNodeDaemonPort()
    pwdata = ss.GetNodeDaemonPassword()
    master_name = ss.GetMasterNode()
    master_netdev = ss.GetMasterNetdev()
    master_ip = ss.GetMasterIP()
  except errors.ConfigurationError, err:
    if debug:
      sys.stderr.write("Cluster configuration incomplete: '%s'\n" % str(err))
    return None
  return (master_name, master_netdev, master_ip)


def StartMaster(master_netdev, master_ip, debug):
  """Starts the master.

  """
  if utils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT):
    if utils.TcpPing(master_ip, constants.DEFAULT_NODED_PORT,
                     source=constants.LOCALHOST_IP_ADDRESS):
      # we already have the ip:
      if debug:
        sys.stderr.write("Notice: already started.\n")
      return EXIT_OK
    else:
      return EXIT_DUPLICATE_IP

  result = utils.RunCmd(["ip", "address", "add", "%s/32" % master_ip,
                         "dev", master_netdev, "label",
                         "%s:0" % master_netdev])
  if result.failed:
    if debug:
      sys.stderr.write("Can't activate master IP: %s\n" % result.output)
    return EXIT_SOME_ERROR

  result = utils.RunCmd(["arping", "-q", "-U", "-c 3", "-I", master_netdev,
                         "-s", master_ip, master_ip])
  # we'll ignore the exit code of arping

  if constants.RAPI_ENABLE:
    # Start remote API
    result = utils.RunCmd(["ganeti-rapi", "--port=%s" % constants.RAPI_PORT])
    if debug and result.failed:
      sys.stderr.write("Failed to start ganeti-rapi, error: %s\n" %
                       result.output)

  return EXIT_OK


def StopMaster(master_netdev, master_ip, debug):
  """Stops the master.

  """
  if constants.RAPI_ENABLE:
    # Stop remote API
    result = utils.RunCmd(["fuser", "-k", "-n", "tcp",
                          str(constants.RAPI_PORT)])
    if debug and result.failed:
      sys.stderr.write("Failed to stop ganeti-rapi, error: %s\n" %
                       result.output)

  result = utils.RunCmd(["ip", "address", "del", "%s/32" % master_ip,
                         "dev", master_netdev])
  if result.failed:
    if debug:
      sys.stderr.write("Can't remove the master IP, error: %s" % result.output)
    # but otherwise ignore the failure

  return EXIT_OK


def main():
  """Main function.

  """
  options, args = ParseOptions()
  debug = options.debug
  try:
    myself = utils.HostInfo()
  except errors.ResolverError, err:
    sys.stderr.write("Cannot resolve my own name (%s)\n" % err.args[0])
    return EXIT_NODESETUP_ERROR

  result = CheckNodeSetup(debug)
  if not result:
    if debug:
      sys.stderr.write("Node configuration incomplete.\n")
    return EXIT_NODESETUP_ERROR

  master_node, master_netdev, master_ip = result
  if myself.name != master_node and args[0] == "start":
    if debug:
      sys.stderr.write("Not master, ignoring request.\n")
    return EXIT_NOTMASTER

  if args[0] == "start":
    fn = StartMaster
  else:
    fn = StopMaster

  result = fn(master_netdev, master_ip, debug)
  sys.exit(result)


if __name__ == '__main__':
  main()
