#!/usr/bin/env python
# ////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8
# Name        :
# Author      : Avi
# Revision    : $Revision: #10 $
#
# Copyright 2009- ECMWF.
# This software is licensed under the terms of the Apache Licence version 2.0
# which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
# In applying this licence, ECMWF does not waive the privileges and immunities
# granted to it by virtue of its status as an intergovernmental organisation
# nor does it submit to any jurisdiction.
# ////////1/////////2/////////3/////////4/////////5/////////6/////////7/////////8

# -m trace --count -C .
"""an example for a simple python client to help ecFlow scheduling
 + unit test
 + task loading in the suite

one script DO-IT-ALL:
  - play suite + update suite holder aka def-file

  - script as a wrapper for a task

  - download content (passive client)

  - upload statuses (active client)

$manual

  MIRRORING suite from server to server

  test - suspend suite and rerun job if aborted... Thanks

$end
$comment
  a comment in a comment
$end

TODO: check MICRO robust sync
"""
import argparse
import os
import pwd
import sys
import time
import unittest
from time import gmtime, strftime

# ECFLOWP = "/usr/local/apps/ecflow/current"
# ECFLOWC = ECFLOWP + "/bin/ecflow_client "
# sys.path.append(ECFLOWP + "/lib/python2.7/site-packages/ecflow")
ECFLOWC = "ecflow_client "
child = None


def getreq():
    return Family("getreq").add(Task("collectreq"))


def ymd():
    return [Edit("YMD", "29090909"), Label("ymd", "29090909")]


def excepthook(exctype, value, traceback):
    if exctype == KeyboardInterrupt:
        if child:
            child.report("abort", "keyb")
    else:
        sys.__excepthook__(exctype, value, traceback)
        if child:
            child.report("abort", "gen")


def fullname(item):
    return ""


sys.excepthook = excepthook
from ecf import *  # mirror
import ecf
import ecflow


# class Edit(ecf.Variables):    pass

MICRO = "$$"  # keep ecFlow pleased with micro character balance
DEBUG = 1
DEBUG = 0
USER = "emos"
# USER = "map"

sms_status = {
    -1: "unknown",
    0: "unknown",
    1: "suspended",
    2: "complete",
    3: "queued",
    4: "submitted",
    5: "active",
    6: "aborted",
    7: "shutdown",
    8: "halted",
    9: "unknown",
}
sms_type = {
    13: "definition",
    12: "suite",
    11: "family",
    10: "task",
    32: "alias",
}


############################
def usage():
    print(
        sys.argv[0],
        """
      -h # help
      -m <server@port> # mirror path fri
      -t # unit test
      -r # load the task definition in a suite, associated with -p option
      -r # with -m, allow suite loading in destination
      -u usage

can also be used as a module: gen_task, gen_suite

    """,
    )
    for num, val in enumerate(sys.argv):
        print(num, val)
    child.report("abort")
    sys.exit(2)


############################
def get_uid():
    return pwd.getpwnam(get_username()).pw_uid


############################
def gen_task(var=True, load_only=False, kind=None):
    from ecf import Task, Label, Variables

    pwd = os.getcwd()
    name = sys.argv[0]
    if "/" in name:
        name = name.split("/")[-1]
    if "." in name:
        name = name.split(".")[0]
    rec = "00:30"
    cmd = "/home/ma/emos/bin/trimurti emos eurus $ECF_JOB$ $ECF_JOBOUT$"
    cmd = "/usr/local/apps/python/2.7.12-01/bin/python $ECF_JOB$"
    WDIR = pwd
    ODIR = pwd

    load = (
        Label("info", "mirror suites..."),
        Variables(
            ECF_MICRO=MICRO[0],
            ECF_HOME=ODIR,
            ECF_EXTN=".py",
            ECF_JOB_CMD=cmd,
        ),
    )
    if load_only:
        return load
    return Task(name).add(Cron("04:00 20:00 " + rec), If(var, load))


############################
# def stop(msg, num, child=None):
#     print(msg);
#     if child:
#         child.report(msg)
#         if num == 0: child.report("complete")
#     sys.stdout.flush()
#     sys.stderr.flush()
#     sys.exit(num)
def timer(a, b):
    if child is None:
        return
    child.report("timeout")
    child.report("complete")


import signal

signal.signal(signal.SIGALRM, timer)
signal.alarm(180)


############################
class Child(object):
    """facilitate job behaviour with ecFlow server:
    just instanciate and call complete when finished

    obj = Child(); obs.report("complete")

    this does nothing when script is called from command line
    """

    def __init__(self):
        import signal

        env = {
            "ECF_HOST": "$ECF_HOST:$",
            # check can be on a server, child on another
            "ECF_PASS": "$ECF_PASS$",
            "ECF_NAME": "$ECF_NAME$",
            "ECF_PORT": "$ECF_PORT:0$",
            "ECF_TRYNO": "$ECF_TRYNO$",
        }
        self.client = None
        if MICRO[0] in env["ECF_PORT"]:
            print("#MSG: cli mode")
            return
        print("#MSG: will communicate with server...")
        print("#MSG: kill: ssh %s kill -15 %d" % (os.uname()[1], os.getpid()))
        for sig in (
            signal.SIGINT,
            signal.SIGHUP,
            signal.SIGQUIT,
            signal.SIGILL,
            signal.SIGTRAP,
            signal.SIGIOT,
            signal.SIGBUS,
            signal.SIGFPE,
            signal.SIGUSR1,
            signal.SIGUSR2,
            signal.SIGPIPE,
            signal.SIGTERM,
            signal.SIGXCPU,
            signal.SIGPWR,
        ):
            signal.signal(sig, self.signal_handler)
        self.set_client()

    def set_client(self):
        self.client = ecflow.Client()
        if len(MICRO) == 2:
            host = env["ECF_HOST"]
            print(host)
            self.client.set_host_port(host, int(env["ECF_PORT"]))
            self.client.set_child_pid(os.getpid())
            self.client.set_child_path(env["ECF_NAME"])
            self.client.set_child_password(env["ECF_PASS"])
            self.client.set_child_try_no(int(env["ECF_TRYNO"]))
        else:
            host = "$ECF_HOST:$"
            print(host)
            self.client.set_host_port(host, int("$ECF_PORT:0$"))
            self.client.set_child_pid(os.getpid())
            self.client.set_child_path("$ECF_NAME$")
            self.client.set_child_password("$ECF_PASS$")
            self.client.set_child_try_no(int("$ECF_TRYNO$"))
        self.client.child_init()

        self.client.child_label("info", "%s" % strftime("%Y-%m-%d %H:%M:%S", gmtime()))
        self.client.set_child_timeout(20)
        print(self.client)

    def signal_handler(self, signum, frame):
        """ catch signal """
        print("Aborting: Signal handler called with signal ", signum)
        self.report("abort", "Signal handler called with signal " + str(signum))

    def __exit__(self, exc_type, exc_value, traceback):
        self.report("abort", "__exit__")

    def report(self, msg, meter=None):
        """ communicate with ecFlow server """
        if not self.client:
            print("#MSG: no child")
            if msg in ("stop", "complete"):
                sys.stdout.flush()
                sys.stderr.flush()
                print("#MSG: no child stop")
                sys.exit(0)
            else:
                print(msg, meter)
            return
        elif msg in ("stop", "complete"):
            self.client.child_complete()
            self.client = None
            sys.stdout.flush()
            sys.stderr.flush()
            print("#MSG: stop")
            sys.exit(0)
        elif msg in ("abort",):
            self.client.child_abort()
            self.client = None
            raise Exception(msg)
        elif meter:
            self.client.child_meter(msg, meter)
        else:
            self.client.child_label("info", msg)


############################
destinations = {
    "emos": "${DEST_HOST:=localhost}@${DEST_PORT=5001}",
    "test": "${DEST_HOST:=localhost}@${DEST_PORT=5001}",
    "mc": "localhost@5003",
}


def gen_suite(host=None, port=None, path=None):
    import ecf
    from ecf import (
        Task,
        Label,
        Variables,
        Suite,
        Defs,
        Family,
        Trigger,
        Defstatus,
        Meter,
        Event,
    )

    # destinations = dict()
    defs = Defs()
    sname = "mirror"
    print(list(definitions.keys()), list(destinations.keys()))
    defs.add_suite(
        Suite(sname).add(
            ecf.Defstatus("suspended"),
            Limit("one", 2),
            Inlimit("one"),
            gen_task(load_only=True),
            Variables(ALL_ECF=" "),
            [
                Family(name).add(
                    Variables(DESTINATIONS=destinations[name]),
                    Label("info", ""),
                    gen_task(0, kind=name),
                )
                for name in list(definitions.keys())
                # if name in destinations.keys()
            ],
        )
    )
    if DEBUG:
        print(defs)
    return defs


definitions = {  # strings as path for all nodes below sync
    # or suites, subset of the original, leaves are task...
    # or else not sync'ed
    # shall we add list of string/path ???
    "emos": Suite("emos").add(
        Defstatus("suspended"), Family("f1").add(Edit(YMD=20320101), Task("t1"))
    ),
    "test": Suite("test").add(
        Defstatus("suspended"), Family("f1").add(Edit(YMD=20320101), Task("t1"))
    ),
    "mc": Suite("mc").add(
        Defstatus("suspended"), Family("main").add(
            Edit(YMD=20990101), 
            Family("06bc").add(
                Task("sweeper"), 
                Family("fc0006d").add(Task("fc")),
            ),
    ), ),
}

############################


class Mirror(object):

    """    """

    def _protect_oper(self, server):
        if server == "xyz@123":
            if 1:
                child.report("abort", "protect")
            elif (
                "/o" in self.path
                or self.path == "all"
                or "/eda" in self.path
                or "/mc" in self.path
            ):
                raise Exception("# wont do that...")

    def _warmup(self):
        self._set_register(self.path)
        if not self.is_sms:
            self.source.ch_register(False, self.register)
            self.source.sync_local()

    def _send(self, msg="", client=None, send=False):
        """self.grouped is None: no buffering, immediate send
        msg accumulate (send False by default)
        msg is sent ("", client, send=True)
        """
        if self.grouped is None:
            print("#MSG: send", self.grouped)
            client.group(msg)
            time.sleep(self.snap)
            return

        self.grouped += msg
        if send and self.grouped != "":
            print("#MSG: send", self.grouped)
            client.group(self.grouped)
            client.sync_local()
            self.grouped = ""
            time.sleep(self.snap)

    def __init__(self, server="eurus@1630", path="/e_45r1", replay=False, is_sms=False):
        self.is_sms = is_sms
        self.replay = replay  # allow replay from skel definition
        self.act = 1  # active ie force change allowed
        self.snap = 1  # sleep interval
        self.wait = 0  # refresh interval,continue as long as sub/act node exist
        self.path = path
        self.grouped = None  # would use atomic alter
        self.grouped = ""  # command to send to the server ... grouped
        self._set_register(path)

        if server == "all":
            self.servers = "$DESTINATIONS$".split(" ")
        elif " " in server:
            self.servers = server.split(" ")
        elif "@" in server:
            host, port = server.split("@")
            self.servers = ("%s@%s" % (host, port),)
        elif ":" in server:
            host, port = server.split(":")
            self.servers = ("%s:%s" % (host, port),)
        if self.servers[-1] == "":
            del self.servers[-1]
        host = os.getenv("ECF_HOST", "localhost")
        # if MICRO[0] in host: host = "localhost"
        port = os.getenv("ECF_PORT", "3141")
        if MICRO[0] in port:
            port = 1500 + int(get_uid())
        # when started with ecflow_start.sh
        # aka reference/source  server name
        self.sname = "%s:%s" % (host, port)

        if "$SOURCE:$" != "" and MICRO[0] not in "$SOURCE:$":
            self.sname = "$SOURCE:$"
        if DEBUG:
            print("#DBG: source is", self.sname)
        self.set_source()

    def set_source(self):
        self.source = ecflow.Client(self.sname)
        self._warmup()

    def __exit__(self):
        if type(self.source) == ecflow.Client:
            try:
                self.source.ch_drop()
            except:
                pass

    def _set_register(self, path):
        if "/" == path[0]:
            suite = str(path.split("/")[1])
            self.register = [
                suite,
            ]

        elif path == "all":
            self.register = list(skel.keys())

        else:
            child.report("abort", "register")

    def _check(self, server, client, stop=0):
        if client == self.source:
            stop = 1

        for suite in self.register:
            top = client.get_defs().find_abs_node("/" + suite)

            if top is None:
                print(
                    "#WAR: node not found",
                    suite,
                    server,
                )
                if stop:
                    child.report("abort", "check")
                elif self.act and server != self.sname:
                    self._protect_oper(server)
                    if self.replay:
                        if type(definitions[suite]) == str:
                            print("#WAR: not yet")
                            child.report("abort", "str")
                        print("replaying")

                        defs = ecflow.Defs()
                        defs.add_suite(definitions[suite])
                        host = "localhost"
                        client.load(defs)
                        client.suspend("/" + suite)
                        client.begin_suite(suite)
                        client.sync_local()
                    else:
                        print("replay?")
                else:
                    pass

            elif server != self.sname and not (
                top.is_suspended()
                or "%s" % top.get_state() == "unknown"  # normal backup server
            ):  # test server
                print("#ERR: top node shall be suspended - NOGO")
                print("#ERR:", server, top.name(), top.get_state())
                if 1:
                    child.report("abort", "state")
                print("#MSG: stop")
                child.report("abort", "state")  # sys.exit(1)

            else:
                pass

            if suite not in list(definitions.keys()):
                raise Exception(list(definitions.keys()), suite)

            if type(definitions[suite]) == str:
                path = definitions[suite]

            elif suite in self.path:
                path = self.path

            else:
                return

            node = client.get_defs().find_abs_node(path)
            if node:
                return
            print("#WAR: node not found", path, server)
            if stop:
                child.report("abort", "stop")

    def _update_var(self, var, nfrom, ndest, server, client, kind="edit"):
        name = var.name()
        vfrom = None
        vdest = None
        repeat = nfrom.get_repeat()
        print("############# %s" % repeat, nfrom.get_abs_node_path())
        if repeat and repeat.name() == name:
            vfrom = repeat
        else:
            for vfrom in nfrom.variables:
                if vfrom.name() == name:
                    break

        for vdest in ndest.variables:
            if vdest.name() == name:
                break
        pnode = ndest.get_abs_node_path()
        pnode = nfrom.get_abs_node_path()

        if vfrom is None:
            return

        elif vfrom.name() != name:
            return

        if vdest is None:
            return

        vname = vdest.name()
        vval = vdest.value()
        if vname != name:
            return

        if vfrom.name() == vname and str(vfrom.value()) != str(vval) or FORCE:
            if type(vfrom) == Repeat:
                self.action(
                    ndest.get_abs_node_path(),
                    server,
                    client,
                    kind="repeat",
                    value=str(vfrom.value()),
                    msg="repeat requeue",
                )
            else:
                self.action(
                    pnode,
                    server,
                    client,
                    kind=name,
                    value=str(vfrom.value()),
                    msg="value update",
                )

    def process(self, server=None, client=None, item=None, var=False):
        if server is None:
            self._check(self.sname, self.source, 1)

            for server in self.servers:
                server = server.replace("@", ":")
                if DEBUG:
                    print("#DBG: target is", server)
                host, port = server.split(":")

                if int(port) < 65536 and 0 == comm(
                    ECFLOWC + "--ping --port %s --host %s" % (port, host), False
                ):
                    client = ecflow.Client(server)
                    client.ch_register(False, self.register)
                    client.sync_local()
                    self._check(server, client)
                    for suite in self.register:
                        node = definitions[suite]
                        if type(node) == str:
                            node = self.source.get_defs().find_abs_node(node)
                        # try: node.add(Label("memo", "This suite was generated by mirror.py"))
                        # except: pass # used to sync, too late
                        # update variables
                        self.process(server, client, node, "repeat")
                        # self.process(server, client, node) # update statuses
                        # self.process(server, client, node, "edit") # update
                        # variables
                    client.ch_drop()

                else:
                    # child.report("abort","res")
                    print("server is not responding")

            if self.wait > 0:
                print("#MSG: start again... snap ", self.wait)
                time.sleep(self.wait)
                self.wait = 0
                self.source.sync_local()
                self.process()
            return

        elif server == self.sname:
            child.report("abort", "same name " + server + " " + self.sname)  # DONT

        elif client is None:
            child.report("abort", "no client")  # DONT

        elif type(item) in (ecflow.Suite, ecf.Suite):

            if 0:
                pass
            else:
                # suite =
                # client.get_defs().find_abs_node(item.get_abs_node_path())
                suite = client.get_defs().find_abs_node(item.fullname())
                # print(type(client), item.fullname(), type(suite))
                if suite:
                    if not suite.is_suspended():
                        print("#ERR: top node shall be suspended - stop", suite, server)
                        return
                    for node in suite.nodes:
                        self.process(server, client, node, var)
            return

        elif type(item) in (ecflow.Family, ecflow.Task):
            pnode = item.get_abs_node_path()
            nfrom = self.source.get_defs().find_abs_node(pnode)
            if 0:
                pass
            else:
                ndest = client.get_defs().find_abs_node(pnode)

            if nfrom is None:
                if var:
                    return  # ignore
                print(
                    "#ERR: node not found, please update mirror.py skel",
                    pnode,
                    self.sname,
                )
                child.report("abort", "nfrom")

            elif ndest is None:
                if var:
                    return  # ignore
                print("#ERR: node not found, replay?", pnode, server)
                return

            else:
                pass
            if nfrom is not None:
                status = "%s" % nfrom.get_state()
            else:
                raise Exception(pnode, self.source, "nfrom is not")

            if 0:
                pass
            else:
                dstate = "%s" % ndest.get_state()
            is_task = type(item) == ecf.Task

            for evs in nfrom.events:
                for evd in ndest.events:
                    # if evd.name_or_number() != evs.name_or_number(): continue
                    if evd.name() == "" and evd.number() != evs.number():
                        continue
                    if evd.value() == evs.value():
                        break
                    print(
                        "#event",
                        evs.name(),
                        evd.number(),
                        evs.number(),
                        evd.value(),
                        evs.value(),
                    )
                    if evs.value():
                        if evs.name() == "":
                            self._send(
                                "alter change event %d set %s;" % (evs.number(), pnode),
                                client,
                            )
                            # client.alter(pnode, "change", "event", "%d" %
                            # evs.number(), "set")
                        else:
                            self._send(
                                "alter change event %s set %s;" % (evs.name(), pnode),
                                client,
                            )
                        # else: client.alter(pnode, "change", "event",
                        # evs.name(), "set")

            for evs in nfrom.meters:
                for evd in ndest.meters:
                    if evd.name() != evs.name():
                        continue
                    if evd.value() == evs.value():
                        break
                    print("#meter", evd.value(), evs.value())
                    try:
                        value = evs.value()
                        if value > 100:
                            value = 100
                        if value > 0:
                            self._send(
                                "alter change meter %s %s %s;"
                                % (evs.name(), value, pnode),
                                client,
                            )
                        # client.alter(pnode, "change", "meter", evs.name(),
                        # "%s" % evs.value())
                    except:
                        pass

            if status != dstate or FORCE:
                print(
                    "#WAR: diff ",
                    pnode,
                    status,
                    dstate,
                )
                if status == "complete":
                    self.action(
                        pnode, server, client, status, is_task, msg="forced complete"
                    )
                    if not is_task:
                        self._send("", client, send=True)
                        return

                elif status == "queued":
                    # self._send("force queued %s;" % (pnode), client)
                    self.action(
                        pnode, server, client, status, is_task, msg="forced queued"
                    )
                elif status in (
                    "submitted",
                    "active",
                    "unknown",
                    "aborted",
                ):
                    print("#IGN: status is active/submit, ignored")
                else:
                    raise Exception(status)

            if type(item) in (ecflow.Suite, ecflow.Family):
                for node in item.nodes:
                    self.process(server, client, node, var)

            if var:
                for edit in item.variables:
                    self._update_var(edit, nfrom, ndest, server, client, var)

            self._send("", client, send=True)
        else:
            raise Exception(type(item), item)

    def action(
        self, pnode, server, client, kind, is_task=False, value="20010101", msg=""
    ):
        if not self.act:
            return

        self._protect_oper(server)

        print("#MSG:", msg)
        if kind in (
            "active",
            "submitted",
        ):
            return
        elif FORCE:
            pass  # SMS ONLY

        elif kind == "complete":
            if is_task:
                self._send("", client, True)
                client.force_state(pnode, ecflow.State.complete)
            else:
                self._send("", client, True)
                client.force_state_recursive(pnode, ecflow.State.complete)
        elif kind == "queued":  # and is_task:
            self._send("force queued %s;" % pnode, client)

        elif kind == "repeat":
            raise Exception

        else:  # var
            if DEBUG:
                print("#DBG: update var", kind, value)
            if kind in ("queued",):
                pass
            elif kind not in (
                "YMD",
                "JUL",
                "DAY",
                "SCHOST",
                "STHOST",
                "YYYY",
                "MM",
                "ECF_FILES",
                "INIBEGINDATE",
                "MIXTASK",
            ):
                raise Exception(kind)
            if 0:
                pass
            else:
                client.requeue(pnode)
                self._send(
                    "alter change variable %s %s %s;" % (kind, value, pnode),
                    client,
                    True,
                )


# ignore Family + queued while is might just be that we dont mirror all
# fam/tasks and inheritance get it already complete in dest while still
# queued in source


############################
def replay(path, defs=None):
    import ecf

    miss = "@undef@"
    host = os.getenv("ECF_HOST", miss)
    if host == miss:
        print("ECF_HOST not defined")
        exit(1)
    port = os.getenv("ECF_PORT", 31415)
    client = ecf.Client(host, port)
    if defs is None:
        defs = gen_suite(host, port, path)
    if DEBUG:
        print(defs)
    # print(defs)
    if (
        "localhost" in host
        and port in ("3141", 3141)
        and ("/test" in path)
        and "/mirror" not in path
    ):
        raise Exception("dont")

    print("#MSG: replacing %s in " % path, host, port, defs)
    client.replace(path, defs, 1, 1)
    return client


def comm(cmd, rem=True):
    import subprocess

    if rem and cmd in list(memo.keys()):
        return memo[cmd]
    (rc, res) = subprocess.getstatusoutput(cmd)
    print("#COMM:", rc, res, cmd)
    if rem:
        memo[cmd] = rc
    return rc


############################
class TestMirror(unittest.TestCase):
    """ a test case """

    def test_1(self, test_ok=1):
        sname = "mirror"
        replay("/" + sname, gen_suite())


memo = dict()
############################
if __name__ == "__main__":
    import argparse

    parser = argparse.ArgumentParser(add_help=False)
    parser.add_argument("-?", default=0, help="help")
    parser.add_argument("-f", "--force", action="store_true", help="force")
    parser.add_argument("-h", default=0, help="help")
    parser.add_argument("-m", "--mirror", default="localhost@31415", help="mirror")
    parser.add_argument("-p", "--path", default="/mirror/test", help="path")
    parser.add_argument("-r", "--replay", action="store_true", help="replay")
    parser.add_argument("-t", "--test", action="store_true", help="test")
    parser.add_argument("-u", "--usage", action="store_true", help="test")

    parsed = parser.parse_args()
    global FORCE
    FORCE = parsed.force
    print(parsed.path, parsed.mirror, parsed.replay, parsed.force, parsed.test)
    # raise Exception
    child = Child()

    if parsed.usage:
        print(
            """

* mirrors (slow real-time) a source suite, from a source server, to a destination server
* mirror suite can be hosted on the source, on the destination or another server
* this script was used in ecflow course to demonstrate
  * one script that can be used from the command line
  * defines a suite
  * play/replace a suite/node
  * a script can be used as task wrapper/template to generate jobs
  * an ecflow client (passive) to download status/value from source
  * an ecflow client (active) to change status/value on destination

* update mirror.py: destination variable with the triplet suite host port

* update mirror.py: definition with the key(suite) and the minimum suite-tree to mirror

* update mirror.py: replay add "if" for "protection" not to overwrite source suite by mistake

* download a copy from the source suite
  ecflow_client --port 3141 --host localhost --get > tmp.def

* load the mirror suite on the source server
  SUITE=test ECF_HOST=localhost ECF_PORT=3141 ./mirror.py -p /mirror/test -r # load

* play (suspended) mirrored suite in destination
  ECF_HOST=ecgate ECF_PORT=31415 ./mirror.py -m ecgate:31415 -p /test

module load python3; module load ecflow/5.7.0
ecflow_start.sh -p 5001 # source
ecflow_start.sh -p 5003 # destination
# ecflow_client --host vecf1 --port 44444 --get mc > mc.dump # an example suite
ecflow_client --host localhost --port 5001 --replace /mc mc.dump # we take it as a source
# definitions =  # contains "mc" definition
# destinations = { # contains one destination for mc at least
 export  SUITE=mc ECF_HOST=localhost ECF_PORT=5001
 ./mirror.py -p /mirror/mc -r # load mirror on source server # might be destination or another server yet
# ImportError: No module named ecf # ecf shall be in the path, at least it can found in ecflow distrib as 
# ecflow/libs/pyext/samples/api/ecf.py
# ('#MSG: replacing /mirror/test in ', 'localhost', '5001', #5.7.0 # loading OK
# begin mirror - 
# Script OK? mirror.py undef ECF_FILES directory?
# Edit OK? $ is used as ECF_MICRO
# cron OK?
# set DESTINATIONS variable as expected

ECF_HOST=localhost ECF_PORT=5003 python3 ./mirror.py -m localhost:5003 -p /mc # ok
#COMM: 0 ping server(localhost:5003) succeeded in 00:00:00.001058  ~1 milliseconds ecflow_client --ping --port 5003 --host localhost # OK
# replay?
ECF_PORT=5003 python3 ./mirror.py -m localhost:5003 -p /mc -r # ok mc loaded on target
# begin /mc in 5003 (it looks suspended
# edit ECF_JOB_CMD "python3 $ECF_JOB$ > $ECF_JOBOUT$ &"

# python3 /home/ma/map/mirror/mc/mirror.job1 # OK after ECF_PASS FREE running as zombie ommand line
# ImportError: No module named 'ecf' # jobs lost in submit

# mirror jobs submits and complete OKWITH ECF_JOB_CMD updated as 
module load python3 && module load ecflow/5.7.0 && cd /home/ma/map && python3 $ECF_JOB$ > $ECF_JOBOUT$ 2>&1 &
"""
        )
        sys.exit(2)

    if not MICRO[0] in "$ECF_PORT$":
        parsed.mirror = "$DESTINATIONS$"
        parsed.path = "/$FAMILY1$"
        print("#MSG: ssh %s kill -9 %d" % (os.uname()[1], os.getpid()))

    if parsed.test:
        sys.argv.pop()
        unittest.main()

    elif parsed.path and parsed.replay:
        print("#MSG: path and replay", parsed.path, parsed.replay)
        port = int(os.getenv("SMS_PROG", 0))
        s = parsed.path.split("/")[1]
        defs = None
        if "mirror" not in parsed.path:
            defs = Defs()
            defs.add_suite(definitions[s])
        replay(parsed.path, defs)

    elif parsed.mirror and parsed.path:
        print(parsed.mirror, "#", parsed.path, parsed.replay, type(Mirror))
        mirror = Mirror(parsed.mirror, parsed.path, parsed.replay)
        print("#MSG: step")
        mirror.process()
        print("#MSG: step")
        child.report("complete")
        print("#MSG: step")

    else:
        usage()
    print("#MSG: step")
    child.report("complete")
"""
  export ECF_HOST=localhost ECF_PORT=5001 SUITE=mc
  ecflow_client --get /mc > tmp.def
  ./mirror.py -p /mirror/mc -r # load

  ECF_HOST=localhost ECF_PORT=31415 ./mirror.py -m localhost:31415 -p /mc

/tmp/map/work/git/ecflow/libs/pyext/samples/mirror.py
"""
