Started working on account service and related protocol commands
This commit is contained in:
parent
587756a51b
commit
35a9e64a10
@ -8,10 +8,6 @@ RESPONSE_TIMEOUT=5
|
||||
# within this timeframe
|
||||
CLIENT_IDLE_TIMEOUT=60
|
||||
|
||||
# A valid port number
|
||||
# If not provided or invalid, defaults to 65526
|
||||
PORT=65526
|
||||
|
||||
# DEBUG, INFO, WARNING, ERROR, CRITICAL are valid
|
||||
# If an invalid value is provided, the app defaults to INFO
|
||||
VERBOSITY=DEBUG
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -168,3 +168,4 @@ cython_debug/
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
bank.db
|
@ -95,3 +95,5 @@ class BankNode():
|
||||
def cleanup(self):
|
||||
self.logger.debug("Closing socket server")
|
||||
self.socket_server.close()
|
||||
self.logger.debug("Closing database connection")
|
||||
self.database_manager.cleanup()
|
||||
|
@ -1,23 +1,23 @@
|
||||
import socket
|
||||
import multiprocessing
|
||||
import logging
|
||||
from typing import Tuple, Dict
|
||||
from typing import Tuple
|
||||
import signal
|
||||
import sys
|
||||
|
||||
from bank_protocol.command_handler import CommandHandler
|
||||
from core import Request, Response
|
||||
from core import Request, Response, BankNodeConfig
|
||||
from core.exceptions import BankNodeError
|
||||
|
||||
|
||||
class BankWorker(multiprocessing.Process):
|
||||
def __init__(self, client_socket: socket.socket, client_address: Tuple, config: Dict):
|
||||
def __init__(self, client_socket: socket.socket, client_address: Tuple, config: BankNodeConfig):
|
||||
super().__init__()
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
self.client_socket = client_socket
|
||||
self.client_socket.settimeout(config["client_idle_timeout"])
|
||||
self.client_socket.settimeout(config.client_idle_timeout)
|
||||
self.client_address = client_address
|
||||
|
||||
self.command_handler = CommandHandler(config)
|
||||
@ -65,8 +65,8 @@ class BankWorker(multiprocessing.Process):
|
||||
response = "ER " + e.message + "\n\r"
|
||||
self.client_socket.sendall(response.encode("utf-8"))
|
||||
except socket.error as e:
|
||||
response = "ER Internal server error\n\r"
|
||||
self.logger.error(e)
|
||||
response = "ER Internal server error\n\r"
|
||||
break
|
||||
|
||||
self.logger.debug("Closing process for %s", self.client_address[0])
|
||||
|
@ -12,3 +12,21 @@ class InvalidRequest(BankNodeError):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class RequestTimeoutError(BankNodeError):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class HostUnreachableError(BankNodeError):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class NoPortsOpenError(BankNodeError):
|
||||
def __init__(self, message):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
@ -1,23 +1,37 @@
|
||||
import socket
|
||||
from typing import Tuple
|
||||
import logging
|
||||
|
||||
from core import Request, Response
|
||||
from bank_protocol.exceptions import ProxyError
|
||||
from core import Request, Response, BankNodeConfig
|
||||
from bank_protocol.exceptions import RequestTimeoutError, NoPortsOpenError, HostUnreachableError
|
||||
|
||||
|
||||
class BankProxy():
|
||||
def __init__(self, request: Request, address: Tuple):
|
||||
def __init__(self, request: Request, address: str, config: BankNodeConfig):
|
||||
self.request = request
|
||||
self.address = address
|
||||
self.config = config
|
||||
|
||||
self.logger = logging.getLogger(__name__)
|
||||
|
||||
def proxy_request(self) -> Response:
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
|
||||
client_socket.connect(self.address)
|
||||
for port in range(self.config.scan_port_start, self.config.scan_port_end + 1):
|
||||
self.logger.debug("Connecting to port %d", port)
|
||||
try:
|
||||
self.config.used_port = port
|
||||
self.__proxy_request(port)
|
||||
return
|
||||
except socket.error as e:
|
||||
if e.errno == 111: # Connection refused
|
||||
self.logger.debug("Port %d not open", port)
|
||||
|
||||
client_socket.sendall(self.request.as_request())
|
||||
self.logger.warning("No ports open on the destination host")
|
||||
raise NoPortsOpenError("Destination host has no open ports from range")
|
||||
|
||||
response = client_socket.recv(1024)
|
||||
return response
|
||||
except socket.error as e:
|
||||
raise ProxyError("Proxy error") from e
|
||||
def __proxy_request(self, port):
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
|
||||
client_socket.connect((self.address, port))
|
||||
|
||||
client_socket.sendall(self.request.as_request())
|
||||
|
||||
response = client_socket.recv(1024)
|
||||
return response
|
||||
|
@ -1,7 +1,9 @@
|
||||
from .request import *
|
||||
from .response import *
|
||||
from .config import BankNodeConfig
|
||||
|
||||
__all__ = [
|
||||
*request.__all__,
|
||||
*response.__all__
|
||||
]
|
||||
*response.__all__,
|
||||
*config.__all__
|
||||
]
|
||||
|
@ -69,3 +69,6 @@ class BankNodeConfig:
|
||||
"scan_port_start": self.scan_port_start,
|
||||
"scan_port_end": self.scan_port_end,
|
||||
}
|
||||
|
||||
|
||||
__all__ = ["BankNodeConfig"]
|
||||
|
@ -1,9 +1,9 @@
|
||||
import logging
|
||||
from typing import Generator
|
||||
from contextlib import contextmanager
|
||||
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from sqlalchemy.exc import DatabaseError
|
||||
|
||||
from database.exceptions import DatabaseConnectionError
|
||||
@ -29,13 +29,13 @@ class DatabaseManager():
|
||||
self.engine = create_engine('sqlite:///bank.db')
|
||||
|
||||
self.Session = sessionmaker(bind=self.engine)
|
||||
self.create_tables()
|
||||
|
||||
def create_tables(self):
|
||||
self.logger.debug("Creating tables")
|
||||
Base.metadata.create_all(self.engine)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
self.logger.debug("Closing connection")
|
||||
self.engine.dispose()
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
@ -52,7 +52,8 @@ class DatabaseManager():
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def get_session(cls) -> Generator:
|
||||
@contextmanager
|
||||
def get_session(cls) -> Generator[Session]:
|
||||
session = cls._instance.Session()
|
||||
try:
|
||||
yield session
|
||||
|
@ -25,4 +25,16 @@ class DuplicateEntryError(DatabaseError):
|
||||
self.message = message
|
||||
|
||||
|
||||
class NonexistentAccountError(DatabaseError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
|
||||
class OutOfAccountSpaceError(DatabaseError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
self.message = message
|
||||
|
||||
|
||||
__all__ = ["DatabaseError", "DatabaseConnectionError", "DuplicateEntryError"]
|
||||
|
47
src/services/account_serice.py
Normal file
47
src/services/account_serice.py
Normal file
@ -0,0 +1,47 @@
|
||||
from sqlalchemy import func
|
||||
|
||||
from models import Account
|
||||
from database import DatabaseManager
|
||||
from database.exceptions import OutOfAccountSpaceError, NonexistentAccountError
|
||||
from utils.constants import MIN_ACCOUNT_NUMBER, MAX_ACCOUNT_NUMBER
|
||||
|
||||
|
||||
def get_next_id() -> int:
|
||||
with DatabaseManager.get_session() as session:
|
||||
new_id = session.query(func.max(Account.account_number)).scalar()
|
||||
new_id = new_id if new_id is not None else MIN_ACCOUNT_NUMBER
|
||||
|
||||
if new_id > MAX_ACCOUNT_NUMBER:
|
||||
raise OutOfAccountSpaceError("Too many users already exist, cannot open new account")
|
||||
|
||||
return new_id
|
||||
|
||||
|
||||
def create_account() -> int:
|
||||
new_id = get_next_id()
|
||||
|
||||
with DatabaseManager.get_session() as session:
|
||||
new_account = Account(account_number=new_id, balance=0)
|
||||
session.add(new_account)
|
||||
session.commit()
|
||||
return new_id
|
||||
|
||||
|
||||
def get_account_balance(account_number: int) -> int:
|
||||
with DatabaseManager.get_session() as session:
|
||||
account: Account = session.query(Account).where(Account.account_number == account_number).one_or_none()
|
||||
if account is NotImplemented:
|
||||
raise NonexistentAccountError(f"Account with number {account_number} doesn't exist")
|
||||
|
||||
return account.balance
|
||||
|
||||
def withdraw_from_account():
|
||||
pass
|
||||
|
||||
|
||||
def deposit_into_account():
|
||||
pass
|
||||
|
||||
|
||||
def delete_account():
|
||||
pass
|
@ -3,3 +3,6 @@ import re
|
||||
IP_REGEX = r"^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}$"
|
||||
ACCOUNT_NUMBER_REGEX = r"[0-9]{9}"
|
||||
MONEY_AMOUNT_MAXIMUM = (2 ^ 63) - 1
|
||||
|
||||
MIN_ACCOUNT_NUMBER = 10_000
|
||||
MAX_ACCOUNT_NUMBER = 99_999
|
||||
|
Loading…
x
Reference in New Issue
Block a user