Пример #1
0
def main():
    import catsoop.base_context as base_context
    from catsoop.process import set_pdeathsig

    # Make sure the checker database is set up
    checker_db_loc = os.path.join(base_context.cs_data_root, "_logs",
                                  "_checker")

    for subdir in ("queued", "running", "results"):
        os.makedirs(os.path.join(checker_db_loc, subdir), exist_ok=True)

    procs = [
        (scripts_dir, [sys.executable, "checker.py"], 0.1, "Checker"),
        (scripts_dir, [sys.executable, "reporter.py"], 0.1, "Reporter"),
    ]

    # set up WSGI options

    if base_context.cs_wsgi_server == "cheroot":
        wsgi_ports = base_context.cs_wsgi_server_port

        if not isinstance(wsgi_ports, list):
            wsgi_ports = [wsgi_ports]

        for port in wsgi_ports:
            procs.append((
                scripts_dir,
                [sys.executable, "wsgi_server.py",
                 str(port)],
                0.1,
                "WSGI Server at Port %d" % port,
            ))
    elif base_context.cs_wsgi_server == "uwsgi":
        if (base_context.cs_wsgi_server_min_processes >=
                base_context.cs_wsgi_server_max_processes):
            uwsgi_opts = [
                "--processes",
                str(base_context.cs_wsgi_server_min_processes)
            ]
        else:
            uwsgi_opts = [
                "--cheaper",
                str(base_context.cs_wsgi_server_min_processes),
                "--workers",
                str(base_context.cs_wsgi_server_max_processes),
                "--cheaper-step",
                "1",
                "--cheaper-initial",
                str(base_context.cs_wsgi_server_min_processes),
            ]

        uwsgi_opts = [
            "--http",
            ":%s" % base_context.cs_wsgi_server_port,
            "--thunder-lock",
            "--wsgi-file",
            "wsgi.py",
            "--touch-reload",
            "wsgi.py",
        ] + uwsgi_opts

        procs.append((base_dir, ["uwsgi"] + uwsgi_opts, 0.1, "WSGI Server"))
    else:
        raise ValueError("unsupported wsgi server: %r" %
                         base_context.cs_wsgi_server)

    running = []

    for (ix, (wd, cmd, slp, name)) in enumerate(procs):
        print("Starting %s (cmd=%s)" % (name, cmd))
        running.append(
            subprocess.Popen(cmd,
                             cwd=wd,
                             preexec_fn=set_pdeathsig(signal.SIGTERM)))
        time.sleep(slp)

    def _kill_children():
        for ix, i in enumerate(running):
            os.kill(i.pid, signal.SIGTERM)

    atexit.register(_kill_children)

    while True:
        time.sleep(1)
Пример #2
0
def do_check(row):
    os.setpgrp()  # make this part of its own process group
    set_pdeathsig()()  # but make it die if the parent dies.  will this work?

    context = loader.spoof_early_load(row['path'])
    context['cs_course'] = row['path'][0]
    context['cs_path_info'] = row['path']
    context['cs_username'] = row['username']
    context['cs_user_info'] = {'username': row['username']}
    context['cs_user_info'] = auth.get_user_information(context)
    context['cs_now'] = datetime.fromtimestamp(row['time'])
    cfile = dispatch.content_file_location(context, row['path'])
    loader.do_late_load(context, context['cs_course'], context['cs_path_info'], context, cfile)

    namemap = collections.OrderedDict()
    for elt in context['cs_problem_spec']:
        if isinstance(elt, tuple):
            m = elt[1]
            namemap[m['csq_name']] = elt

    # now, depending on the action we want, take the appropriate steps

    names_done = set()
    for name in row['names']:
        if name.startswith('__'):
            name = name[2:].rsplit('_', 1)[0]
        if name in names_done:
            continue
        names_done.add(name)
        question, args = namemap[name]
        if row['action'] == 'submit':
            try:
                resp = question['handle_submission'](row['form'],
                                                     **args)
                score = resp['score']
                msg = resp['msg']
                extra = resp.get('extra_data', None)
            except:
                resp = {}
                score = 0.0
                msg = exc_message(context)
                extra = None

            score_box = context['csm_tutor'].make_score_display(context, args,
                                                                name, score,
                                                                True)

        elif row['action'] == 'check':
            try:
                msg = question['handle_check'](row['form'], **args)
            except:
                msg = exc_message(context)

            score = None
            score_box = ''
            extra = None

        row['score'] = score
        row['score_box'] = score_box
        row['response'] = language.handle_custom_tags(context, msg)
        row['extra_data'] = extra

        # make temporary file to write results to
        _, temploc = tempfile.mkstemp()
        with open(temploc, 'w') as f:
            f.write(context['csm_cslog'].prep(row))
        # move that file to results, close the handle to it.
        magic = row['magic']
        newloc = os.path.join(RESULTS, magic[0], magic[1], magic)
        os.makedirs(os.path.dirname(newloc), exist_ok=True)
        shutil.move(temploc, newloc)
        try:
            os.close(_)
        except:
            pass
        # then remove from running
        os.unlink(os.path.join(RUNNING, row['magic']))
        # finally, update the appropriate log
        lockname = context['csm_cslog'].get_log_filename(row['username'], row['path'], 'problemstate')
        with context['csm_cslog'].FileLock(lockname) as lock:
            x = context['csm_cslog'].most_recent(row['username'], row['path'], 'problemstate', {}, lock=False)
            if row['action'] == 'submit':
                x.setdefault('scores', {})[name] = row['score']
            x.setdefault('score_displays', {})[name] = row['score_box']
            x.setdefault('cached_responses', {})[name] = row['response']
            x.setdefault('extra_data', {})[name] = row['extra_data']
            context['csm_cslog'].overwrite_log(row['username'], row['path'], 'problemstate', x, lock=False)
Пример #3
0
def main():
    import catsoop.base_context as base_context
    import catsoop.loader as loader
    from catsoop.process import set_pdeathsig

    # Make sure the checker database is set up
    checker_db_loc = os.path.join(base_context.cs_data_root, "_logs",
                                  "_checker")

    for subdir in ("queued", "running", "results", "staging"):
        os.makedirs(os.path.join(checker_db_loc, subdir), exist_ok=True)

    procs = [
        (scripts_dir, [sys.executable, "checker.py"], 0.1, "Checker"),
        (scripts_dir, [sys.executable, "reporter.py"], 0.1, "Reporter"),
    ]

    # put plugin autostart scripts into the list

    ctx = loader.generate_context([])
    for plugin in loader.available_plugins(ctx, course=None):
        script_dir = os.path.join(plugin, "autostart")
        if os.path.isdir(script_dir):
            for script in sorted(os.listdir(script_dir)):
                if not script.endswith(".py"):
                    continue
                procs.append((
                    script_dir,
                    [sys.executable, script],
                    0.1,
                    os.path.join(script_dir, script),
                ))

    # set up WSGI options

    if base_context.cs_wsgi_server == "cheroot":
        print("[start_catsoop] Using cheroot for web service")
        wsgi_ports = base_context.cs_wsgi_server_port

        if not isinstance(wsgi_ports, list):
            wsgi_ports = [wsgi_ports]

        for port in wsgi_ports:
            procs.append((
                scripts_dir,
                [sys.executable, "wsgi_server.py",
                 str(port)],
                0.1,
                "WSGI Server at Port %d" % port,
            ))
    elif base_context.cs_wsgi_server == "uwsgi":
        print("[start_catsoop] Using uwsgi for web service")
        if (base_context.cs_wsgi_server_min_processes >=
                base_context.cs_wsgi_server_max_processes):
            uwsgi_opts = [
                "--processes",
                str(base_context.cs_wsgi_server_min_processes)
            ]
        else:
            uwsgi_opts = [
                "--cheaper",
                str(base_context.cs_wsgi_server_min_processes),
                "--workers",
                str(base_context.cs_wsgi_server_max_processes),
                "--cheaper-step",
                "1",
                "--cheaper-initial",
                str(base_context.cs_wsgi_server_min_processes),
            ]

        uwsgi_opts = [
            "--http",
            ":%s" % base_context.cs_wsgi_server_port,
            "--thunder-lock",
            "--wsgi-file",
            "wsgi.py",
            "--touch-reload",
            "wsgi.py",
        ] + uwsgi_opts

        procs.append((base_dir, ["uwsgi"] + uwsgi_opts, 0.1, "WSGI Server"))
    else:
        raise ValueError("unsupported wsgi server: %r" %
                         base_context.cs_wsgi_server)

    running = []

    for (ix, (wd, cmd, slp, name)) in enumerate(procs):
        LOGGER.error("[start_catsoop] Starting %s (cmd=%s, wd=%s)" %
                     (name, cmd, wd))
        running.append(
            subprocess.Popen(cmd,
                             cwd=wd,
                             preexec_fn=set_pdeathsig(signal.SIGTERM)))
        LOGGER.error("[start_catsoop]     %s has pid=%s" %
                     (name, running[-1].pid))
        time.sleep(slp)

    def _kill_children():
        for ix, i in enumerate(running):
            os.kill(i.pid, signal.SIGTERM)

    atexit.register(_kill_children)

    while True:
        for idx, (procinfo, proc) in enumerate(zip(
                procs, running)):  # restart running process if it has died
            if proc.poll() is not None:
                (wd, cmd, slp, name) = procinfo
                LOGGER.error(
                    '[start_catsoop] %s (pid=%s) was killed, restarting it' %
                    (name, proc.pid))
                running[idx] = subprocess.Popen(cmd,
                                                cwd=wd,
                                                preexec_fn=set_pdeathsig(
                                                    signal.SIGTERM))
                LOGGER.error(
                    "[start_catsoop] Starting %s (cmd=%s, wd=%s) as pid=%s" %
                    (name, cmd, wd, running[idx].pid))
        time.sleep(1)
Пример #4
0
            '--cheaper-step', '1',
            '--cheaper-initial', str(base_context.cs_wsgi_server_min_processes),
        ]

    uwsgi_opts = [
        '--http', ':%s' % base_context.cs_wsgi_server_port,
        '--thunder-lock',
        '--wsgi-file', os.path.join('catsoop', 'wsgi.py'),
        '--touch-reload', os.path.join('catsoop', 'wsgi.py'),
    ] + uwsgi_opts

    procs.append((base_dir, ['uwsgi'] + uwsgi_opts, 0.1, 'WSGI Server'))
else:
    raise ValueError('unsupported wsgi server: %r' % base_context.cs_wsgi_server)

running = []

for (ix, (wd, cmd, slp, name)) in enumerate(procs):
    print('Starting', name)
    running.append(subprocess.Popen(cmd, cwd=wd,
                                    preexec_fn=set_pdeathsig(signal.SIGTERM)))
    time.sleep(slp)

def _kill_children():
    for ix, i in enumerate(running):
        os.kill(i.pid, signal.SIGTERM)
atexit.register(_kill_children)

while True:
    time.sleep(1)
Пример #5
0
def do_check(row):
    """
    Check submission, dispatching to appropriate question handler

    row: (dict) action to take, with input data
    """
    os.setpgrp()  # make this part of its own process group
    set_pdeathsig()()  # but make it die if the parent dies.  will this work?

    context = loader.spoof_early_load(row["path"])
    context["cs_course"] = row["path"][0]
    context["cs_path_info"] = row["path"]
    context["cs_username"] = row["username"]
    context["cs_user_info"] = {"username": row["username"]}
    context["cs_user_info"] = auth.get_user_information(context)
    context["cs_now"] = datetime.fromtimestamp(row["time"])

    have_lti = ("cs_lti_config" in context) and ("lti_data" in row)
    if have_lti:
        lti_data = row["lti_data"]
        lti_handler = lti.lti4cs_response(
            context, lti_data)  # LTI response handler, from row['lti_data']
        log("lti_handler.have_data=%s" % lti_handler.have_data)
        if lti_handler.have_data:
            log("lti_data=%s" % lti_handler.lti_data)
            if not "cs_session_data" in context:
                context["cs_session_data"] = {}
            context["cs_session_data"][
                "is_lti_user"] = True  # so that course preload.py knows

    cfile = dispatch.content_file_location(context, row["path"])
    log("Loading grader python code course=%s, cfile=%s" %
        (context["cs_course"], cfile))
    loader.do_late_load(context, context["cs_course"], context["cs_path_info"],
                        context, cfile)

    namemap = collections.OrderedDict()
    cnt = 0
    total_possible_npoints = 0
    for elt in context["cs_problem_spec"]:
        if isinstance(elt,
                      tuple):  # each elt is (problem_context, problem_kwargs)
            m = elt[1]
            namemap[m["csq_name"]] = elt
            csq_npoints = m.get("csq_npoints", 0)
            total_possible_npoints += (
                csq_npoints)  # used to compute total aggregate score pct
            if DEBUG:
                question = elt[0]["handle_submission"]
                dn = m.get("csq_display_name")
                log("Map: %s (%s) -> %s" % (m["csq_name"], dn, question))
                log("%s csq_npoints=%s, total_points=%s" %
                    (dn, csq_npoints, elt[0]["total_points"]()))
            cnt += 1
    if DEBUG:
        log("Loaded %d procedures into question namemap (total_possible_npoints=%s)"
            % (cnt, total_possible_npoints))

    # now, depending on the action we want, take the appropriate steps

    names_done = set()
    for name in row["names"]:
        if name.startswith("__"):
            name = name[2:].rsplit("_", 1)[0]
        if name in names_done:
            continue
        names_done.add(name)
        question, args = namemap[name]
        if row["action"] == "submit":
            if DEBUG:
                log("submit name=%s, row=%s" % (name, row))
            try:
                handler = question["handle_submission"]
                if DEBUG:
                    log("handler=%s" % handler)
                resp = handler(row["form"], **args)
                score = resp["score"]
                msg = resp["msg"]
                extra = resp.get("extra_data", None)
            except Exception as err:
                resp = {}
                score = 0.0
                log("Failed to handle submission, err=%s" % str(err))
                log("Traceback=%s" % traceback.format_exc())
                msg = exc_message(context)
                extra = None

            if DEBUG:
                log("submit resp=%s, msg=%s" % (resp, msg))

            score_box = context["csm_tutor"].make_score_display(
                context, args, name, score, True)

        elif row["action"] == "check":
            try:
                msg = question["handle_check"](row["form"], **args)
            except:
                msg = exc_message(context)

            score = None
            score_box = ""
            extra = None

            if DEBUG:
                log("check name=%s, msg=%s" % (name, msg))

        row["score"] = score
        row["score_box"] = score_box
        row["response"] = language.handle_custom_tags(context, msg)
        row["extra_data"] = extra

        # make temporary file to write results to
        _, temploc = tempfile.mkstemp()
        with open(temploc, "wb") as f:
            f.write(context["csm_cslog"].prep(row))
        # move that file to results, close the handle to it.
        magic = row["magic"]
        newloc = os.path.join(RESULTS, magic[0], magic[1], magic)
        os.makedirs(os.path.dirname(newloc), exist_ok=True)
        shutil.move(temploc, newloc)
        try:
            os.close(_)
        except:
            pass
        # then remove from running
        os.unlink(os.path.join(RUNNING, row["magic"]))
        # finally, update the appropriate log
        cm = context["csm_cslog"].log_lock(
            [row["username"], *row["path"], "problemstate"])
        with cm as lock:
            x = context["csm_cslog"].most_recent(row["username"],
                                                 row["path"],
                                                 "problemstate", {},
                                                 lock=False)
            if row["action"] == "submit":
                x.setdefault("scores", {})[name] = row["score"]
            x.setdefault("score_displays", {})[name] = row["score_box"]
            x.setdefault("cached_responses", {})[name] = row["response"]
            x.setdefault("extra_data", {})[name] = row["extra_data"]
            context["csm_cslog"].overwrite_log(row["username"],
                                               row["path"],
                                               "problemstate",
                                               x,
                                               lock=False)

            # update LTI tool consumer with new aggregate score
            if have_lti and lti_handler.have_data:
                aggregate_score = 0
                cnt = 0
                for k, v in x["scores"].items(
                ):  # e.g. 'scores': {'q000000': 1.0, 'q000001': True, 'q000002': 1.0}
                    aggregate_score += float(v)
                    cnt += 1
                if total_possible_npoints == 0:
                    total_possible_npoints = 1.0
                    LOGGER.error("[checker] total_possible_npoints=0 ????")
                aggregate_score_fract = (aggregate_score * 1.0 /
                                         total_possible_npoints
                                         )  # LTI wants score in [0, 1.0]
                log("Computed aggregate score from %d questions, aggregate_score=%s (fraction=%s)"
                    % (cnt, aggregate_score, aggregate_score_fract))
                log("magic=%s sending aggregate_score_fract=%s to LTI tool consumer"
                    % (row["magic"], aggregate_score_fract))
                try:
                    lti_handler.send_outcome(aggregate_score_fract)
                except Exception as err:
                    LOGGER.error(
                        "[checker] failed to send outcome to LTI consumer, err=%s"
                        % str(err))
                    LOGGER.error("[checker] traceback=%s" %
                                 traceback.format_exc())