Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(database): Physical Backup & Restore #2419

Draft
wants to merge 29 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
03782a5
refactor: Remove unused fields from Site Backup doctype
tanmoysrt Jan 8, 2025
d2bcf69
refactor(snapshot): Allow to exclude boot volume while snapshot
tanmoysrt Jan 9, 2025
d4099a2
feat(site-backup): Added required fields for physical backup
tanmoysrt Jan 9, 2025
4ae28dd
feat: Added Agent job for physical backup of database
tanmoysrt Jan 9, 2025
204cca6
feat(site-backup): Added support for physical backup of database
tanmoysrt Jan 9, 2025
c378d8e
feat(site-backup): Store the required data for physical backup restore
tanmoysrt Jan 9, 2025
14d27da
feat(snapshot): Added function to create volume from snapshot
tanmoysrt Jan 14, 2025
a56dda1
feat(physical-backup-restoration): Added doctype and util functions
tanmoysrt Jan 14, 2025
385fe0f
feat(virtual-machine): Add support for temporary volumes
tanmoysrt Jan 15, 2025
01c07bf
refactor(virtual-machine): Split attach_new_volume fn to reuse parts
tanmoysrt Jan 15, 2025
b652b0e
feat(agent): Added function for physical backup restore
tanmoysrt Jan 15, 2025
f4820a9
chore(cspell): Add missing word
tanmoysrt Jan 15, 2025
046b5e6
feat(physical-backup-restore): Implemented complete flow of restoration
tanmoysrt Jan 15, 2025
9e8734a
chore(cspell): Fix conflict
tanmoysrt Jan 15, 2025
9115898
Merge branch 'master' into physical_backup_restore
tanmoysrt Jan 15, 2025
93e1659
fix(physical-backup-restore): Send agent requests to database server
tanmoysrt Jan 15, 2025
4eca9b0
Merge branch 'master' into physical_backup_restore
tanmoysrt Jan 17, 2025
d271912
refactor(site-backup): Generate snapshot request key on insert
tanmoysrt Jan 17, 2025
06cd205
fix(site-backup): Send agent request to Database Server
tanmoysrt Jan 17, 2025
f1ce6e5
refactor(site-backup): Send private ip of database server
tanmoysrt Jan 17, 2025
c699c65
refactor(site-backup): In snapshot create api set admin user temporarily
tanmoysrt Jan 17, 2025
ecc5a00
chore(site-backup): Site Backup DocType layout updated
tanmoysrt Jan 17, 2025
d7bdb66
chore(site-backup): Add Physical Backup function and button to Site
tanmoysrt Jan 17, 2025
35bc8fa
feat(site-backup): Modify permission of db directory before backup
tanmoysrt Jan 17, 2025
c26c7ec
feat(site-backup): On disk snapshot status update sync that to backup
tanmoysrt Jan 17, 2025
cbae905
Merge branch 'master' into physical_backup_restore
tanmoysrt Jan 17, 2025
4709021
feat(disk-snapshot): Record the snapshot completion duration
tanmoysrt Jan 17, 2025
87b4050
refactor(physical-restore): Reimplement the process flow
tanmoysrt Jan 17, 2025
9eafc11
refactor(physical-restore): Add support for async agent job step
tanmoysrt Jan 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
854 changes: 427 additions & 427 deletions .cspell.json

Large diffs are not rendered by default.

63 changes: 60 additions & 3 deletions press/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,11 @@

from press.press.doctype.agent_job.agent_job import AgentJob
from press.press.doctype.app_patch.app_patch import AgentPatchConfig, AppPatch
from press.press.doctype.physical_backup_restoration.physical_backup_restoration import (
PhysicalBackupRestoration,
)
from press.press.doctype.site.site import Site
from press.press.doctype.site_backup.site_backup import SiteBackup


class Agent:
Expand Down Expand Up @@ -420,12 +424,65 @@ def archive_site(self, site, site_name=None, force=False):
site=site.name,
)

def backup_site(self, site, with_files=False, offsite=False):
def physical_backup_database(self, site: Site, site_backup: SiteBackup):
"""
For physical database backup, the flow :
- Create the agent job
- Agent job will lock the specific database + flush the changes to disk
- Take a database dump
- Use `fsync` to ensure the changes are written to disk
- Agent will send back a request to FC for taking the snapshot
- By calling `snapshot_create_callback` url
- Then, unlock the database
"""
url = frappe.utils.get_url()
data = {
"databases": [site_backup.database_name],
"mariadb_root_password": get_mariadb_root_password(site),
"private_ip": frappe.get_value(
"Database Server", frappe.db.get_value("Server", site.server, "database_server"), "private_ip"
),
"site_backup": {
"name": site_backup.name,
"snapshot_request_key": site_backup.snapshot_request_key,
"snapshot_trigger_url": f"{url}/api/method/press.api.site_backup.create_snapshot",
},
}
return self.create_agent_job(
"Physical Backup Database",
"/database/physical-backup",
data=data,
bench=site.bench,
site=site.name,
)

def physical_restore_database(self, site, backup_restoration: PhysicalBackupRestoration):
backup: SiteBackup = frappe.get_doc("Site Backup", backup_restoration.site_backup)
data = {
"backup_db": backup_restoration.source_database,
"target_db": backup_restoration.destination_database,
"target_db_root_password": get_mariadb_root_password(site),
"innodb_tables": json.loads(backup.innodb_tables),
"myisam_tables": json.loads(backup.myisam_tables),
"table_schema": backup.table_schema,
"backup_db_base_directory": os.path.join(backup_restoration.mount_path, "/var/lib/mysql"),
}
return self.create_agent_job(
"Physical Restore Database",
"/database/physical-restore",
data=data,
bench=site.bench,
site=site.name,
reference_name=backup_restoration.name,
reference_doctype=backup_restoration.doctype,
)

def backup_site(self, site, site_backup: SiteBackup):
from press.press.doctype.site_backup.site_backup import get_backup_bucket

data = {"with_files": with_files}
data = {"with_files": site_backup.with_files}

if offsite:
if site_backup.offsite:
settings = frappe.get_single("Press Settings")
backups_path = os.path.join(site.name, str(date.today()))
backup_bucket = get_backup_bucket(site.cluster, region=True)
Expand Down
31 changes: 31 additions & 0 deletions press/api/site_backup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from typing import TYPE_CHECKING

import frappe

if TYPE_CHECKING:
from press.press.doctype.site_backup.site_backup import SiteBackup


@frappe.whitelist(allow_guest=True, methods="POST")
def create_snapshot(name: str, key: str):
"""
This API will be called by agent during physical backup of database server.
Once, agent prepare the specific database for backup, it will call this API to create a snapshot of the database.
Because we need to hold the lock on the database for the duration of the backup.
Only after the snapshot is created, the agent will release the lock on the database.
"""
current_user = frappe.session.user
try:
frappe.set_user("Administrator")
site_backup: SiteBackup = frappe.get_doc("Site Backup", name)
if not (key and site_backup.snapshot_request_key == key):
frappe.throw("Invalid key for snapshot creation")
site_backup.create_database_snapshot()
site_backup.reload()
# Re-verify if the snapshot was created and linked to the site backup
if not site_backup.database_snapshot:
frappe.throw("Failed to create a snapshot for the database server")
except Exception as e:
raise e
finally:
frappe.set_user(current_user)
30 changes: 30 additions & 0 deletions press/fixtures/agent_job_type.json
Original file line number Diff line number Diff line change
Expand Up @@ -2290,5 +2290,35 @@
"step_name": "Analyze Slow Queries"
}
]
},
{
"disabled_auto_retry": 1,
"docstatus": 0,
"doctype": "Agent Job Type",
"max_retry_count": 3,
"modified": "2025-01-09 17:07:45.359754",
"name": "Physical Backup Database",
"request_method": "POST",
"request_path": "/database/physical-backup",
"steps": [
{
"step_name": "Fetch Database Tables Information"
},
{
"step_name": "Flush Database Tables"
},
{
"step_name": "Flush Changes to Disk"
},
{
"step_name": "Export Table Schema"
},
{
"step_name": "Create Database Snapshot"
},
{
"step_name": "Unlock Tables"
}
]
}
]
7 changes: 6 additions & 1 deletion press/press/doctype/agent_job/agent_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,9 @@ def process_job_updates(job_name: str, response_data: dict | None = None): # no
process_stop_code_server_job_update,
)
from press.press.doctype.deploy_candidate.deploy_candidate import DeployCandidate
from press.press.doctype.physical_backup_restoration.physical_backup_restoration import (
process_job_update as process_physical_backup_restoration_job_update,
)
from press.press.doctype.proxy_server.proxy_server import (
process_update_nginx_job_update,
)
Expand Down Expand Up @@ -1022,7 +1025,7 @@ def process_job_updates(job_name: str, response_data: dict | None = None): # no
process_stop_code_server_job_update(job)
elif job.job_type == "Archive Code Server" or job.job_type == "Remove Code Server from Upstream":
process_archive_code_server_job_update(job)
elif job.job_type == "Backup Site":
elif job.job_type in ["Backup Site", "Physical Backup Database"]:
process_backup_site_job_update(job)
elif job.job_type == "Archive Site" or job.job_type == "Remove Site from Upstream":
process_archive_site_job_update(job)
Expand Down Expand Up @@ -1073,6 +1076,8 @@ def process_job_updates(job_name: str, response_data: dict | None = None): # no
"Modify Database User Permissions",
]:
SiteDatabaseUser.process_job_update(job)
elif job.job_type == "Physical Restore Database":
process_physical_backup_restoration_job_update(job)

# send failure notification if job failed
if job.status == "Failure":
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Copyright (c) 2025, Frappe and contributors
// For license information, please see license.txt

// frappe.ui.form.on("Physical Backup Restoration", {
// refresh(frm) {

// },
// });
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
{
"actions": [],
"allow_rename": 1,
"creation": "2025-01-10 13:02:39.393157",
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"site",
"status",
"site_backup",
"disk_snapshot",
"column_break_zind",
"volume",
"device",
"mount_path",
"job",
"section_break_pqgo",
"source_database",
"column_break_kaja",
"destination_database",
"destination_server",
"section_break_aqam",
"steps"
],
"fields": [
{
"fieldname": "site",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Site",
"options": "Site",
"reqd": 1
},
{
"fieldname": "column_break_zind",
"fieldtype": "Column Break"
},
{
"default": "Pending",
"fieldname": "status",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Status",
"options": "Pending\nRunning\nSuccess\nFailure",
"reqd": 1
},
{
"fieldname": "disk_snapshot",
"fieldtype": "Link",
"label": "Disk Snapshot",
"options": "Virtual Disk Snapshot",
"reqd": 1
},
{
"fieldname": "site_backup",
"fieldtype": "Link",
"label": "Site Backup",
"options": "Site Backup"
},
{
"fieldname": "section_break_pqgo",
"fieldtype": "Section Break"
},
{
"fieldname": "source_database",
"fieldtype": "Data",
"label": "Source Database",
"reqd": 1
},
{
"fieldname": "column_break_kaja",
"fieldtype": "Column Break"
},
{
"fieldname": "destination_database",
"fieldtype": "Data",
"label": " Destination Database",
"reqd": 1
},
{
"fieldname": "section_break_aqam",
"fieldtype": "Section Break"
},
{
"fieldname": "steps",
"fieldtype": "Table",
"label": "Steps",
"options": "Physical Backup Restoration Step"
},
{
"fieldname": "destination_server",
"fieldtype": "Link",
"label": "Destination Server",
"options": "Database Server",
"reqd": 1
},
{
"fieldname": "volume",
"fieldtype": "Data",
"label": "Volume",
"read_only": 1
},
{
"fieldname": "device",
"fieldtype": "Data",
"label": "Device",
"read_only": 1
},
{
"fieldname": "mount_path",
"fieldtype": "Data",
"label": "Mount Path",
"read_only": 1
},
{
"fieldname": "job",
"fieldtype": "Link",
"label": "Job",
"options": "Agent Job"
}
],
"index_web_pages_for_search": 1,
"links": [],
"modified": "2025-01-17 19:24:16.834232",
"modified_by": "Administrator",
"module": "Press",
"name": "Physical Backup Restoration",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
}
],
"sort_field": "creation",
"sort_order": "DESC",
"states": []
}
Loading
Loading