#!/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.


import sys
from optparse import make_option
import pprint

from ganeti.cli import *
from ganeti import opcodes
from ganeti import constants
from ganeti import errors
from ganeti import utils


def InitCluster(opts, args):
  """Initialize the cluster.

  Args:
    opts - class with options as members
    args - list of arguments, expected to be [clustername]

  """
  op = opcodes.OpInitCluster(cluster_name=args[0],
                             secondary_ip=opts.secondary_ip,
                             hypervisor_type=opts.hypervisor_type,
                             vg_name=opts.vg_name,
                             mac_prefix=opts.mac_prefix,
                             def_bridge=opts.def_bridge,
                             master_netdev=opts.master_netdev)
  SubmitOpCode(op)
  return 0


def DestroyCluster(opts, args):
  """Destroy the cluster.

  Args:
    opts - class with options as members

  """
  if not opts.yes_do_it:
    print ("Destroying a cluster is irreversibly. If you really want destroy"
           " this cluster, supply the --yes-do-it option.")
    return 1

  op = opcodes.OpDestroyCluster()
  SubmitOpCode(op)
  return 0


def RenameCluster(opts, args):
  """Rename the cluster.

  Args:
    opts - class with options as members, we use force only
    args - list of arguments, expected to be [new_name]

  """
  name = args[0]
  if not opts.force:
    usertext = ("This will rename the cluster to '%s'. If you are connected"
                " over the network to the cluster name, the operation is very"
                " dangerous as the IP address will be removed from the node"
                " and the change may not go through. Continue?") % name
    if not AskUser(usertext):
      return 1

  op = opcodes.OpRenameCluster(name=name)
  SubmitOpCode(op)
  return 0


def ShowClusterVersion(opts, args):
  """Write version of ganeti software to the standard output.

  Args:
    opts - class with options as members

  """
  op = opcodes.OpQueryClusterInfo()
  result = SubmitOpCode(op)
  print ("Software version: %s" % result["software_version"])
  print ("Internode protocol: %s" % result["protocol_version"])
  print ("Configuration format: %s" % result["config_version"])
  print ("OS api version: %s" % result["os_api_version"])
  print ("Export interface: %s" % result["export_version"])
  return 0


def ShowClusterMaster(opts, args):
  """Write name of master node to the standard output.

  Args:
    opts - class with options as members

  """
  op = opcodes.OpQueryClusterInfo()
  result = SubmitOpCode(op)
  print (result["master"])
  return 0


def ShowClusterConfig(opts, args):
  """Shows cluster information.

  """
  op = opcodes.OpQueryClusterInfo()
  result = SubmitOpCode(op)

  print ("Cluster name: %s" % result["name"])

  print ("Master node: %s" % result["master"])

  print ("Architecture (this node): %s (%s)" %
         (result["architecture"][0], result["architecture"][1]))

  print ("Cluster hypervisor: %s" % result["hypervisor_type"])

  return 0


def ClusterCopyFile(opts, args):
  """Copy a file from master to some nodes.

  Args:
    opts - class with options as members
    args - list containing a single element, the file name
  Opts used:
    nodes - list containing the name of target nodes; if empty, all nodes

  """
  op = opcodes.OpClusterCopyFile(filename=args[0], nodes=opts.nodes)
  SubmitOpCode(op)
  return 0


def RunClusterCommand(opts, args):
  """Run a command on some nodes.

  Args:
    opts - class with options as members
    args - the command list as a list
  Opts used:
    nodes: list containing the name of target nodes; if empty, all nodes

  """
  command = " ".join(args)
  nodes = opts.nodes
  op = opcodes.OpRunClusterCommand(command=command, nodes=nodes)
  result = SubmitOpCode(op)
  for node, output, exit_code in result:
    print ("------------------------------------------------")
    print ("node: %s" % node)
    print ("%s" % output)
    print ("return code = %s" % exit_code)


def VerifyCluster(opts, args):
  """Verify integrity of cluster, performing various test on nodes.

  Args:
    opts - class with options as members

  """
  skip_checks = []
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
  result = SubmitOpCode(op)
  return result


def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

  Args:
    opts - class with options as members

  """
  op = opcodes.OpVerifyDisks()
  result = SubmitOpCode(op)
  if not isinstance(result, tuple) or len(result) != 4:
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

  nodes, nlvm, instances, missing = result

  if nodes:
    print "Nodes unreachable or with bad data:"
    for name in nodes:
      print "\t%s" % name
  retcode = constants.EXIT_SUCCESS

  if nlvm:
    for node, text in nlvm.iteritems():
      print ("Error on node %s: LVM error: %s" %
             (node, text[-400:].encode('string_escape')))
      retcode |= 1
      print "You need to fix these nodes first before fixing instances"

  if instances:
    for iname in instances:
      if iname in missing:
        continue
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
      try:
        print "Activating disks for instance '%s'" % iname
        SubmitOpCode(op)
      except errors.GenericError, err:
        nret, msg = FormatError(err)
        retcode |= nret
        print >> sys.stderr, ("Error activating disks for instance %s: %s" %
                              (iname, msg))

  if missing:
    for iname, ival in missing.iteritems():
      all_missing = utils.all(ival, lambda x: x[0] in nlvm)
      if all_missing:
        print ("Instance %s cannot be verified as it lives on"
               " broken nodes" % iname)
      else:
        print "Instance %s has missing logical volumes:" % iname
        ival.sort()
        for node, vol in ival:
          if node in nlvm:
            print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
          else:
            print ("\t%s /dev/xenvg/%s" % (node, vol))
    print ("You need to run replace_disks for all the above"
           " instances, if this message persist after fixing nodes.")
    retcode |= 1

  return retcode


def MasterFailover(opts, args):
  """Failover the master node.

  This command, when run on a non-master node, will cause the current
  master to cease being master, and the non-master to become new
  master.

  """
  op = opcodes.OpMasterFailover()
  SubmitOpCode(op)


def SearchTags(opts, args):
  """Searches the tags on all the cluster.

  """
  op = opcodes.OpSearchTags(pattern=args[0])
  result = SubmitOpCode(op)
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
    print "%s %s" % (path, tag)


# this is an option common to more than one command, so we declare
# it here and reuse it
node_option = make_option("-n", "--node", action="append", dest="nodes",
                          help="Node to copy to (if not given, all nodes),"
                               " can be given multiple times",
                          metavar="<node>", default=[])

commands = {
  'init': (InitCluster, ARGS_ONE,
           [DEBUG_OPT,
            make_option("-s", "--secondary-ip", dest="secondary_ip",
                        help="Specify the secondary ip for this node;"
                        " if given, the entire cluster must have secondary"
                        " addresses",
                        metavar="ADDRESS", default=None),
            make_option("-t", "--hypervisor-type", dest="hypervisor_type",
                        help="Specify the hypervisor type "
                        "(xen-3.0, fake, xen-hvm-3.1)",
                        metavar="TYPE", choices=["xen-3.0",
                                                 "fake",
                                                 "xen-hvm-3.1"],
                        default="xen-3.0",),
            make_option("-m", "--mac-prefix", dest="mac_prefix",
                        help="Specify the mac prefix for the instance IP"
                        " addresses, in the format XX:XX:XX",
                        metavar="PREFIX",
                        default="aa:00:00",),
            make_option("-g", "--vg-name", dest="vg_name",
                        help="Specify the volume group name "
                        " (cluster-wide) for disk allocation [xenvg]",
                        metavar="VG",
                        default="xenvg",),
            make_option("-b", "--bridge", dest="def_bridge",
                        help="Specify the default bridge name (cluster-wide)"
                          " to connect the instances to [%s]" %
                          constants.DEFAULT_BRIDGE,
                        metavar="BRIDGE",
                        default=constants.DEFAULT_BRIDGE,),
            make_option("--master-netdev", dest="master_netdev",
                        help="Specify the node interface (cluster-wide)"
                          " on which the master IP address will be added "
                          " [%s]" % constants.DEFAULT_BRIDGE,
                        metavar="NETDEV",
                        default=constants.DEFAULT_BRIDGE,),
            ],
           "[opts...] <cluster_name>",
           "Initialises a new cluster configuration"),
  'destroy': (DestroyCluster, ARGS_NONE,
              [DEBUG_OPT,
               make_option("--yes-do-it", dest="yes_do_it",
                           help="Destroy cluster",
                           action="store_true"),
              ],
              "", "Destroy cluster"),
  'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
               "<new_name>",
               "Renames the cluster"),
  'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
             make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
                         help="Skip N+1 memory redundancy tests",
                         action="store_true",
                         default=False,),
             ],
             "", "Does a check on the cluster configuration"),
  'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
                   "", "Does a check on the cluster disk status"),
  'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
                     "", "Makes the current node the master"),
  'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
              "", "Shows the cluster version"),
  'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
                "", "Shows the cluster master"),
  'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
               "[-n node...] <filename>",
               "Copies a file to all (or only some) nodes"),
  'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
              "[-n node...] <command>",
              "Runs a command on all (or only some) nodes"),
  'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
                 "", "Show cluster configuration"),
  'list-tags': (ListTags, ARGS_NONE,
                [DEBUG_OPT], "", "List the tags of the cluster"),
  'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
               "tag...", "Add tags to the cluster"),
  'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
                  "tag...", "Remove tags from the cluster"),
  'search-tags': (SearchTags, ARGS_ONE,
                  [DEBUG_OPT], "", "Searches the tags on all objects on"
                  " the cluster for a given pattern (regex)"),
  }

if __name__ == '__main__':
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))
