Skip to content

Commit

Permalink
Merge pull request #67 from Aerodisk/backup/main
Browse files Browse the repository at this point in the history
Добавление нового модуля для управления резервным копированием данных с использованием Restic.
  • Loading branch information
tihon49 authored Dec 26, 2024
2 parents bf9b6c8 + 0da2698 commit fe96833
Show file tree
Hide file tree
Showing 35 changed files with 2,013 additions and 1 deletion.
12 changes: 11 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,15 @@ install_jq(){
execute "sudo apt-get install jq -y" "$message"
}

install_restic(){
local apt_get_command="sudo apt-get install restic"
local self_update_command="sudo restic self-update"
local install_message="Installing restic"
local update_message="Updating restic"
execute "$apt_get_command" "$install_message"
execute "$self_update_command" "$update_message"
}

# ========= Daemons =========
process_services() {
go_to_home_dir
Expand All @@ -691,7 +700,7 @@ process_services() {
# Clear home directory
clear_home_dir() {
local message="Clear home directory"
execute "rm -rf .nvm .npm .cache .config" "$message"
execute "sudo rm -rf .nvm .npm .cache .config" "$message"
}

# Create hashed password
Expand Down Expand Up @@ -819,6 +828,7 @@ main() {
install_node_exporter
install_open_iscsi
clone_novnc
install_restic
process_services
clear_home_dir
make_hashed_password
Expand Down
32 changes: 32 additions & 0 deletions openvair/common/schemas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"""Common schemas for standardizing API responses.
This module defines a generic Pydantic schema for creating consistent
API response structures across the application.
Classes:
BaseResponse: A generic schema for API responses, supporting customizable
data, status, and error messages.
"""

from typing import Generic, TypeVar, Optional

from pydantic import BaseModel

T = TypeVar('T')


class BaseResponse(BaseModel, Generic[T]):
"""Generic schema for API responses.
This schema is designed to standardize API responses by including
a status, optional data, and an optional error message.
Attributes:
status (str): The status of the response (e.g., "success", "error").
data (Optional[T]): The data payload of the response, if applicable.
error (Optional[str]): An error message, if applicable.
"""

status: str
data: Optional[T] = None
error: Optional[str] = None
1 change: 1 addition & 0 deletions openvair/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
data = toml.load(config_toml)

database: Dict = data.get('database', {})
DB_CONTAINER: str = data['docker']['db_container']

def get_postgres_uri() -> str:
"""Generates a PostgreSQL URI from configuration settings.
Expand Down
17 changes: 17 additions & 0 deletions openvair/libs/messaging/service_interfaces/backup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""Module for managing backup-related operations in the service layer.
The module includes an interface definition for the backup service layer
manager, which outlines the methods that should be implemented by any class
responsible for managing backup operations.
"""

from typing import Protocol


class BackupServiceLayerManagerProtocolInterface(Protocol):
"""Interface for the BackupServiceLayerManager.
This interface defines the methods that should be implemented by any class
that manages backup-related operations in the service layer.
"""
...
2 changes: 2 additions & 0 deletions openvair/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
from openvair.modules.user.entrypoints.api import router as user
from openvair.modules.image.entrypoints.api import router as image
from openvair.modules.user.entrypoints.auth import router as auth
from openvair.modules.backup.entrypoints.api import router as backup_router
from openvair.modules.volume.entrypoints.api import router as volume
from openvair.modules.network.entrypoints.api import router as network
from openvair.modules.storage.entrypoints.api import router as storage
Expand Down Expand Up @@ -101,6 +102,7 @@
app.include_router(notification_router)
app.include_router(block_router)
app.include_router(vn_router)
app.include_router(backup_router)

project_dir = Path(__file__).parent
templates = Jinja2Templates(directory=project_dir / 'dist')
Expand Down
Empty file.
137 changes: 137 additions & 0 deletions openvair/modules/backup/adapters/repository.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
"""Repository module for managing PostgreSQL databases.
This module defines an abstract base class for database repositories and
provides a concrete implementation using SQLAlchemy to manage PostgreSQL
databases. It includes methods for terminating connections, dropping databases,
creating databases
"""

from abc import ABCMeta, abstractmethod
from typing import TYPE_CHECKING

from sqlalchemy import text, create_engine

from openvair.config import database, get_postgres_uri

if TYPE_CHECKING:
from sqlalchemy.orm import Session


class AbstractRepository(metaclass=ABCMeta):
"""Abstract base class for database repositories.
Defines the interface for database operations such as terminating
connections, dropping databases, creating databases, and restoring data.
"""

@abstractmethod
def terminate_all_connections(self, db_name: str) -> None:
"""Terminate all active connections to a specified database.
Args:
db_name (str): Name of the database whose connections should be
terminated.
"""
...

@abstractmethod
def drop_db(self, db_name: str) -> None:
"""Drop a specified database.
Args:
db_name (str): Name of the database to drop.
"""
...

@abstractmethod
def create_db(self, db_name: str) -> None:
"""Create a new database.
Args:
db_name (str): Name of the database to create.
"""
...


class SqlAlchemyRepository(AbstractRepository):
"""SQLAlchemy implementation of a database repository.
This class provides concrete implementations of database operations
using SQLAlchemy, tailored for PostgreSQL.
Attributes:
session (Session): SQLAlchemy session for database interactions.
engine (Engine): SQLAlchemy engine for executing database commands.
"""
def __init__(self, session: 'Session'):
"""Initialize the SQLAlchemy repository.
Args:
session (Session): SQLAlchemy session for database interactions.
"""
self.session: Session = session
self.engine = create_engine(
get_postgres_uri().replace(
database['db_name'], 'postgres'
) # for connection to 'postgres' db, instead 'openvair'
)

def terminate_all_connections(self, db_name: str) -> None:
"""Terminate all active connections to a specified database.
Uses a SQL query to terminate all connections to the specified database,
except the current session.
Args:
db_name (str): Name of the database whose connections should be
terminated.
"""
with self.session.connection() as conn:
conn.execute(
text("""
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = :db_name
AND pid <> pg_backend_pid();
"""),
{'db_name': db_name},
)

def drop_db(self, db_name: str) -> None:
"""Drop a specified database.
Executes a SQL command to drop the database. All connections to the
database are terminated before the drop operation.
Args:
db_name (str): Name of the database to drop.
"""
with self.engine.connect().execution_options(
isolation_level='AUTOCOMMIT'
) as conn: # connect withot transaction
# Stop all connections
conn.execute(
text("""
SELECT pg_terminate_backend(pg_stat_activity.pid)
FROM pg_stat_activity
WHERE pg_stat_activity.datname = :db_name
AND pid <> pg_backend_pid();
"""),
{'db_name': db_name},
)

conn.execute(text(f'DROP DATABASE "{db_name}";'))

def create_db(self, db_name: str) -> None:
"""Create a new database.
Executes a SQL command to create a new database.
Args:
db_name (str): Name of the database to create.
"""
with self.engine.connect().execution_options(
isolation_level='AUTOCOMMIT'
) as conn: # Connect without transaction
conn.execute(text(f'CREATE DATABASE "{db_name}";'))

Empty file.
36 changes: 36 additions & 0 deletions openvair/modules/backup/adapters/restic/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""Module for custom exceptions in the restic adapters."""

from typing import Any

from openvair.abstracts.base_exception import BaseCustomException


class ResticError(BaseCustomException):
"""Base errro class for error in restic adapter"""

def __init__(self, message: str, *args: Any): # noqa: ANN401
"""Initialize ResticAdapterException"""
super().__init__(message, args)


class ResticExecutorError(ResticError):
"""Raises when execution failed"""

...


class ResticInitRepoError(ResticError):
"""Raises when getting error while initialize restic repository"""

...


class ResticBackupError(ResticError):
"""Raises when error while backup restic operation"""

...

class ResticRestoreError(ResticError):
"""Raises when error while restoring restic operation"""

...
Loading

0 comments on commit fe96833

Please sign in to comment.