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
|
# within this timeframe
|
||||||
CLIENT_IDLE_TIMEOUT=60
|
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
|
# DEBUG, INFO, WARNING, ERROR, CRITICAL are valid
|
||||||
# If an invalid value is provided, the app defaults to INFO
|
# If an invalid value is provided, the app defaults to INFO
|
||||||
VERBOSITY=DEBUG
|
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.
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
#.idea/
|
#.idea/
|
||||||
|
|
||||||
|
bank.db
|
@ -95,3 +95,5 @@ class BankNode():
|
|||||||
def cleanup(self):
|
def cleanup(self):
|
||||||
self.logger.debug("Closing socket server")
|
self.logger.debug("Closing socket server")
|
||||||
self.socket_server.close()
|
self.socket_server.close()
|
||||||
|
self.logger.debug("Closing database connection")
|
||||||
|
self.database_manager.cleanup()
|
||||||
|
@ -1,23 +1,23 @@
|
|||||||
import socket
|
import socket
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
import logging
|
import logging
|
||||||
from typing import Tuple, Dict
|
from typing import Tuple
|
||||||
import signal
|
import signal
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
from bank_protocol.command_handler import CommandHandler
|
from bank_protocol.command_handler import CommandHandler
|
||||||
from core import Request, Response
|
from core import Request, Response, BankNodeConfig
|
||||||
from core.exceptions import BankNodeError
|
from core.exceptions import BankNodeError
|
||||||
|
|
||||||
|
|
||||||
class BankWorker(multiprocessing.Process):
|
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__()
|
super().__init__()
|
||||||
|
|
||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
self.client_socket = client_socket
|
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.client_address = client_address
|
||||||
|
|
||||||
self.command_handler = CommandHandler(config)
|
self.command_handler = CommandHandler(config)
|
||||||
@ -65,8 +65,8 @@ class BankWorker(multiprocessing.Process):
|
|||||||
response = "ER " + e.message + "\n\r"
|
response = "ER " + e.message + "\n\r"
|
||||||
self.client_socket.sendall(response.encode("utf-8"))
|
self.client_socket.sendall(response.encode("utf-8"))
|
||||||
except socket.error as e:
|
except socket.error as e:
|
||||||
response = "ER Internal server error\n\r"
|
|
||||||
self.logger.error(e)
|
self.logger.error(e)
|
||||||
|
response = "ER Internal server error\n\r"
|
||||||
break
|
break
|
||||||
|
|
||||||
self.logger.debug("Closing process for %s", self.client_address[0])
|
self.logger.debug("Closing process for %s", self.client_address[0])
|
||||||
|
@ -12,3 +12,21 @@ class InvalidRequest(BankNodeError):
|
|||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
super().__init__(message)
|
super().__init__(message)
|
||||||
self.message = 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
|
import socket
|
||||||
from typing import Tuple
|
import logging
|
||||||
|
|
||||||
from core import Request, Response
|
from core import Request, Response, BankNodeConfig
|
||||||
from bank_protocol.exceptions import ProxyError
|
from bank_protocol.exceptions import RequestTimeoutError, NoPortsOpenError, HostUnreachableError
|
||||||
|
|
||||||
|
|
||||||
class BankProxy():
|
class BankProxy():
|
||||||
def __init__(self, request: Request, address: Tuple):
|
def __init__(self, request: Request, address: str, config: BankNodeConfig):
|
||||||
self.request = request
|
self.request = request
|
||||||
self.address = address
|
self.address = address
|
||||||
|
self.config = config
|
||||||
|
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def proxy_request(self) -> Response:
|
def proxy_request(self) -> Response:
|
||||||
|
for port in range(self.config.scan_port_start, self.config.scan_port_end + 1):
|
||||||
|
self.logger.debug("Connecting to port %d", port)
|
||||||
try:
|
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)
|
||||||
|
|
||||||
|
self.logger.warning("No ports open on the destination host")
|
||||||
|
raise NoPortsOpenError("Destination host has no open ports from range")
|
||||||
|
|
||||||
|
def __proxy_request(self, port):
|
||||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client_socket:
|
||||||
client_socket.connect(self.address)
|
client_socket.connect((self.address, port))
|
||||||
|
|
||||||
client_socket.sendall(self.request.as_request())
|
client_socket.sendall(self.request.as_request())
|
||||||
|
|
||||||
response = client_socket.recv(1024)
|
response = client_socket.recv(1024)
|
||||||
return response
|
return response
|
||||||
except socket.error as e:
|
|
||||||
raise ProxyError("Proxy error") from e
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
from .request import *
|
from .request import *
|
||||||
from .response import *
|
from .response import *
|
||||||
|
from .config import BankNodeConfig
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
*request.__all__,
|
*request.__all__,
|
||||||
*response.__all__
|
*response.__all__,
|
||||||
|
*config.__all__
|
||||||
]
|
]
|
@ -69,3 +69,6 @@ class BankNodeConfig:
|
|||||||
"scan_port_start": self.scan_port_start,
|
"scan_port_start": self.scan_port_start,
|
||||||
"scan_port_end": self.scan_port_end,
|
"scan_port_end": self.scan_port_end,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = ["BankNodeConfig"]
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
from typing import Generator
|
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 import create_engine, text
|
||||||
|
|
||||||
from sqlalchemy.exc import DatabaseError
|
from sqlalchemy.exc import DatabaseError
|
||||||
|
|
||||||
from database.exceptions import DatabaseConnectionError
|
from database.exceptions import DatabaseConnectionError
|
||||||
@ -29,13 +29,13 @@ class DatabaseManager():
|
|||||||
self.engine = create_engine('sqlite:///bank.db')
|
self.engine = create_engine('sqlite:///bank.db')
|
||||||
|
|
||||||
self.Session = sessionmaker(bind=self.engine)
|
self.Session = sessionmaker(bind=self.engine)
|
||||||
|
self.create_tables()
|
||||||
|
|
||||||
def create_tables(self):
|
def create_tables(self):
|
||||||
self.logger.debug("Creating tables")
|
self.logger.debug("Creating tables")
|
||||||
Base.metadata.create_all(self.engine)
|
Base.metadata.create_all(self.engine)
|
||||||
|
|
||||||
def cleanup(self) -> None:
|
def cleanup(self) -> None:
|
||||||
self.logger.debug("Closing connection")
|
|
||||||
self.engine.dispose()
|
self.engine.dispose()
|
||||||
|
|
||||||
def test_connection(self) -> bool:
|
def test_connection(self) -> bool:
|
||||||
@ -52,7 +52,8 @@ class DatabaseManager():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_session(cls) -> Generator:
|
@contextmanager
|
||||||
|
def get_session(cls) -> Generator[Session]:
|
||||||
session = cls._instance.Session()
|
session = cls._instance.Session()
|
||||||
try:
|
try:
|
||||||
yield session
|
yield session
|
||||||
|
@ -25,4 +25,16 @@ class DuplicateEntryError(DatabaseError):
|
|||||||
self.message = message
|
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"]
|
__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}$"
|
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}"
|
ACCOUNT_NUMBER_REGEX = r"[0-9]{9}"
|
||||||
MONEY_AMOUNT_MAXIMUM = (2 ^ 63) - 1
|
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