Ejemplo n.º 1
0
def get_stimela_logger():
    """Returns Stimela's logger, or None if no Stimela installed"""
    try:
        import stimela
        return stimela.logger()
    except ImportError:
        return None
Ejemplo n.º 2
0
    def __init__(self,
                 indir=None,  # input directory
                 outdir=None,  # output directory
                 msdir=None,  # MS directory
                 parameter_file=None,
                 task=None,
                 base=None,
                 binary=None,
                 description=None,
                 tag=None,
                 prefix=None,
                 parameters=[],
                 version=None):

        self.indir = indir
        self.outdir = outdir

        if parameter_file:
            cab = utils.readJson(parameter_file)
            self.task = cab["task"]
            self.base = cab["base"]
            self.binary = cab["binary"]
            self.tag = cab["tag"]
            self.version = cab.get("version", "x.x.x")
            if cab["msdir"]:
                self.msdir = msdir
            else:
                self.msdir = None
            self.description = cab["description"]
            self.prefix = cab["prefix"]
            parameters0 = cab["parameters"]
            self.parameters = []

            for param in parameters0:
                default = param.get("default", param.get("value", None))
                addme = Parameter(name=param["name"],
                                  dtype=param["dtype"],
                                  io=param.get("io", None),
                                  info=param.get(
                                      "info", None) or "No documentation. Bad! Very bad...",
                                  default=default,
                                  mapping=param.get("mapping", None),
                                  required=param.get("required", False),
                                  choices=param.get("choices", False),
                                  check_io=param.get("check_io", True))
                self.parameters.append(addme)

        else:
            self.task = task
            self.base = base
            self.binary = binary
            self.prefix = prefix
            self.parameters = parameters
            self.description = description
            self.msdir = msdir
            self.tag = tag
            self.version = version

        self.log = stimela.logger()
Ejemplo n.º 3
0
def create_logger():
    """ Creates logger and associated objects. Called upon import"""
    global log, log_filehandler

    log.setLevel(logging.DEBUG)
    log.propagate = False

    # init stimela logger as a sublogger
    if stimela.is_logger_initialized():
        raise RuntimeError("Stimela logger already initialized. This is a bug: you must have an incompatible version of Stimela.")

    stimela.logger(STIMELA_LOGGER_NAME, propagate=True, console=False)

    log_filehandler = DelayedFileHandler()

    log_filehandler.setFormatter(stimela.log_boring_formatter)
    log_filehandler.setLevel(logging.INFO)

    log.addHandler(log_filehandler)
Ejemplo n.º 4
0
def pull(image, name, docker=True, directory=".", force=False):
    """ 
        pull an image
    """
    if docker:
        fp = "docker://{0:s}".format(image)
    else:
        fp = image
    if not os.path.exists(directory):
        os.mkdir(directory)

    image_path = os.path.abspath(os.path.join(directory, name))
    if os.path.exists(image_path) and not force:
        stimela.logger().info(f"Singularity image already exists at '{image_path}'. To replace it, please re-run with the 'force' option")
    else:
        utils.xrun(f"cd {directory} && singularity", ["pull", 
        	"--force" if force else "", "--name", 
         	name, fp])

    return 0
Ejemplo n.º 5
0
    def setup_job_log(self, log_name=None):
        """ set up a log for the job on the host side 
            log_name: preferably unique name for this jobs log
            log_dir: log base directory, None is current directory
        """
        log_name = log_name or self.name
        if log_name not in StimelaJob.logs_avail:
            self.log = stimela.logger().getChild(log_name)

            if self.logfile is not False:
                log_dir = os.path.dirname(self.logfile) or "."
                if not os.path.exists(log_dir):
                    os.mkdir(log_dir)
                fh = logging.FileHandler(self.logfile, 'w', delay=True)
                fh.setLevel(logging.INFO)
                self.log.addHandler(fh)

            self.log.propagate = True            # propagate also to main stimela logger

            StimelaJob.logs_avail[log_name] = self.log
        else:
            self.log = StimelaJob.logs_avail[log_name]
Ejemplo n.º 6
0
    def __init__(self, name, data=None,
                 parameter_file_dir=None, ms_dir=None,
                 build_label=None,
                 singularity_image_dir=None, JOB_TYPE='docker',
                 cabpath=None,
                 logger=None,
                 msdir=None,
                 indir=None,
                 outdir=None,
                 log_dir=None, logfile=None, logfile_task=None,
                 cabspecs=None,
                 loglevel="INFO"):
        """
        Deifine and manage a stimela recipe instance.        

        name    :   Name of stimela recipe
        msdir   :   Path of MSs to be used during the execution of the recipe
        parameter_file_dir :   Will store task specific parameter files here

        logger:   if set to a logger object, uses the specified logger.
                  if None, sets up its own logger using the parameters below

        loglevel: default logging level
        log_dir:  default directory for logfiles
        logfile:  name of logfile, False to disable recipe-level logfiles, or None to form a default name

        logfile_task: name of task-level logfile, False to disable task-level logfiles, or None to form a default name.
                      logfile_task may contain a "{task}" entry which will be substituted for a task name.
        """
        self.name = name
        self.name_ = re.sub(r'\W', '_', name)  # pausterized name

        self.stimela_context = inspect.currentframe().f_back.f_globals
        self.stimela_path = os.path.dirname(docker.__file__)
        # Update I/O with values specified on command line
        self.indir = indir
        self.outdir = outdir
        self.msdir = self.ms_dir = msdir or ms_dir
        self.loglevel = self.stimela_context.get('_STIMELA_LOG_LEVEL', None) or loglevel
        self.JOB_TYPE = self.stimela_context.get('_STIMELA_JOB_TYPE', None) or JOB_TYPE

        self.cabpath = cabpath
        self.cabspecs = cabspecs or {}

        # set default name for task-level logfiles
        self.logfile_task = "{0}/log-{1}-{{task}}".format(log_dir or ".", self.name_) \
            if logfile_task is None else logfile_task

        self._log_fh = None

        if logger is not None:
            self.log = logger
        else:
            logger = stimela.logger(loglevel=self.loglevel)
            self.log = logger.getChild(name)
            self.log.propagate = True # propagate to main stimela logger

            # logfile is False: no logfile at recipe level
            if logfile is not False:
                # logfile is None: use default name
                if logfile is None:
                    logfile = "{0}/log-{1}.txt".format(log_dir or ".", self.name_)

                # reset default name for task-level logfiles based on logfile
                self.logfile_task = os.path.splitext(logfile)[0] + "-{task}.txt" \
                            if logfile_task is None else logfile_task

                # ensure directory exists
                log_dir = os.path.dirname(logfile) or "."
                if not os.path.exists(log_dir):
                    self.log.info('creating log directory {0:s}'.format(log_dir))
                    os.makedirs(log_dir)

                self._log_fh = logging.FileHandler(logfile, 'w', delay=True)
                self._log_fh.setLevel(getattr(logging, self.loglevel))
                self._log_fh.setFormatter(stimela.log_formatter)
                self.log.addHandler(self._log_fh)

        self.resume_file = '.last_{}.json'.format(self.name_)
        # set to default if not set
        # create a folder to store config files
        # if it doesn't exist. These config
        # files can be resued to re-run the
        # task

        self.jobs = []
        self.completed = []
        self.failed = None
        self.remaining = []

        self.pid = os.getpid()

        cmd_line_pf = self.stimela_context.get('_STIMELA_PULLFOLDER', None)
        self.singularity_image_dir = cmd_line_pf or singularity_image_dir or PULLFOLDER
        if self.singularity_image_dir and not self.JOB_TYPE:
            self.JOB_TYPE = "singularity"

        self.log.info('---------------------------------')
        self.log.info('Stimela version {0}'.format(stimela.__version__))
        self.log.info('Running: {:s}'.format(self.name))
        self.log.info('---------------------------------')
        
        self.workdir = None
        self.__make_workdir()

        self.parameter_file_dir = parameter_file_dir or f'{self.workdir}/stimela_parameter_files'
        if not os.path.exists(self.parameter_file_dir):
            self.log.info(
                f'Config directory cannot be found. Will create {self.parameter_file_dir}')
            os.mkdir(self.parameter_file_dir)
Ejemplo n.º 7
0
    def __init__(self, name, data=None,
                 parameter_file_dir=None, ms_dir=None,
                 tag=None, build_label=None,
                 singularity_image_dir=None, JOB_TYPE='docker',
                 cabpath=None,
                 logger=None,
                 log_dir=None, logfile=None, logfile_task=None):
        """
        Deifine and manage a stimela recipe instance.        

        name    :   Name of stimela recipe
        msdir   :   Path of MSs to be used during the execution of the recipe
        tag     :   Use cabs with a specific tag
        parameter_file_dir :   Will store task specific parameter files here

        logger:   if set to a logger object, uses the specified logger.
                  if None, sets up its own logger using the parameters below

        loglevel: default logging level
        log_dir:  default directory for logfiles
        logfile:  name of logfile, False to disable recipe-level logfiles, or None to form a default name

        logfile_task: name of task-level logfile, False to disable task-level logfiles, or None to form a default name.
                      logfile_task may contain a "{task}" entry which will be substituted for a task name.
        """
        self.name = name
        self.name_ = re.sub(r'\W', '_', name)  # pausterized name

        self.cabpath = cabpath

        # set default name for task-level logfiles
        self.logfile_task = "{0}/log-{1}-{{task}}".format(log_dir or ".", self.name) \
            if logfile_task is None else logfile_task

        if logger is not None:
            self.log = logger
        else:
            self.log = stimela.logger().getChild(name)
            self.log.propagate = True # propagate to main stimela logger

            # logfile is False: no logfile at recipe level
            if logfile is not False:
                # logfile is None: use default name
                if logfile is None:
                    logfile = "{0}/log-{1}.txt".format(log_dir or ".", self.name)

                # reset default name for task-level logfiles based on logfile
                self.logfile_task = os.path.splitext(logfile)[0] + "-{task}.txt" \
                            if logfile_task is None else logfile_task

                # ensure directory exists
                log_dir = os.path.dirname(logfile) or "."
                if not os.path.exists(log_dir):
                    self.log.info('creating log directory {0:s}'.format(log_dir))
                    os.makedirs(log_dir)

                fh = logging.FileHandler(logfile, 'w', delay=True)
                fh.setLevel(logging.INFO)
                fh.setFormatter(stimela.log_formatter)
                self.log.addHandler(fh)

        self.JOB_TYPE = JOB_TYPE

        self.resume_file = '.last_{}.json'.format(self.name_)
        self.stimela_context = inspect.currentframe().f_back.f_globals
        self.stimela_path = os.path.dirname(docker.__file__)

        self.build_label = build_label or stimela.CAB_USERNAME
        self.ms_dir = ms_dir
        if not os.path.exists(self.ms_dir):
            self.log.info(
                'MS directory \'{}\' does not exist. Will create it'.format(self.ms_dir))
            os.mkdir(self.ms_dir)
        self.tag = tag
        # create a folder to store config files
        # if it doesn't exist. These config
        # files can be resued to re-run the
        # task
        self.parameter_file_dir = parameter_file_dir or "stimela_parameter_files"
        if not os.path.exists(self.parameter_file_dir):
            self.log.info(
                'Config directory cannot be found. Will create ./{}'.format(self.parameter_file_dir))
            os.mkdir(self.parameter_file_dir)

        self.jobs = []
        self.completed = []
        self.failed = None
        self.remaining = []

        self.pid = os.getpid()
        self.singularity_image_dir = singularity_image_dir
        if self.singularity_image_dir and not self.JOB_TYPE:
            self.JOB_TYPE = "singularity"

        self.log.info('---------------------------------')
        self.log.info('Stimela version {0}'.format(stimela.__version__))
        self.log.info('Sphesihle Makhathini <*****@*****.**>')
        self.log.info('Running: {:s}'.format(self.name))
        self.log.info('---------------------------------')
Ejemplo n.º 8
0
def xrun(command,
         options,
         log=None,
         logfile=None,
         timeout=-1,
         kill_callback=None):
    command_name = command

    # this part could be inside the container
    command = " ".join([command] + list(map(str, options)))

    log = log or get_stimela_logger()

    if log is None:
        return xrun_nolog(command, name=command_name)

    # this part is never inside the container
    import stimela

    log = log or stimela.logger()

    log.info("running " + command,
             extra=dict(stimela_subprocess_output=(command_name, "start")))

    start_time = time.time()

    proc = subprocess.Popen([command],
                            stdin=subprocess.PIPE,
                            stdout=subprocess.PIPE,
                            stderr=subprocess.PIPE,
                            bufsize=1,
                            universal_newlines=True,
                            shell=True)

    poller = Poller(log=log)
    poller.register_process(proc)

    proc_running = True

    try:
        while proc_running and poller.fdlabels:
            fdlist = poller.poll(verbose=DEBUG > 0)
            for fname, fobj in fdlist:
                try:
                    line = fobj.readline()
                except EOFError:
                    line = b''
                empty_line = not line
                line = (line.decode('utf-8')
                        if type(line) is bytes else line).rstrip()
                # break out if process closes
                if empty_line:
                    poller.unregister_file(fobj)
                    if proc.stdout not in poller and proc.stderr not in poller:
                        log.debug("The {} process has exited".format(command))
                        proc_running = None
                        break
                    continue
                # dispatch output to log
                line = _remove_ctrls(line)
                # the extra attributes are filtered by e.g. the CARACal logger
                if fobj is proc.stderr:
                    log.warning(
                        line,
                        extra=dict(stimela_subprocess_output=(command_name,
                                                              "stderr")))
                else:
                    log.info(
                        line,
                        extra=dict(stimela_subprocess_output=(command_name,
                                                              "stdout")))
            if timeout > 0 and time.time() > start_time + timeout:
                log.error("timeout, killing {} process".format(command))
                kill_callback() if callable(kill_callback) else proc.kill()
                proc_running = False

        proc.wait()
        status = proc.returncode

    except SystemExit as exc:
        log.error("{} has exited with code {}".format(command, exc.code))
        proc.wait()
        status = exc.code
        raise StimelaCabRuntimeError('{}: SystemExit with code {}'.format(
            command_name, status))

    except KeyboardInterrupt:
        if callable(kill_callback):
            log.warning(
                "Ctrl+C caught: shutting down {} process, please give it a few moments"
                .format(command_name))
            kill_callback()
            log.info("the {} process was shut down successfully".format(
                command_name),
                     extra=dict(stimela_subprocess_output=(command_name,
                                                           "status")))
        else:
            log.warning(
                "Ctrl+C caught, killing {} process".format(command_name))
            proc.kill()
        proc.wait()
        raise StimelaCabRuntimeError(
            '{} interrupted with Ctrl+C'.format(command_name))

    except Exception as exc:
        traceback.print_exc()
        log.error("Exception caught: {}".format(str(exc)))
        proc.wait()
        raise StimelaCabRuntimeError("{} throws exception '{}'".format(
            command_name, str(exc)))

    if status:
        raise StimelaCabRuntimeError("{} returns error code {}".format(
            command_name, status))

    return status
Ejemplo n.º 9
0
def main(argv):
    # parse initial arguments to init basic switches and modes
    parser = config_parser.basic_parser(argv)
    options, _ = parser.parse_known_args(argv)

    caracal.init_console_logging(boring=options.boring, debug=options.debug)
    stimela.logger().setLevel(logging.DEBUG if options.debug else logging.INFO)

    # user requests worker help
    if options.worker_help:
        if not print_worker_help(options.worker_help):
            parser.error("unknown worker '{}'".format(options.worker_help))
        return

    # User requests default config => dump and exit
    if options.get_default:
        sample_config = SAMPLE_CONFIGS.get(options.get_default_template)
        if sample_config is None:
            parser.error("unknown default template '{}'".format(options.get_default_template))
        sample_config_path = os.path.join(pckgdir, "sample_configurations", sample_config)
        if not os.path.exists(sample_config_path):
            raise RuntimeError("Missing sample config file {}. This is a bug, please report".format(sample_config))
        # validate the file
        try:
            parser = config_parser.config_parser()
            _, version = parser.validate_config(sample_config_path)
            if version != SCHEMA_VERSION:
                log.warning("Sample config file {} version is {}, current CARACal version is {}.".format(sample_config,
                                                                                                         version,
                                                                                                         SCHEMA_VERSION))
                log.warning("Proceeding anyway, but please notify the CARACal team to ship a newer sample config!")
        except config_parser.ConfigErrors as exc:
            log.error("{}, list of errors follows:".format(exc))
            for section, errors in exc.errors.items():
                print("  {}:".format(section))
                for err in errors:
                    print("    - {}".format(err))
            sys.exit(1)  # indicate failure
        log.info("Initializing {1} from config template '{0}' (schema version {2})".format(options.get_default_template,
                                                                                           options.get_default, version))
        shutil.copy2(sample_config_path, options.get_default)
        return

    if options.print_calibrator_standard:
        cdb = mkct.calibrator_database()
        log.info("Found the following reference calibrators (in CASA format):")
        log.info(cdb)
        return

    # if config was not specified (i.e. stayed default), print help and exit
    config_file = options.config
    if config_file == caracal.DEFAULT_CONFIG:
        parser.print_help()
        sys.exit(1)

    try:
        parser = config_parser.config_parser()
        config, version = parser.validate_config(config_file)
        if version != SCHEMA_VERSION:
            log.warning("Config file {} schema version is {}, current CARACal version is {}".format(config_file,
                                    version, SCHEMA_VERSION))
            log.warning("Will try to proceed anyway, but please be advised that configuration options may have changed.")
        # populate parser with items from config
        parser.populate_parser(config)
        # reparse arguments
        caracal.log.info("Loading pipeline configuration from {}".format(config_file), extra=dict(color="GREEN"))
        options, config = parser.update_config_from_args(config, argv)
        # raise warning on schema version
    except config_parser.ConfigErrors as exc:
        log.error("{}, list of errors follows:".format(exc))
        for section, errors in exc.errors.items():
            print("  {}:".format(section))
            for err in errors:
                print("    - {}".format(err))
        sys.exit(1)  # indicate failure
    except Exception as exc:
        traceback.print_exc()
        log.error("Error parsing arguments or configuration: {}".format(exc))
        if options.debug:
            log.warning("you are running with -debug enabled, dropping you into pdb. Use Ctrl+D to exit.")
            pdb.post_mortem(sys.exc_info()[2])
        sys.exit(1)  # indicate failure

    if options.report and options.no_reports:
        log.error("-report contradicts --no-reports")
        sys.exit(1)

    log_logo()
    # Very good idea to print user options into the log before running:
    parser.log_options(config)

    execute_pipeline(options, config, block=True)