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
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
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
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
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)
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
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