예제 #1
0
def validate_translation(p4_file, target_dir, p4c_bin):
    log.info("\n\n" + "-" * 70)
    log.info("Analysing %s", p4_file)
    util.check_dir(target_dir)
    fail_dir = target_dir.joinpath("failed")
    # run the p4 compiler and dump all the passes for this file
    passes = gen_p4_passes(p4c_bin, target_dir, p4_file)
    passes = prune_passes(passes)
    p4_py_files = []
    # for each emitted pass, generate a python representation
    for p4_pass in passes:
        p4_path = Path(p4_pass).stem
        py_file = f"{target_dir}/{p4_path}.py"
        result = run_p4_to_py(p4_pass, py_file)
        if result.returncode != util.EXIT_SUCCESS:
            log.error("Failed to translate P4 to Python.")
            log.error("Compiler crashed!")
            util.check_dir(fail_dir)
            with open(f"{fail_dir}/error.txt", 'w+') as err_file:
                err_file.write(result.stderr.decode("utf-8"))
            util.copy_file([p4_pass, py_file], fail_dir)
            return result.returncode
        p4_py_files.append(f"{target_dir}/{p4_path}")
    if len(p4_py_files) < 2:
        log.warning("P4 file did not generate enough passes!")
        return util.EXIT_SKIPPED
    # perform the actual comparison
    result = z3check.z3_check(p4_py_files, fail_dir)
    return result
def validate_translation(p4_file,
                         target_dir,
                         p4c_bin,
                         allow_undef=False,
                         dump_info=False):
    info = INFO

    # customize the main info with the new information
    info["compiler"] = str(p4c_bin)
    info["exit_code"] = util.EXIT_SUCCESS
    info["p4z3_bin"] = str(P4Z3_BIN)
    info["out_dir"] = str(target_dir)
    info["input_file"] = str(p4_file)
    info["allow_undef"] = allow_undef
    info["validation_bin"] = f"python3 {__file__}"

    log.info("\n" + "-" * 70)
    log.info("Analysing %s", p4_file)
    start_time = datetime.now()
    util.check_dir(target_dir)
    fail_dir = target_dir.joinpath("failed")
    # run the p4 compiler and dump all the passes for this file
    passes = gen_p4_passes(p4c_bin, target_dir, p4_file)
    passes = prune_passes(passes)
    p4_py_files = []
    # for each emitted pass, generate a python representation
    for p4_pass in passes:
        p4_path = Path(p4_pass).stem
        py_file = f"{target_dir}/{p4_path}.py"
        result = run_p4_to_py(p4_pass, py_file)
        if result.returncode != util.EXIT_SUCCESS:
            log.error("Failed to translate P4 to Python.")
            log.error("Compiler crashed!")
            util.check_dir(fail_dir)
            with open(f"{fail_dir}/error.txt", 'w+') as err_file:
                err_file.write(result.stderr.decode("utf-8"))
            util.copy_file([p4_pass, py_file], fail_dir)
            return result.returncode
        p4_py_files.append(f"{target_dir}/{p4_path}")
    if len(p4_py_files) < 2:
        log.warning("P4 file did not generate enough passes!")
        return util.EXIT_SKIPPED
    # perform the actual comparison
    result, check_info = z3check.z3_check(p4_py_files, fail_dir, allow_undef)
    # merge the two info dicts
    info["exit_code"] = result
    info = {**info, **check_info}
    done_time = datetime.now()
    elapsed = done_time - start_time
    time_str = time.strftime("%H hours %M minutes %S seconds",
                             time.gmtime(elapsed.total_seconds()))
    ms = elapsed.microseconds / 1000
    log.info("Translation validation took %s %s milliseconds.", time_str, ms)
    if dump_info:
        json_name = target_dir.joinpath(f"{p4_file.stem}_info.json")
        log.info("Dumping configuration to %s.", json_name)
        with open(json_name, 'w') as json_file:
            json.dump(info, json_file, indent=2, sort_keys=True)
    return result
예제 #3
0
def get_semantics(config):
    p4_input = config["p4_input"]
    out_dir = config["out_dir"]
    py_file = Path(f"{out_dir}/{p4_input.stem}.py")
    fail_dir = out_dir.joinpath("failed")

    result = run_p4_to_py(p4_input, py_file, config)
    if result.returncode != util.EXIT_SUCCESS:
        log.error("Failed to translate P4 to Python.")
        util.check_dir(fail_dir)
        with open(f"{fail_dir}/error.txt", 'w+') as err_file:
            err_file.write(result.stderr.decode("utf-8"))
        util.copy_file([p4_input, py_file], fail_dir)
        return None, result.returncode
    z3_prog, result = z3check.get_z3_formulization(py_file, fail_dir)
    if result != util.EXIT_SUCCESS:
        return None, result
    return z3_prog, util.EXIT_SUCCESS
예제 #4
0
def perform_blackbox_test(config):
    out_dir = config["out_dir"]
    p4_input = config["p4_input"]
    if out_dir == OUT_DIR:
        out_dir = out_dir.joinpath(p4_input.stem)
    util.check_dir(out_dir)
    util.copy_file(p4_input, out_dir)
    config["out_dir"] = out_dir
    config["p4_input"] = p4_input

    main_formula, pkt_range = get_main_formula(config)
    if main_formula == None or not pkt_range:
        return util.EXIT_FAILURE
    conditions = set()
    # FIXME: Another hack to deal with branch conditions we cannot control
    for child in main_formula.children()[pkt_range]:
        conditions |= get_branch_conditions(child)
    cond_tuple = dissect_conds(config, conditions)
    stf_str = build_test(config, main_formula, cond_tuple, pkt_range)
    # finally, run the test with the stf string we have assembled
    # and return the result of course
    return run_stf_test(config, stf_str)
def diff_files(passes, pass_dir, p4_file):

    p4_name = p4_file.name.stem
    for index, p4_pass in enumerate(passes[1:]):
        pass_before = passes[index - 1]
        pass_after = passes[index]
        diff_dir = f"{pass_dir}/{p4_name}"
        util.check_dir(diff_dir)
        diff_file = f"{diff_dir}/{p4_name}_{p4_pass}.diff"
        diff_cmd = "diff -rupP "
        diff_cmd += "--label=\"before_pass\" --label=\"after_pass\" "
        diff_cmd += f"{pass_before} {pass_after}"
        diff_cmd += f"> {diff_file}"
        log.debug("Creating a diff of the file")
        log.debug("Command: %s", diff_cmd)
        util.exec_process(diff_cmd)
        if os.stat(diff_file).st_size == 0:
            os.remove(diff_file)
        else:
            after_name = f"{diff_dir}/{p4_name}_{p4_pass}{p4_file.suffix}"
            util.copy_file(pass_after, after_name)
            og_name = f"{diff_dir}/{p4_name}_original{p4_file.suffix}"
            util.copy_file(p4_file, og_name)
    return util.EXIT_SUCCESS
예제 #6
0
def validate_translation(p4_file, target_dir, p4c_bin, allow_undef=False):
    log.info("\n\n" + "-" * 70)
    log.info("Analysing %s", p4_file)
    start_time = datetime.now()
    util.check_dir(target_dir)
    fail_dir = target_dir.joinpath("failed")
    # run the p4 compiler and dump all the passes for this file
    passes = gen_p4_passes(p4c_bin, target_dir, p4_file)
    passes = prune_passes(passes)
    p4_py_files = []
    # for each emitted pass, generate a python representation
    for p4_pass in passes:
        p4_path = Path(p4_pass).stem
        py_file = f"{target_dir}/{p4_path}.py"
        result = run_p4_to_py(p4_pass, py_file)
        if result.returncode != util.EXIT_SUCCESS:
            log.error("Failed to translate P4 to Python.")
            log.error("Compiler crashed!")
            util.check_dir(fail_dir)
            with open(f"{fail_dir}/error.txt", 'w+') as err_file:
                err_file.write(result.stderr.decode("utf-8"))
            util.copy_file([p4_pass, py_file], fail_dir)
            return result.returncode
        p4_py_files.append(f"{target_dir}/{p4_path}")
    if len(p4_py_files) < 2:
        log.warning("P4 file did not generate enough passes!")
        return util.EXIT_SKIPPED
    # perform the actual comparison
    result = z3check.z3_check(p4_py_files, fail_dir, allow_undef)
    done_time = datetime.now()
    elapsed = done_time - start_time
    time_str = time.strftime("%H hours %M minutes %S seconds",
                             time.gmtime(elapsed.total_seconds()))
    ms = elapsed.microseconds / 1000
    log.info("Translation validation took %s %s milliseconds.", time_str, ms)
    return result
예제 #7
0
def handle_pyz3_error(fail_dir, p4_file):
    util.check_dir(fail_dir)
    failed = [p4_file.with_suffix(".py"), p4_file.with_suffix(".p4i")]
    util.copy_file(failed, fail_dir)
예제 #8
0
def perform_blackbox_test(config):
    out_dir = config["out_dir"]
    p4_input = config["p4_input"]
    if out_dir == OUT_DIR:
        out_dir = out_dir.joinpath(p4_input.stem)
    util.check_dir(out_dir)
    util.copy_file(p4_input, out_dir)
    config["out_dir"] = out_dir
    config["p4_input"] = p4_input

    # get the semantic representation of the original program
    z3_main_prog, result = get_semantics(config)
    if result != util.EXIT_SUCCESS:
        return result
    # now we actually verify that we can find an input
    s = z3.Solver()
    # we currently ignore all other pipelines and focus on the ingress pipeline
    main_formula = z3.simplify(z3_main_prog[config["pipe_name"]])
    # this util might come in handy later.
    # z3.z3util.get_vars(main_formula)
    conditions = get_branch_conditions(main_formula)
    log.info(conditions)
    cond_tuple = dissect_conds(config, conditions)
    permut_conds, avoid_conds, undefined_conds = cond_tuple
    log.info("Computing permutations...")
    # FIXME: This does not scale well...
    # FIXME: This is a complete hack, use a more sophisticated method
    permuts = [[f(var) for var, f in zip(permut_conds, x)]
               for x in itertools.product([z3.Not, lambda x: x],
                                          repeat=len(permut_conds))]
    output_const = z3.Const("output", main_formula.sort())
    # bind the output constant to the output of the main program
    s.add(main_formula == output_const)
    # all keys must be false for now
    # FIXME: Some of them should be usable
    log.info(15 * "#")
    log.info("Undefined conditions:")
    s.add(z3.And(*undefined_conds))
    for cond in undefined_conds:
        log.info(cond)
    log.info("Conditions to avoid:")
    s.add(z3.Not(z3.Or(*avoid_conds)))
    for cond in avoid_conds:
        log.info(cond)
    log.info("Permissible permutations:")
    for cond in permuts:
        log.info(cond)
    log.info(15 * "#")
    for permut in permuts:
        s.push()
        s.add(permut)
        log.info("Checking for solution...")
        ret = s.check()
        if ret == z3.sat:
            log.info("Found a solution!")
            # get the model
            m = s.model()
            # this does not work well yet... desperate hack
            # FIXME: Figure out a way to solve this, might not be solvable
            avoid_matches = z3.Not(z3.Or(*avoid_conds))
            undefined_matches = z3.And(*undefined_conds)
            permut_match = z3.And(*permut)
            g = z3.Goal()
            g.add(main_formula == output_const, avoid_matches,
                  undefined_matches, permut_match)
            log.debug(z3.tactics())
            t = z3.Then(z3.Tactic("propagate-values"),
                        z3.Tactic("ctx-solver-simplify"),
                        z3.Tactic("elim-and"))
            log.info("Inferring simplified input and output")
            constrained_output = t.apply(g)
            log.info("Inferring dont-care map...")
            output_var = constrained_output[0][0]
            dont_care_map = get_dont_care_map(config, output_var)
            result = check_with_stf(config, m, output_const, dont_care_map)
            if result != util.EXIT_SUCCESS:
                return result
        else:
            # FIXME: This should be an error
            log.warning("No valid input could be found!")
        s.pop()
    return result
예제 #9
0
def run_tofino_test(out_dir, p4_input, stf_file_name):
    # we need to change the working directory
    # tofino scripts make some assumptions where to dump files
    prog_name = p4_input.stem
    # we need to create a specific test dir in which we can run tests
    test_dir = out_dir.joinpath("test_dir")
    util.check_dir(test_dir)
    util.copy_file(stf_file_name, test_dir)
    template_name = test_dir.joinpath(f"{prog_name}.py")
    # use a test template that runs stf tests
    util.copy_file(f"{FILE_DIR}/tofino_test_template.py", template_name)

    # initialize the target install
    log.info("Building the tofino target...")
    config_cmd = f"{TOFINO_DIR}/pkgsrc/p4-build/configure "
    config_cmd += f"--with-tofino --with-p4c=bf-p4c "
    config_cmd += f"--prefix={TOFINO_DIR}/install "
    config_cmd += f"--bindir={TOFINO_DIR}/install/bin "
    config_cmd += f"P4_NAME={prog_name} "
    config_cmd += f"P4_PATH={p4_input.resolve()} "
    config_cmd += f"P4_VERSION=p4-16 "
    config_cmd += f"P4_ARCHITECTURE=tna "
    result = util.exec_process(config_cmd, cwd=out_dir)
    if result.returncode != util.EXIT_SUCCESS:
        return result, result.stdout, result.stderr
    # create the target
    make_cmd = f"make -C {out_dir} "
    result = util.exec_process(make_cmd)
    if result.returncode != util.EXIT_SUCCESS:
        return result, result.stdout, result.stderr
    # install the target in the tofino folder
    make_cmd = f"make install -C {out_dir} "
    result = util.exec_process(make_cmd)
    if result.returncode != util.EXIT_SUCCESS:
        return result, result.stdout, result.stderr
    procs = []
    test_proc = None
    # start the target in the background
    log.info("Starting the tofino model...")
    os_env = os.environ.copy()
    os_env["SDE"] = f"{TOFINO_DIR}"
    os_env["SDE_INSTALL"] = f"{TOFINO_DIR}/install"

    model_cmd = f"{TOFINO_DIR}/run_tofino_model.sh "
    model_cmd += f"-p {prog_name} "
    proc = util.start_process(model_cmd,
                              preexec_fn=os.setsid,
                              env=os_env,
                              cwd=out_dir)
    procs.append(proc)
    # start the binary for the target in the background
    log.info("Launching switchd...")
    os_env = os.environ.copy()
    os_env["SDE"] = f"{TOFINO_DIR}"
    os_env["SDE_INSTALL"] = f"{TOFINO_DIR}/install"

    switch_cmd = f"{TOFINO_DIR}/run_switchd.sh "
    switch_cmd += f"--arch tofino "
    switch_cmd += f"-p {prog_name} "
    proc = util.start_process(switch_cmd,
                              preexec_fn=os.setsid,
                              env=os_env,
                              cwd=out_dir)
    procs.append(proc)

    # wait for a bit
    time.sleep(2)
    # finally we can run the test
    log.info("Running the actual test...")
    test_cmd = f"{TOFINO_DIR}/run_p4_tests.sh "
    test_cmd += f"-t {test_dir} "
    os_env = os.environ.copy()
    os_env["SDE"] = f"{TOFINO_DIR}"
    os_env["SDE_INSTALL"] = f"{TOFINO_DIR}/install"
    os_env["PYTHONPATH"] = f"${{PYTHONPATH}}:{FILE_DIR}"
    test_proc = util.start_process(test_cmd, env=os_env, cwd=out_dir)

    def signal_handler(sig, frame):
        log.warning("run_tofino_test: Caught Interrupt, exiting...")
        cleanup(procs)
        os.kill(test_proc.pid, signal.SIGINT)
        os.kill(test_proc.pid, signal.SIGTERM)
        sys.exit(1)

    signal.signal(signal.SIGINT, signal_handler)
    signal.signal(signal.SIGTERM, signal_handler)

    stdout, stderr = test_proc.communicate()
    cleanup(procs)
    return test_proc, stdout, stderr