diff --git a/deploy_mock/scripts/build_compose.py b/deploy_mock/scripts/build_compose.py index 38fd0e022dc388e31a83d6d8e79be94de25bc5f7..80329a8e7046fa81b75b1ab640ef68aad7ddfb99 100644 --- a/deploy_mock/scripts/build_compose.py +++ b/deploy_mock/scripts/build_compose.py @@ -1,4 +1,7 @@ +"""Utility generating a compose.yaml file""" + import os +from io import TextIOWrapper from pathlib import Path from typing import Optional @@ -8,16 +11,35 @@ FILEDIR = os.path.dirname(__file__) CPUS = os.cpu_count() -def get_text_file(filename: str): +def get_text_file(filename: str) -> TextIOWrapper: + """Utility opening a file in the same directory""" return open(os.path.join(FILEDIR, filename), "r", encoding="utf-8") def build_compose_file( clients: int = 2, port: int = 8000, - mem_limit: int = 100, + mem_limit: int = 30, cpu_limit: Optional[float] = None, -): +) -> None: + """Utility recursively generating a compose.yaml file + + Parameters + ---------- + clients: int, default=2 + The number of clients + port: int, default=8000 + The host port on which to hook the server container. + Clients will hook sequentially on the next ports, + e.g., 8001, 8002, ... + mem_limit: int, default=30 + Maximum number of MB available to each client + cpu_limit: optional float + Maximim percentage of the CPU to be used by an + individual client. If None, default to + 0.7* <number_of_cpus> / <number of clients> + """ + # Getting templates with get_text_file("compose_server.txt") as file: server_string = file.read().replace("$HOSTPORT$", str(port)) diff --git a/deploy_mock/scripts/client.py b/deploy_mock/scripts/client.py index cd5547b9f36272a27ce42d90e29246971d1011ac..97c86bb2d48b35c3c6b9a029cced78ca92f0ebfa 100644 --- a/deploy_mock/scripts/client.py +++ b/deploy_mock/scripts/client.py @@ -1,3 +1,5 @@ +"Mock client python script" + import sys import fire @@ -9,6 +11,11 @@ from comms import message_pb2, message_pb2_grpc def run(server: str = "server", port: str = "8000"): + + """Read data.csv, peform a simple calcultaion and send + its result to the server, awaiting its response to close + """ + print("Calculating result") df = pd.read_csv("data.csv") out = int(df.to_numpy().sum() ** 2) diff --git a/deploy_mock/scripts/client.sh b/deploy_mock/scripts/client.sh index 8502baec038d9b54ff1fc5cc13fc4658691a5139..3f321b196917271f976bcc46234f8f83048a18c4 100644 --- a/deploy_mock/scripts/client.sh +++ b/deploy_mock/scripts/client.sh @@ -1,2 +1,2 @@ -# Run the client python script with the the server address as input +# Run the client python script python -u run.py diff --git a/deploy_mock/scripts/server.py b/deploy_mock/scripts/server.py index e420fb54f2500118da08cd7e6eb2eeb67609f852..9654a2c2b823681a1cf8d9c4f4c3d0f7717340de 100644 --- a/deploy_mock/scripts/server.py +++ b/deploy_mock/scripts/server.py @@ -14,15 +14,30 @@ FILEDIR = os.path.dirname(__file__) class Servicer(message_pb2_grpc.BasicMessageServicer): + """Servicer for the GRPC server""" + def __init__(self): self.responses = [] def SendMessage(self, request, context): + """Python counterpart of the SendMessage method + defined in the .proto file""" self.responses.append(request.res) return message_pb2.BasicReply(ok=f"{request.res}") -def serve(clients: int = 5, port: str = "8000"): +def serve(clients: int = 2, port: str = "8000") -> None: + """Mian server script. Open a communication line, wait for all + clients to send their results, close once all rsuslts were received. + + Parameters + ---------- + clients: int, default=2 + The number of clients + port: int, default=8000 + The host port on which to hook the server container. + """ + # Set up server = grpc.server(futures.ThreadPoolExecutor(max_workers=10)) servicer = Servicer() @@ -32,7 +47,7 @@ def serve(clients: int = 5, port: str = "8000"): server.start() print("Server started, listening on " + port) # Monitoring results - target_sum = sum((i**2 for i in range(1,clients+1))) + target_sum = sum((i**2 for i in range(1, clients + 1))) while sum(servicer.responses) < target_sum: print(f"Current sum:{sum(servicer.responses)}, goal:{target_sum}") time.sleep(2) @@ -44,7 +59,7 @@ def serve(clients: int = 5, port: str = "8000"): print("Target reached, exchange closed") server.stop(None) else: - raise (ValueError("Unexpected results from clients")) + raise ValueError("Unexpected results from clients") if __name__ == "__main__": diff --git a/deploy_mock/scripts/setup.py b/deploy_mock/scripts/setup.py index 2f569aadb5507e3a4bd6af603e81af51eeab799f..3763d2b1325b16b42d27efe473664cab53d704ca 100644 --- a/deploy_mock/scripts/setup.py +++ b/deploy_mock/scripts/setup.py @@ -11,10 +11,61 @@ import fire FILEDIR = os.path.dirname(__file__) +def write_dir( + dest_dir: str, + grpc_dir: str, + dir_type: str, + client_nb: Optional[int] = None, + dotenv: bool = False, +) -> str: + + """Utility copying over selected files into a destination directory. + + Parameters + ---------- + dest_dir: str + The target directory + grpc_dir: str + The source directory for grpc files + dir_type: str + The type of directory created, intented to be + "server" or "client" + client_nb: optional int + The id of the client, ignored if dir_type is not + "client" + dotenv: bool, default=False + Whether or not to copy over the .env file + + """ + + opj = os.path.join # local alias to shorten code + name = ( + f"{dir_type}_{client_nb}" + if client_nb is not None and dir_type == "client" + else dir_type + ) + out_dir = opj(dest_dir, name) + os.makedirs(out_dir, exist_ok=True) + shutil.copyfile(opj(FILEDIR, f"{dir_type}.py"), opj(out_dir, "run.py")) + shutil.copyfile(opj(FILEDIR, f"{dir_type}.sh"), opj(out_dir, "run.sh")) + shutil.copytree(grpc_dir, opj(out_dir, "comms")) + if dotenv: + shutil.copyfile(opj(FILEDIR, ".env"), opj(out_dir, ".env")) + + return out_dir + + def generate_folders(clients: int): - """Generate the server folder and the appropriate number of client folders, - with appropriate scripts and simulated, unique data""" + """ + Generate the server folder and the appropriate number of client folders, + with appropriate scripts and simulated, unique data + + Parameters + ---------- + clients: int + The number of clients + """ # Clear existing files dest_dir = os.path.join(Path(FILEDIR).resolve().parents[0], "nodes") @@ -36,26 +87,5 @@ def generate_folders(clients: int): writer.writerow([clt / 2, clt / 2]) -def write_dir( - dest_dir: str, - grpc_dir: str, - dir_type: str, - client_nb: Optional[int] = None, - dotenv: bool = False, -) -> str: - - opj = os.path.join # local alias to shorten code - name = f"{dir_type}_{client_nb}" if client_nb is not None else dir_type - out_dir = opj(dest_dir, name) - os.makedirs(out_dir, exist_ok=True) - shutil.copyfile(opj(FILEDIR, f"{dir_type}.py"), opj(out_dir, "run.py")) - shutil.copyfile(opj(FILEDIR, f"{dir_type}.sh"), opj(out_dir, "run.sh")) - shutil.copytree(grpc_dir, opj(out_dir, "comms")) - if dotenv: - shutil.copyfile(opj(FILEDIR, ".env"), opj(out_dir, ".env")) - - return out_dir - - if __name__ == "__main__": fire.Fire(generate_folders)