From d1d53217847be30a287655d41b3f2a492033e5ea Mon Sep 17 00:00:00 2001 From: minhtrannhat Date: Mon, 27 Nov 2023 14:19:19 -0500 Subject: [PATCH] feat: Testing environment - both server and client must specify their directories --- .tmuxinator.yml | 59 +++++++++++++++++++++++++++++++++++++++++++++ Dockerfile.client | 11 ++++----- Dockerfile.server | 11 +++------ README.md | 8 +++--- docker-compose.yaml | 4 +++ src/myftp/client.py | 32 +++++++++++++++++++++--- src/myftp/server.py | 25 +++++++++++++++++++ 7 files changed, 130 insertions(+), 20 deletions(-) create mode 100644 .tmuxinator.yml diff --git a/.tmuxinator.yml b/.tmuxinator.yml new file mode 100644 index 0000000..4c4280b --- /dev/null +++ b/.tmuxinator.yml @@ -0,0 +1,59 @@ +# ./.tmuxinator.yml + +name: coen366 +root: ~/Desktop/University/Fall2023_Classes/COEN366/project + +# Optional tmux socket +# socket_name: foo + +# Note that the pre and post options have been deprecated and will be replaced by +# project hooks. + +# Project hooks + +# Runs on project start, always +# on_project_start: command + +# Run on project start, the first time +# on_project_first_start: command + +# Run on project start, after the first time +# on_project_restart: command + +# Run on project exit ( detaching from tmux session ) +# on_project_exit: command + +# Run on project stop +# on_project_stop: command + +# Runs in each window and pane before window/pane specific commands. Useful for setting up interpreter versions. +# pre_window: rbenv shell 2.0.0-p247 + +# Pass command line options to tmux. Useful for specifying a different tmux.conf. +# tmux_options: -f ~/.tmux.mac.conf + +# Change the command to call tmux. This can be used by derivatives/wrappers like byobu. +# tmux_command: byobu + +# Specifies (by name or index) which window will be selected on project startup. If not set, the first window is used. +# startup_window: editor + +# Specifies (by index) which pane of the specified window will be selected on project startup. If not set, the first pane is used. +# startup_pane: 1 + +# Controls whether the tmux session should be attached to automatically. Defaults to true. +# attach: false + +windows: + - editor: + root: ~/Desktop/University/Fall2023_Classes/COEN366/project + layout: main-vertical + panes: + - lvim + - test: + layout: main-vertical + root: ~/Desktop/University/Fall2023_Classes/COEN366/project + panes: + - docker-compose up --build --remove-orphans + - sleep 15 && docker exec -it project-ftp_client-1 python client.py --debug 1 + - sleep 15 && docker exec -it project-ftp_server-1 python server.py --port_number 12000 --debug 1 diff --git a/Dockerfile.client b/Dockerfile.client index d1860ec..38b37ae 100644 --- a/Dockerfile.client +++ b/Dockerfile.client @@ -5,13 +5,12 @@ FROM python:3.11 EXPOSE 12000 # Create and set the working directory -WORKDIR /app +WORKDIR /client -# Copy the local app files to the container -COPY ./src/myftp /app - -RUN dd if=/dev/urandom of=/app/file_local.txt bs=1024 count=10 -RUN dd if=/dev/urandom of=/app/image_local.png bs=1024 count=50 +# Copy the local client to the container +RUN mkdir /client_directory +RUN dd if=/dev/urandom of=/client_directory/file_local.txt bs=1024 count=10 +RUN dd if=/dev/urandom of=/client_directory/image_local.png bs=1024 count=50 # Start your Python application #CMD python client.py --debug 1 diff --git a/Dockerfile.server b/Dockerfile.server index 5c0d5f0..0a8ef43 100644 --- a/Dockerfile.server +++ b/Dockerfile.server @@ -5,15 +5,12 @@ FROM python:3.11 EXPOSE 12000 # Create and set the working directory -WORKDIR /app - -# Copy the local app files to the container -COPY ./src/myftp /app +WORKDIR /server # Create files with random content in the /server directory -RUN mkdir /server -RUN dd if=/dev/urandom of=/server/file1.txt bs=1024 count=10 -RUN dd if=/dev/urandom of=/server/image1.png bs=1024 count=50 +RUN mkdir /server_directory +RUN dd if=/dev/urandom of=/server_directory/file_server.txt bs=1024 count=10 +RUN dd if=/dev/urandom of=/server_directory/image_server.png bs=1024 count=50 # Start your Python application #CMD python server.py --port_number 12000 --debug 1 diff --git a/README.md b/README.md index 73e54c7..4d815c3 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ Zero. Only python standard libs were used. ### Client -You can run `python3 src/myftp/client.py` to start the client or `python3 src/myftp/client.py --debug 1` for debugging purposes. +You can run `python3 src/myftp/client.py --directory ` to start the client or `python3 src/myftp/client.py --debug 1 --directory ` for debugging purposes. ### Server By default, the server IP address or hostname or server name will be `0.0.0.0` (meaning it will bind to all interfaces). The `--port_number` flag, if not specified will be by default `12000`. -You can run `python3 src/myftp/server.py` to start the server or `python3 src/myftp/server.py --ip_addr --port_number --debug 1` for debugging purposes and to specify the port number. +You can run `python3 src/myftp/server.py --directory ` to start the server or `python3 src/myftp/server.py --ip_addr --port_number --debug 1 --directory ` for debugging purposes and to specify the port number. ## Testing with Docker @@ -29,7 +29,7 @@ You can run `python3 src/myftp/server.py` to start the server or `python3 src/my - Wait 10 seconds. - 2 containers will be created on the same network `mynetwork`. Their respective IP addresses will be printed to stdout. - Open two terminal windows: one for each of server and client. -- Run the server with `docker exec -it project-ftp_server_1 python server.py `. -- Run the client with `docker exec -it project-ftp_client_1 python client.py `. +- Run the server with `docker exec -it project-ftp_server_1 python server.py --directory /server_directory `. +- Run the client with `docker exec -it project-ftp_client_1 python client.py --directory /client_directory `. - For the client, when asked to put in the ip address and port number of the server, you can put in `ftp_server 12000` or adjust to your chosen port number. The IP address is resolved by Docker so ftp_server can not be changed. - Tear down everything with `docker-compose down`. diff --git a/docker-compose.yaml b/docker-compose.yaml index fe2c48a..4fe6d50 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,6 +5,8 @@ services: build: context: . dockerfile: Dockerfile.server + volumes: + - ./src/myftp:/server:ro networks: - mynetwork command: @@ -22,6 +24,8 @@ services: dockerfile: Dockerfile.client networks: - mynetwork + volumes: + - ./src/myftp:/client:ro command: - sh - -c diff --git a/src/myftp/client.py b/src/myftp/client.py index aaf4be1..77ea02e 100644 --- a/src/myftp/client.py +++ b/src/myftp/client.py @@ -1,6 +1,7 @@ from socket import socket, AF_INET, SOCK_DGRAM from typing import Tuple from argparse import ArgumentParser +import os # custome type to represent the hostname(server name) and the server port Address = Tuple[str, int] @@ -19,12 +20,13 @@ class UDPClient: if not self.pong_received: return - client_socket = socket(AF_INET, SOCK_DGRAM) - - client_socket.connect((self.server_name, self.server_port)) + client_socket = None # shutup the variableNotBound warning while True: try: + client_socket = socket(AF_INET, SOCK_DGRAM) + client_socket.connect((self.server_name, self.server_port)) + # get command from user while (command := input(f"myftp> - {self.mode} - : ")) not in [ "put", @@ -117,6 +119,20 @@ def get_address_input() -> Address: ) +def check_directory(path): + if os.path.exists(path): + if os.path.isdir(path): + if os.access(path, os.R_OK) and os.access(path, os.W_OK): + return True + else: + print(f"Error: The directory '{path}' is not readable or writable.") + else: + print(f"Error: '{path}' is not a directory.") + else: + print(f"Error: The directory '{path}' does not exist.") + return False + + def init(): arg_parser = ArgumentParser(description="A FTP client written in Python") @@ -129,6 +145,10 @@ def init(): help="Enable or disable the flag (0 or 1)", ) + arg_parser.add_argument( + "--directory", required=True, type=str, help="Path to the client directory" + ) + args = arg_parser.parse_args() while ( @@ -136,6 +156,12 @@ def init(): ) not in {"1", "2"}: print("myftp>Invalid choice. Press 1 for TCP, Press 2 for UDP") + if not check_directory(args.directory): + print( + f"The directory '{args.directory}' does not exists or is not readable/writable." + ) + return + # UDP client selected here if protocol_selection == "2": user_supplied_address = get_address_input() diff --git a/src/myftp/server.py b/src/myftp/server.py index 13411a9..ca4ed84 100644 --- a/src/myftp/server.py +++ b/src/myftp/server.py @@ -1,5 +1,6 @@ from socket import socket, AF_INET, SOCK_DGRAM from argparse import ArgumentParser +import os class UDPServer: @@ -48,6 +49,20 @@ class UDPServer: print(f"myftp> - {self.mode} - Closed the server socket\n") +def check_directory(path): + if os.path.exists(path): + if os.path.isdir(path): + if os.access(path, os.R_OK) and os.access(path, os.W_OK): + return True + else: + print(f"Error: The directory '{path}' is not readable or writable.") + else: + print(f"Error: '{path}' is not a directory.") + else: + print(f"Error: The directory '{path}' does not exist.") + return False + + def init(): parser = ArgumentParser(description="A FTP server written in Python") @@ -59,6 +74,10 @@ def init(): help="Port number for the server. Default = 12000", ) + parser.add_argument( + "--directory", required=True, type=str, help="Path to the server directory" + ) + parser.add_argument( "--ip_addr", default="0.0.0.0", @@ -82,6 +101,12 @@ def init(): ) not in {"1", "2"}: print("myftp>Invalid choice. Press 1 for TCP, Press 2 for UDP") + if not check_directory(args.directory): + print( + f"The directory '{args.directory}' does not exists or is not readable/writable." + ) + return + # UDP client selected here if protocol_selection == "2": udp_server = UDPServer(args.ip_addr, args.port_number, args.debug)