#!/usr/bin/env /opt/opensvc/bin/python
#
# Copyright (c) 2009 Christophe Varoqui <christophe.varoqui@free.fr>'
# Copyright (c) 2009 Cyril Galibern <cyril.galibern@free.fr>'
#
# 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., 675 Mass Ave, Cambridge, MA 02139, USA.
#
from __future__ import print_function
import sys
import os
import optparse

#
# add project lib to path
#
pathsvc = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
pathetc = os.path.join(pathsvc, 'etc')
sys.path.append(os.path.join(pathsvc, 'lib'))
prog = os.path.basename(__file__)
action = None

import svcBuilder
import rcStatus
import rcOptParser
import rcExceptions as ex
from rcUtilities import ximport
node_mod = ximport('node')

build_err = False

try:
    from version import version
except:
    version = "dev"

def _exit(r):
    node.close()
    if build_err:
        sys.exit(1)
    sys.exit(r)

node = node_mod.Node()

def install_service(svcnames, src_env):
    if len(svcnames) != 1:
        print("only one service must be specified", file=sys.stderr)
        return 1

    if not os.path.exists(src_env):
        print("%s does not exists"%src_env, file=sys.stderr)
        return 1

    try:
        _install_service(svcnames[0], src_env)
    except Exception as e:
        print(e)
        return 1

    return 0

def _install_service(svcname, src_env):
    import shutil

    pathsvc = os.path.realpath(os.path.join(os.path.dirname(__file__), '..'))
    pathetc = os.path.join(pathsvc, 'etc')
    pathbin = os.path.join(pathsvc, 'bin')

    # install env file in etc/
    src_env = os.path.realpath(src_env)
    dst_env = os.path.join(pathetc, svcname+'.env')
    if dst_env != src_env:
        shutil.copy2(src_env, dst_env)

    # install .dir
    d = os.path.join(pathetc, svcname+'.dir')
    if not os.path.exists(d):
        os.makedirs(d)

    import platform
    sysname, nodename, x, x, machine, x = platform.uname()
    if sysname == 'Windows':
        return

    # install .d
    ld = os.path.join(pathetc, svcname+'.d')
    if not os.path.exists(ld):
        os.symlink(d, ld)
    elif not os.path.exists(ld+os.sep):
        # repair broken symlink
        os.unlink(ld)
        os.symlink(d, ld)

    # install svcmgr link
    ls = os.path.join(pathetc, svcname)
    s = os.path.join(pathbin, 'svcmgr')
    if not os.path.exists(ls):
        os.symlink(s, ls)
    elif os.path.realpath(s) != os.path.realpath(ls):
        os.unlink(ls)
        os.symlink(s, ls)


__ver = prog + " version " + version
__usage = "%prog [options] command\n\n"
parser = optparse.OptionParser(version=__ver, usage=__usage + rcOptParser.format_desc())
parser.add_option("--debug", default=False,
		  action="store_true", dest="debug",
                  help="debug mode")
parser.add_option("--dry-run", default=False, action="store_true", dest="dry_run",
                  help="Show the action execution plan")
parser.add_option("--disable-rollback", default=False, action="store_true", dest="disable_rollback",
                  help="Exit without resource activation rollback on start action error")
parser.add_option("-p", "--parallel", default=False, action="store_true", dest="parallel",
                  help="start actions on specified services in parallel")
parser.add_option("--ignore-affinity", default=False, action="store_true", dest="ignore_affinity",
                  help="ignore service anti-affinity with other services check")
parser.add_option("--remote", default=False, action="store_true", dest="remote",
                  help="flag action as triggered by a remote node. used to avoid recursively triggering actions amongst nodes")
parser.add_option("-f", "--force", default=False, action="store_true", dest="force",
                  help="force action, ignore sanity check warnings")
parser.add_option("--cron", default=False, action="store_true", dest="cron",
                  help="used by cron'ed action to tell the collector to treat the log entries as such")
parser.add_option("--slaves", default=False, action="store_true", dest="slaves",
                  help="option to set to limit the action scope to all slave service resources")
parser.add_option("--slave", default=None, action="store", dest="slave",
                  help="option to set to limit the action scope to the service resources in the specified, comma-sperated, slaves")
parser.add_option("--master", default=False, action="store_true", dest="master",
                  help="option to set to limit the action scope to the master service resources")
parser.add_option("-c", "--cluster", default=False, action="store_true", dest="cluster",
                  help="option to set when excuting from a clusterware to disable safety net")
parser.add_option("-i", "--interactive", default=False, action="store_true", dest="interactive",
                  help="prompt user for a choice instead of going for defaults or failing")
parser.add_option("--rid", default=None, action="store", dest="parm_rid",
                  help="comma-separated list of resource to limit action to")
parser.add_option("--tags", default=None, action="store", dest="parm_tags",
                  help="comma-separated list of resource tags to limit action to")
parser.add_option("--resource", default=[], action="append",
                  help="a resource definition in json dictionary format fed to create or update")
parser.add_option("--provision", default=False, action="store_true", dest="provision",
                  help="provision the service in addition to env file creation. defaults to False.")
parser.add_option("--waitlock", default=60, action="store", dest="parm_waitlock", type="int",
                  help="comma-separated list of resource tags to limit action to")
parser.add_option("--to", default=None, action="store", dest="parm_destination_node",
                  help="remote node to start or migrate the service to")
parser.add_option("--attach", default=False,
                  action="store_true", dest="attach",
                  help="attach the modulesets specified during a compliance check/fix/fixable command")
parser.add_option("--module", default="",
                  action="store", dest="module",
                  help="compliance, set module list")
parser.add_option("--moduleset", default="",
                  action="store", dest="moduleset",
                  help="compliance, set moduleset list. The 'all' value can be used in conjonction with detach.")
parser.add_option("--ruleset", default="",
                  action="store", dest="ruleset",
                  help="compliance, set ruleset list. The 'all' value can be used in conjonction with detach.")
parser.add_option("--ruleset-date", default="",
                  action="store", dest="ruleset_date",
                  help="compliance, use rulesets valid on specified date")
parser.add_option("--param", default=None,
                  action="store", dest="param",
                  help="point a service configuration parameter for the 'get' and 'set' actions")
parser.add_option("--value", default=None,
                  action="store", dest="value",
                  help="set a service configuration parameter value for the 'set --param' action")
parser.add_option("--duration", default=None, action="store", dest="duration", type="int",
                  help="a duration expressed in minutes. used with the 'collector ack unavailability' action")
parser.add_option("--account", default=False, action="store_true", dest="account",
                  help="decides that the unavailabity period should be deduced from the service availability anyway. used with the 'collector ack unavailability' action")
parser.add_option("--begin", default=None, action="store", dest="begin",
                  help="a begin date expressed as 'YYYY-MM-DD hh:mm'. used with the 'collector ack unavailability' action")
parser.add_option("--end", default=None, action="store", dest="end",
                  help="a end date expressed as 'YYYY-MM-DD hh:mm'. used with the 'collector ack unavailability' action")
parser.add_option("--comment", default=None, action="store", dest="comment",
                  help="a comment to log when used with the 'collector ack unavailability' action")
parser.add_option("--author", default=None, action="store", dest="author",
                  help="the acker name to log when used with the 'collector ack unavailability' action")
parser.add_option("--id", default=0, action="store", dest="id", type="int",
                  help="specify an id to act on")
parser.add_option("--table", default=False, action="store_true", dest="table",
                  help="use table representation of collector data instead of the default itemized list of objects and properties")
parser.add_option("--refresh", default=False, action="store_true", dest="refresh",
                  help="drop last resource status cache and re-evaluate before printing with the 'print [json] status' commands")


cluster = False
svcnames = []
cmd = os.path.basename(__file__)

try:
    if cmd == 'allservices':
        node.build_services()
    elif cmd == 'allupservices':
        node.build_services(status=rcStatus.UP)
        node.build_services(status=rcStatus.WARN)
    elif cmd == 'alldownservices':
        node.build_services(status=rcStatus.DOWN)
    elif cmd == 'allprimaryservices':
        node.build_services(onlyprimary=True)
    elif cmd == 'allsecondaryservices':
        node.build_services(onlysecondary=True)
    elif cmd == 'svcmgr':
        parser.add_option("-s", "--service", default=None, action="store", dest="parm_svcs",
                  help="comma-separated list of service to operate on")
        parser.add_option("--status", default=None, action="store", dest="parm_status",
                  help="operate only on service in the specified status (up/down/warn)")
        parser.add_option("--onlyprimary", default=None, action="store_true", dest="parm_primary",
                  help="operate only on service flagged for autostart on this node")
        parser.add_option("--onlysecondary", default=None, action="store_true", dest="parm_secondary",
                  help="operate only on service not flagged for autostart on this node")
        parser.add_option("--envfile", default=None, action="store", dest="param_envfile",
                  help="the envfile to use when installing a service")
    else:
        svcname = cmd.split('.')
        if len(svcname) > 1 and svcname[-1] == 'cluster':
                cluster = True
                svcname = ".".join(svcname[:-1])
        elif len(svcname) > 1 and svcname[-1] == 'stonith':
                cluster = True
                svcname = ".".join(svcname[:-1])
                action = "stonith"
        else:
            svcname = cmd
        node.build_services(svcnames=svcname)
except ex.excError:
    build_err = True

docker_argv = None
if len(sys.argv) > 1 and 'docker' in sys.argv:
    pos = sys.argv.index('docker')
    if len(sys.argv) > pos + 1:
        docker_argv = sys.argv[pos+1:]
    else:
        docker_argv = []
    sys.argv = sys.argv[:pos+1]

(options, args) = parser.parse_args()
if action is not None:
    args = ['stonith']

if node.svcs is None:
    parm_primary = False
    parm_secondary = False
    parm_status = None

    if hasattr(options, "parm_svcs") and options.parm_svcs is not None:
        svcnames = options.parm_svcs.split(',')

    if hasattr(options, "parm_status") and options.parm_status is not None:
        status = rcStatus.status_value(options.parm_status)
    else:
        status = None

    if hasattr(options, "parm_primary") and options.parm_primary is not None and \
       hasattr(options, "parm_secondary") and options.parm_secondary is not None:
        node.close()
        parser.error("--onlyprimary and --onlysecondary are exclusive")

    if hasattr(options, "parm_primary") and options.parm_primary is not None:
        onlyprimary = options.parm_primary
    else:
        onlyprimary = False

    if hasattr(options, "parm_secondary") and options.parm_secondary is not None:
        onlysecondary = options.parm_secondary
    else:
        onlysecondary = False

    try:
        node.build_services(svcnames=svcnames,
                            status=status,
                            onlyprimary=onlyprimary,
                            onlysecondary=onlysecondary)
    except ex.excError:
        build_err = True

if len(args) is 0:
    node.close()
    parser.error("Missing action")

if action is None:
    action = '_'.join(args)

if not action in rcOptParser.supported_actions():
    node.close()
    parser.set_usage(__usage + rcOptParser.format_desc(action=action))
    parser.error("unsupported action")

if node.svcs is not None:
    svcnames = map(lambda x: x.svcname, node.svcs)

if action in ("create", "install") and hasattr(options, "parm_svcs") and options.parm_svcs is not None:
    svcnames = options.parm_svcs.split(',')

if cmd == 'svcmgr' and len(svcnames) == 0:
    sys.stderr.write("""No service specified. Try:
 allservices
 allupservices
 alldownservices
 allprimaryservices
 allsecondaryservices
 svcmgr -s svcname,...
 svcname
""")
    _exit(1)

if action == 'install':
    r = install_service(svcnames, options.param_envfile)
    _exit(r)

if hasattr(options, "parm_rid") and options.parm_rid is not None:
   rid = options.parm_rid.split(',')
else:
   rid = []

if options.slave is not None:
   slave = options.slave.split(',')
else:
   slave = None

if options.parm_tags is not None:
   tags = set(options.parm_tags.split(','))
else:
   tags = set([])

if action in ['create', 'update']:
    data = getattr(svcBuilder, action)(svcnames, options.resource, interactive=options.interactive, provision=options.provision)
    if options.provision:
        rid = data.get("rid", [])
        # force a refresh of node.svcs
        # don't push to the collector yet
        del(node.svcs)
        node.svcs = None
        try:
            node.build_services(svcnames=svcnames, autopush=False)
        except ex.excError:
            build_err = True
        if len(node.svcs) == 1:
            node.svcs[0].action("provision", rid=rid)
    _exit(data["ret"])

node.options.parallel = options.parallel
node.options.waitlock = options.parm_waitlock

for s in node.svcs:
    s.options = options
    s.force = options.force
    s.remote = options.remote
    s.cron = options.cron
    s.options.slaves = options.slaves
    s.options.slave = slave
    s.options.master = options.master
    if cluster:
        s.cluster = cluster
    else:
        s.cluster = options.cluster
    s.destination_node = options.parm_destination_node
    if docker_argv is not None:
        s.docker_argv = docker_argv

err = node.do_svcs_action(action, rid, tags)

try:
    import logging
    logging.shutdown()
except:
    pass

_exit(err)
