#!/usr/bin/env python3

import os
import sys
import subprocess as sub
import argparse
import shutil

RAMA_DIR = "/".join(os.path.realpath( __file__ ).split("/")[:-1])

LOG_TO_STDOUT = ["-Drama.log4j.appender=stdout", "-Dlogfile.path=/dev/null"];

def maybe_help(args, help_str, min_args):
    if len(args) > 0 and args[0] in ["help", "--help", "-h", "h"]:
        print(help_str)
        sys.exit(0)
    elif len(args) < min_args:
        print("Invalid Usage")
        print(help_str)
        sys.exit(-1)

def get_jars_full(adir):
    files = os.listdir(adir)
    ret = []
    for f in files:
        if f.endswith(".jar"):
            ret.append(adir + "/" + f)
    return ret

def get_logpath(logFileName):
    return RAMA_DIR + "/logs/" + logFileName

def get_classpath(extrajars):
    ret = get_jars_full(RAMA_DIR)
    ret.extend(get_jars_full(RAMA_DIR + "/lib"))
    ret.extend(extrajars)

    ret.append(RAMA_DIR) # for rama.yaml
    return ":".join(ret)

def confvalue(name, logFileName = None):
    command = ["java", "-client", "-Drama.home=" + RAMA_DIR] + LOG_TO_STDOUT + [
        "-cp", get_classpath([]), "rpl.rama.distributed.command.config_value", name
    ]
    p = sub.Popen(command, stdout=sub.PIPE, universal_newlines=True)
    output, errors = p.communicate()
    lines = output.split("\n")
    for line in lines:
        tokens = line.split(" ")
        if tokens[0] == "VALUE:":
            return " ".join(tokens[1:])
    return ""

confValue_help = """
Usage: rama confValue <configuration name>

Prints value configured for given config in this unpacked release.

Example:
  rama confValue "replication.streaming.timeout.millis"
"""

def print_confvalue(*args):
    maybe_help(args, confValue_help, 1)
    print(confvalue(args[0]))

def quote_str(s):
    return "'" + s + "'"

def use_rlwrap(rlwrap):
    try:
        if rlwrap:
            out = sub.check_call(["rlwrap", "--version"], stdout=sub.DEVNULL, stderr=sub.DEVNULL)
            return out == 0
        return False
    except:
        print("Please install rlwrap to get a better REPL experience.")
        return False

def exec_rama_class(klass, jvmtype="-server", jvmopts=[], extrajars=[], args=[], redirectfile=None, rlwrap=False):
    args = list(args)
    cp = get_classpath(extrajars)
    prefix = ["rlwrap"] if use_rlwrap(rlwrap) else []
    all_args = prefix + ["java", jvmtype, "-Drama.home=" + RAMA_DIR,
                         "-Dlog4j.configurationFile=file:" + RAMA_DIR + "/log4j2.properties",
                         "-cp", cp] + jvmopts + [klass] + args
    # TODO: have a command line switch to print this
    # print("exec " + " ".join(all_args), flush=True)
    if redirectfile != None:
        STDOUT_FILENO = 1
        STDERR_FILENO = 2
        new_out = os.open(redirectfile, os.O_WRONLY|os.O_CREAT)
        os.dup2(new_out, STDOUT_FILENO)
        os.dup2(new_out, STDERR_FILENO)
    os.execvp(all_args[0], all_args)

licenseInfo_help = """
Usage: rama licenseInfo

Prints out information about all installed licenses
"""

def license_info(*args):
    maybe_help(args, licenseInfo_help, 0)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.license.info",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

upsertLicense_help = """
Usage: rama upsertLicense --licensePath <path>

Adds a license to Conductor.

Example:
  rama upsertLicense --licensePath /path/to/license.edn
"""

def upsert_license(*args):
    maybe_help(args, upsertLicense_help, 2)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.license.upsert",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

cleanupLicenses_help = """
Usage: rama cleanupLicenses

Removes expired licenses from the conductor
"""

def cleanup_licenses(*args):
    maybe_help(args, cleanupLicenses_help, 0)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.license.cleanup",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

def string_or_array_options(opts):
    if isinstance(opts, str):
        return opts.split()
    else:
        return opts

conductor_help = """
Usage: rama conductor

Launches a conductor on the current machine.

Can also be used to launch a conductor locally for testing.
"""

def conductor(klass="rpl.rama.distributed.daemon.conductor"):
    maybe_help([klass], conductor_help, 0)

    tmp_dir = confvalue("local.dir") + "/conductor/tmp"

    # Before starting the conductor, we check if it's tmp directory already
    # exits. We delete it to make sure it doesn't accumulate junk from rocksdb
    if os.path.exists(tmp_dir):
        shutil.rmtree(tmp_dir)

    os.makedirs(tmp_dir, exist_ok=True)

    # NOTE: Even though we're passing logfile.path, we still need to pass the
    # plain logfile.name. This is used in some log4j configs to manage the file
    # deletion strategy along with a basedir. In that case, we can't use
    # relative file paths, but that won't impact this.
    jvmopts = ["-Dlogfile.name=conductor.log",
               "-Djava.io.tmpdir=" + tmp_dir,
               "-Dlogfile.path=" + get_logpath("conductor.log"),
               "-Drama.log4j.appender=file"] \
               + string_or_array_options(confvalue("conductor.child.opts"))
    exec_rama_class(
        klass,
        jvmtype="-server",
        jvmopts=jvmopts,
        redirectfile=RAMA_DIR + "/logs/conductor.out")

supervisor_help = """
Usage: rama supervisor

Launches a supervisor on the current machine.

Can also be used to launch a supervisor locally for testing.
"""

def supervisor(klass="rpl.rama.distributed.daemon.supervisor"):
    maybe_help([klass], supervisor_help, 0)
    tmp_dir = confvalue("local.dir") + "/supervisor/tmp"

    # Before starting the supervisor, we check if it's tmp directory already
    # exits. We delete it to make sure it doesn't accumulate junk from rocksdb
    if os.path.exists(tmp_dir):
        shutil.rmtree(tmp_dir)

    os.makedirs(tmp_dir, exist_ok=True)

    jvmopts = ["-Dlogfile.name=supervisor.log",
               "-Djava.io.tmpdir=" + tmp_dir,
               "-Drama.log4j.appender=file",
               "-Dlogfile.path=" + get_logpath("supervisor.log")] \
               + string_or_array_options(confvalue("supervisor.child.opts"))
    exec_rama_class(
        klass,
        jvmtype="-server",
        jvmopts=jvmopts,
        redirectfile=RAMA_DIR + "/logs/supervisor.out")

devZookeeper_help = """
Usage: rama devZookeeper

Launches a Zookeeper server. Not for production use.

This is for testing or experimenting purposes only.
"""

def dev_zookeeper(*args):
    maybe_help(args, devZookeeper_help, 0)
    jvmopts = ["-Dlogfile.name=dev-zookeeper.log",
               "-Dlogfile.path=" + get_logpath("dev-zookeeper.log"),
               "-Drama.log4j.appender=file",
               "-Xmx1G",
               "-Xms1G",
               "-verbose:gc"]

    exec_rama_class(
        "rpl.rama.distributed.command.dev_zookeeper",
        jvmtype="-server",
        jvmopts=jvmopts,
        redirectfile=RAMA_DIR + "/logs/dev-zookeeper.out")

backup_help = """
Usage: rama backup --action <action> <options> [args]

Backup, restore, or retrieve information about previous backups. Restoring a module
that's not currently running requires parallelism settings to be set with --threads
and --workers.

Options:
  --action             `backup` to immediately backup a module
                       `restore` to restore a module from a backup
                       `list` to list available backups
                       `info` to show summary of contents of backup
                       `status` to get status of backups of current module instance
  --module             The name of the module
  --backupId           Backup ID to use for restore
  --pageToken          Token to fetch next page for list command
  --threads            Number of threads to allocate for a restored module
  --workers            Number of workers to allocate for a restored module
  --replicationFactor  Replication factor to use for a restored module (optional)

Examples:
  rama backup \\
    --action backup \\
    --module com.mycompany.MyModule

  rama backup \\
    --action restore \\
    --backupId com.mycompany.MyModule-00000193E038B5F8-DE

  rama backup \\
    --action list \\
    --module com.mycompany.MyModule

  rama backup \\
    --action info \\
    --backupId com.mycompany.MyModule-00000193E038B5F8-DE

  rama backup \\
    --action status \\
    --module com.mycompany.MyModule
"""

def backup(*args):
    maybe_help(args, backup_help, 4)
    jvmopts = LOG_TO_STDOUT
    jars = filter(lambda s: s.endswith(".jar"), args)
    exec_rama_class(
        "rpl.rama.distributed.command.backup",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args,
        extrajars=jars)

deploy_help = """
Usage: rama deploy --action <action> <options> [args]

Launches or updates a module.

Options:
  --action            `launch` to start a new module, `update` to redeploy a module with new code
  --jar                Path to the jar on the local machine containing the module
  --module             Fully qualified name of the module to launch
  --systemModule       Name of a system module. Mutually exclusive with `--jar` and `--module`
  --tasks              Number of tasks to launch the module with. Must be a power of 2.
  --threads            Number of threads to launch with. Must be less than or equal to the tasks
  --workers            Number of workers to launch with. Must be less than or equal to the threads
  --replicationFactor  Number of replicas each task for the module should have.
  --moduleOptions      List of key/value pairs for configuring the module.
  --topologyOptions    List of key/value pairs for configuring the module's topologies.
  --depotOptions       List of key/value pairs for configuring the module's depots.
  --pstateOptions      List of key/value pairs for configuring the module's pstates.
  --configOverrides    Path to a YAML file containing options for configuring your workers
  --objectsToDelete    List of depots or pstates to explicitly delete from a module
  --metadata           Arbitrary string that is shown in UI for module instance

The format for supplying a list of objects to delete, or topology, depot, and pstate options is:
`<name>,<key>=<value>[;<name>,<key>=<value>]...`

The list of options for configuring modules, topologies, depots and pstates can be found in the
online documentation: https://redplanetlabs.com/docs/~/index.html

Example:
  rama deploy \\
    --action launch \\
    --jar target/my-application.jar \\
    --module com.mycompany.MyModule \\
    --tasks 128 \\
    --threads 32 \\
    --workers 8 \\
    --replicationFactor 2 \\
    --moduleOptions 'replication.streaming.timeout.millis=20000;pstate.batch.buffer.limit=10' \\
    --topologyOptions 'myTopology,topology.combiner.limit=99' \\
    --depotOptions '*depot,depot.max.fetch=251' \\
    --pstateOptions '$$mb,pstate.reactivity.queue.limit=499;$$stream,pstate.reactivity.queue.limit=89'
"""

def deploy(*args):
    maybe_help(args, deploy_help, 4)
    custom_jvm_opts = os.getenv("RAMA_CLI_JVM_OPTS", "-Xss4m")
    jvmopts = LOG_TO_STDOUT + [custom_jvm_opts] + ["-Delide.rama.module.compilation=true"]
    jars = filter(lambda s: s.endswith(".jar"), args)
    exec_rama_class(
        "rpl.rama.distributed.command.deploy",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args,
        extrajars=jars)

scaleExecutors_help = """
Usage: rama scaleExecutors <options>

Scales a module up or down.

Options:
  --module             Fully qualified name of the module to scale
  --threads            Number of threads to scale to. Must be less than or equal to the tasks
  --workers            Number of workers to scale to. Must be less than or equal to the threads
  --replicationFactor  Number of replicas each task for the module should have.

Example:
  rama scaleExecutors \\
    --module com.mycompany.MyModule \\
    --threads 90 \\
    --workers 30 \\
    --replicationFactor 3
"""

def scale_executors(*args):
    maybe_help(args, scaleExecutors_help, 2)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.scale_executors",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

setOption_help = """
Usage: rama setOption <optionType> <moduleName> [<objectName>] <optionName> <valueJSON> [args]

Sets a module, topology, PState, or depot dynamic option to a new value.
<objectName> parameter must be given for PState and depot options.

<optionType> is one of module, topology, pstate or depot.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration

Example:
  rama setOption rpl.rama.distributed.monitoring.module/Monitoring process-telemetry
"""

def setoption(*args):
    maybe_help(args, setOption_help, 4)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.setoption",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

disallowLeaders_help = """
Usage: rama disallowLeaders <supervisorId>

Disallows leaders on the given supervisor ID. Command blocks until all leaders
that can be moved are moved to different replicas. If the leader's task group only
has one leader candidate (e.g. replication factor is one), the leader will not
be moved.

Only one supervisor can have leaders disallowed at a time.

If invoked with zero arguments, this command prints the current supervisor ID
with leaders disallowed.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration

Example:
  rama disallowLeaders 02e20dcc-74d3-2716-4f45-6d17cf3f4874
"""

def disallow_leaders(*args):
    maybe_help(args, disallowLeaders_help, 0)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.disallow_leaders",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

allowLeaders_help = """
Usage: rama allowLeaders <supervisorId>

Allows leaders on the given supervisor ID.

If invoked with zero arguments, this command prints the current supervisor ID
with leaders disallowed.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration

Example:
  rama allowLeaders 02e20dcc-74d3-2716-4f45-6d17cf3f4874
"""

def allow_leaders(*args):
    maybe_help(args, allowLeaders_help, 0)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.allow_leaders",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

monitoringConfig_help = """
Usage: rama monitoringConfig <options>

Used to get/set monitoring retention windows. Times are specified in number of seconds.

Options:
  --setRetention          granularity,retentionWindow
  --resetRetention        granularity
  --useInternalHostnames  when included, will use internal hostnames for communicating

Granularity may be one of: 60, 600, 3600, 86400, 604800

Examples:
  # Get config:
  rama monitoringConfig

  # Set retention window:
  rama monitoringConfig --setRetention 60,90000

  # Clear retention window:
  rama monitoringConfig --resetRetention 60
"""

def monitoring_config(*args):
    maybe_help(args, monitoringConfig_help, 0)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.monitoring_config",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

changeConfigOverrides_help = """
Usage: rama changeConfigOverrides <module-name> <config overrides yaml file>

Overrides the existing module config in a running module.
"""

def change_config_overrides(*args):
    maybe_help(args, changeConfigOverrides_help, 2)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.override_config",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)


shutdownCluster_help = """
Usage: rama shutdownCluster

Gracefully shuts down a Rama cluster by letting all in-flight processing complete and then
terminating every worker process.

Used as part of upgrading a Rama cluster to a new version.

Can only be run from the Conductor node.

More about upgrading a Rama cluster can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_upgrading_a_rama_cluster_to_a_new_version
"""

def shutdown_cluster(*args):
    maybe_help(args, shutdownCluster_help, 0)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.shutdown_cluster",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

forceClusterOpen_help = """
Usage: rama forceClusterOpen

Allows you to start the cluster before all tasks are ready, for any other reason that might prevent the task from starting.

Must be run from the conductor.

For more information about dealing with cluster upgrades, see the full Rama docs:
https://redplanetlabs.com/docs/~/operating-rama.html#_upgrading_a_rama_cluster_to_a_new_version
"""

def force_cluster_open(*args):
    maybe_help(args, forceClusterOpen_help, 0)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.force_cluster_open",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

allowUngracefulStartup_help = """
Usage: rama allowUngracefulStartup --module <moduleName> --moduleInstanceId <moduleInstanceId> --taskGroupIds <taskGroupIds> [options]

In a cluster upgrade, allows a task thread start with data loss.
Requires all tasks to be running before the conductor opens the cluster.

Options:
  --module                the module name of the task group IDs to allow to start
  --moduleInstanceId      the module instance ID of the task groups to allow to start
  --taskGroupIds          the list of task group IDs to allow to start
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration

For more information about dealing with cluster upgrades, see the full Rama docs:
https://redplanetlabs.com/docs/~/operating-rama.html#_upgrading_a_rama_cluster_to_a_new_version
"""

def allow_ungraceful_startup(*args):
    maybe_help(args, allowUngracefulStartup_help, 6)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.allow_ungraceful_startup",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

forceModuleUpdateProgress_help = """
Usage: rama forceModuleUpdateProgress <moduleName> [options]

In a module update, forces the update to progress to the next state without waiting for usual conditions.
This is an advanced feature that should only be used under the guidance of Red Planet Labs.
When used inappropriately this can cause data loss.

Options:
  --useInternalHostnames when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration
"""

def force_module_update_progress(*args):
    maybe_help(args, forceModuleUpdateProgress_help, 1)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.force_module_update_progress",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

forceModuleUpdateAbort_help = """
Usage: rama forceModuleUpdateAbort <moduleName> [options]

In a module update, forces the update process to abort ungracefully.
This is an advanced feature that should only be used under the guidance of Red Planet Labs.
When used inappropriately this can cause data loss.

Options:
  --useInternalHostnames when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration
"""

def force_module_update_abort(*args):
    maybe_help(args, forceModuleUpdateAbort_help, 1)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.force_module_update_abort",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

conductorReady_help = """
Usage: rama conductorReady

Prints a boolean indicating if the conductor is in READY state or not.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.
"""

def conductor_ready(*args):
    maybe_help(args, conductorReady_help, 0)
    jvmopts = LOG_TO_STDOUT
    try:
        exec_rama_class(
            "rpl.rama.distributed.command.conductor_ready",
            jvmtype="-client",
            jvmopts=jvmopts,
            args=args)
    except:
        sys.exit(1)

numSupervisors_help = """
Usage: rama numSupervisors

Prints the number of supervisors currently running.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.
"""

def num_supervisors(*args):
    maybe_help(args, numSupervisors_help, 0)
    jvmopts = LOG_TO_STDOUT
    try:
        exec_rama_class(
            "rpl.rama.distributed.command.num_supervisors",
            jvmtype="-client",
            jvmopts=jvmopts,
            args=args)
    except:
        sys.exit(1)


moduleStatus_help = """
Usage: rama moduleStatus <moduleName> [options]

Prints current status of a module.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration

Example:
  rama moduleStatus "rpl.rama.distributed.monitoring.module/Monitoring"
"""

def modulestatus(*args):
    maybe_help(args, moduleStatus_help, 1)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.modulestatus",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

moduleInstanceStatus_help = """
Usage: rama moduleInstanceStatus <moduleName> <moduleInstanceId> [options]

Prints current status of a module instance.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration
"""

def module_instance_info(*args):
    maybe_help(args, moduleInstanceStatus_help, 2)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.module_instance_info",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

taskGroupsStatus_help = """
Usage: rama taskGroupsStatus <moduleName> <moduleInstanceId> [options]

Prints status of every task group in a module instance.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration
"""

def taskgroupsstatus(*args):
    maybe_help(args, taskGroupsStatus_help, 2)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.taskgroupsstatus",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

destroy_help = """
Usage: rama destroy <moduleName>

Destroys a module. Fails if there are any dependent modules.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration

Example:
  rama destroy com.mycompany.MyModule
"""

def destroy(*args):
    maybe_help(args, destroy_help, 1)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.destroy",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

pauseTopology_help = """
Usage: rama pauseTopology <moduleName> <topologyName> [--useInternalHostnames]

Pauses a microbatch topology in a module.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration

Example:
  rama pauseTopology rpl.rama.distributed.monitoring.module/Monitoring process-telemetry
"""

def pause_topology(*args):
    maybe_help(args, pauseTopology_help, 2)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.set_topology_active",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=(args + ("false",)))

resumeTopology_help = """
Usage: rama resumeTopology <moduleName> <topologyName> [--useInternalHostnames]

Activates a paused microbatch topology in a module.

Options:
  --useInternalHostnames  when included, will use internal hostnames for communicating.

More about use of internal host names can be found here:
https://redplanetlabs.com/docs/~/operating-rama.html#_internalexternal_hostname_configuration

Example:
  rama resumeTopology rpl.rama.distributed.monitoring.module/Monitoring process-telemetry
"""

def resume_topology(*args):
    maybe_help(args, resumeTopology_help, 2)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.set_topology_active",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=(args + ("true",)))


repl_help = """
Usage: rama repl

Start a Clojure REPL with which you can interact with the cluster
"""

def repl(*args):
    maybe_help(args, repl_help, 0)
    jvmopts = LOG_TO_STDOUT + ["-Delide.rama.module.compilation=true"]
    parser = argparse.ArgumentParser()
    parser.add_argument("--module")
    parser.add_argument("--jar", action="append", default=[])
    parsed = parser.parse_args(args)
    module = parsed.module
    jars = parsed.jar
    tmppath = "__module.jar"

    if module != None:
        print("Downloading module jar...")
        exec_rama_class(
            "rpl.rama.distributed.command.download",
            jvmtype="-client",
            jvmopts=jvmopts,
            args=[module, tmppath]
            )

    jars.append(tmppath)

    try:
        print("Launching REPL...")
        exec_rama_class(
            "clojure.main",
            jvmtype="-client",
            jvmopts=jvmopts,
            extrajars=jars,
            rlwrap=True)
    finally:
        if module != None:
            os.remove(tmppath)

runClj_help = """
Usage: rama runClj <namespace> <jarPath>+

Runs the given Clojure namespace with the Rama release and rama.yaml on the classpath.
Accepts additional jars to include on classpath as additional arguments.

Example:
  rama runClj my.mycompany.script /path/to/jar
"""

def run_clj(*args):
    maybe_help(args, runClj_help, 1)
    namespace = args[0]
    jars = args[1:]
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "clojure.main",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=["-m", namespace],
        extrajars=jars)

runJava_help = """
Usage: rama runJava <classname> <jarPath>+

Runs the given Java class with the Rama release and rama.yaml on the classpath.
Accepts additional jars to include on classpath as additional arguments.

Example:
  rama runJava my.mycompany.MyClass /path/to/jar
"""

def run_java(*args):
    maybe_help(args, runJava_help, 1)
    klass = args[0]
    jars = args[1:]
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        klass,
        jvmtype="-client",
        jvmopts=jvmopts,
        extrajars=jars)

supportBundle_help = """
Usage: rama supportBundle

Generates a zip file containing logs, metadata and telemetry from the cluster.

Options:
  --maxLogSizePerNode     limits the size of the included logs per node, in gigabytes.
                          will include all logs if no limit is specified.
  --output                the name of the file to write the support bundle to
                          defaults to support-bundle-<timestamp>.zip
  --startUI               start a cluster UI with all of the data from a target bundle
  --useInternalHostnames  when included, will use internal hostnames for communicating.

Examples:
  Create a support bundle with a max of 2 gigabytes of logs per node, and write to a specific file
    rama supportBundle --output support-bundle.zip \
                       --maxLogSizePerNode 2

  Don't build a new support bundle, just start a UI with an existing one
    rama supportBundle --startUI support-bundle.2024-01-01.zip
"""

def support_bundle(*args):
    maybe_help(args, supportBundle_help, 0)
    jvmopts = LOG_TO_STDOUT
    exec_rama_class(
        "rpl.rama.distributed.command.support_bundle",
        jvmtype="-client",
        jvmopts=jvmopts,
        args=args)

full_help = """
Usage: rama <subcommand> [options]

Interact with cluster:
  conductor               Launches a conductor
  supervisor              Launches a supervisor
  deploy                  Launches or updates a module
  destroy                 Destroys a module. Fails if there are any dependent modules
  scaleExecutors          Scales a module up or down
  shutdownCluster         Gracefully shuts down a Rama cluster
  pauseTopology           Pauses a microbatch topology in a module
  resumeTopology          Activates a microbatch topology in a module
  backup                  List, trigger or restore a backup for a module
  setOption               Sets a module, topology, PState or depot dynamic option to a given value
  disallowLeaders         Disallows leaders on the given node
  allowLeaders            Allows leaders on the given node
  monitoringConfig        Sets monitoring config, such as retention lengths for telemetry
  changeConfigOverrides   Overrides the existing module config in a running module
  allowUngracefulStartup  In a cluster upgrade, allows a task thread start with data loss
  forceClusterOpen        Allows you to start the cluster before all tasks are ready
  upsertLicense           Adds a license to the conductor
  cleanupLicenses         Removes expired licenses from the conductor
  repl                    Start a Clojure REPL with which you can interact with the cluster
  runClj                  Runs the given Clojure namespace on the classpath
  runJava                 Runs the given Java class on the classpath
  supportBundle           Generates a zip file containing logs and metadata

Query cluster:
  conductorReady          Prints a boolean informing you if the conductor is in READY state
  confValue               Prints the value configured for given config in this unpacked release
  numSupervisors          Prints the number of supervisors currently running
  moduleStatus            Prints current status of a module
  moduleInstanceStatus    Prints status of the given module instance
  taskGroupsStatus        Prints status of every task group in a module instance
  licenseInfo             Prints information about your current Rama license
  monitoringConfig        Without args, prints current monitoring module config
  help                    Prints this help page. Use `rama <subcommand> help` to get further help

Other commands:
  devZookeeper            Launch a Zookeeper server. Not for production use.

To get help for specific subcommands, use `rama <subcommand> help`

The full documentation for Rama can be found here: https://redplanetlabs.com/docs/~/index.html

Examples:

  Add a license:
    rama upsertLicense --licensePath /path/to/license.edn

  Launch a module:
    rama deploy \\
      --action launch \\
      --jar target/my-application.jar \\
      --module 'com.mycompany.MyModule' \\
      --tasks 64 \\
      --threads 16 \\
      --workers 8 \\
      --replicationFactor 3

  Scale a module:
    rama scaleExecutors \\
      --module com.mycompany.MyModule \\
      --threads 90 \\
      --workers 30 \\
      --replicationFactor 3
"""

def rama_help():
    print(full_help)

def unknown_command(args):
    print("""Unknown command: [rama %s]""" % ' '.join(args))
    print(full_help)
    sys.exit(-1)

def invalid_usage():
    print("Invalid usage")
    print(full_help)
    sys.exit(-1)

COMMANDS = {"allowLeaders": allow_leaders,
            "allowUngracefulStartup": allow_ungraceful_startup,
            "backup": backup,
            "conductor": conductor,
            "conductorReady": conductor_ready,
            "confValue": print_confvalue,
            "deploy": deploy,
            "destroy": destroy,
            "devZookeeper": dev_zookeeper,
            "disallowLeaders": disallow_leaders,
            "forceClusterOpen": force_cluster_open,
            "forceModuleUpdateProgress": force_module_update_progress,
            "forceModuleUpdateAbort": force_module_update_abort,
            "moduleStatus": modulestatus,
            "moduleInstanceStatus": module_instance_info,
            "monitoringConfig": monitoring_config,
            "changeConfigOverrides": change_config_overrides,
            "numSupervisors": num_supervisors,
            "pauseTopology": pause_topology,
            "repl": repl,
            "resumeTopology": resume_topology,
            "runClj": run_clj,
            "runJava": run_java,
            "scaleExecutors": scale_executors,
            "setOption": setoption,
            "shutdownCluster": shutdown_cluster,
            "supervisor": supervisor,
            "supportBundle": support_bundle,
            "taskGroupsStatus": taskgroupsstatus,
            "licenseInfo": license_info,
            "upsertLicense": upsert_license,
            "cleanupLicenses": cleanup_licenses,
            "help": rama_help,
            }

def main():
    if len(sys.argv) <= 1:
        invalid_usage()
    args = sys.argv[1:]
    command = args[0]
    command_args = args[1:]
    command_fn = COMMANDS.get(command)
    if command_fn != None:
        command_fn(*command_args)
    else:
        unknown_command(args)

if __name__ == "__main__":
    main()
