Skip to content

Commit 983631c

Browse files
authored
Merge pull request #1090 from frappe/site-migration-restore-fail-auto-drop
fix(SiteMigration): Remove site in destination (idempotently) before restoring
2 parents 59a8f55 + d7ed9c0 commit 983631c

File tree

8 files changed

+159
-29
lines changed

8 files changed

+159
-29
lines changed

press/api/tests/test_site.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,9 +305,11 @@ def test_restore_job_updates_apps_table_with_output_from_job(self):
305305
with fake_agent_job(
306306
"Restore Site",
307307
"Success",
308-
output="""frappe 15.0.0-dev HEAD
308+
data=frappe._dict(
309+
output="""frappe 15.0.0-dev HEAD
309310
insights 0.8.3 HEAD
310-
""",
311+
"""
312+
),
311313
):
312314
restore(
313315
site.name,
@@ -347,9 +349,7 @@ def test_restore_job_updates_apps_table_when_only_frappe_is_installed(self):
347349
self.assertEqual(site.status, "Active")
348350

349351
with fake_agent_job(
350-
"Restore Site",
351-
"Success",
352-
output="""frappe 15.0.0-dev HEAD""",
352+
"Restore Site", "Success", data=frappe._dict(output="""frappe 15.0.0-dev HEAD""")
353353
):
354354
restore(
355355
site.name,
@@ -384,9 +384,11 @@ def test_new_site_from_backup_job_updates_apps_table_with_output_from_job(self):
384384
with fake_agent_job(
385385
"New Site from Backup",
386386
"Success",
387-
output="""frappe 15.0.0-dev HEAD
387+
data=frappe._dict(
388+
output="""frappe 15.0.0-dev HEAD
388389
erpnext 0.8.3 HEAD
389-
""",
390+
"""
391+
),
390392
):
391393
new(
392394
{

press/press/doctype/agent_job/test_agent_job.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,24 @@
2222
from press.utils.test import foreground_enqueue_doc
2323

2424

25+
def fn_appender(before_insert: Callable, prepare_agent_responses: Callable):
26+
def new_before_insert(self):
27+
before_insert(self)
28+
prepare_agent_responses(self)
29+
30+
return new_before_insert
31+
32+
33+
before_insert: Callable = lambda self: None
34+
35+
2536
def fake_agent_job_req(
2637
job_name: str,
2738
status: Literal["Success", "Pending", "Running", "Failure"],
28-
output: str = "",
29-
steps: list[dict] = None,
39+
data: dict,
40+
steps: list[dict],
3041
) -> Callable:
42+
data = data or {}
3143
steps = steps or []
3244

3345
def prepare_agent_responses(self):
@@ -38,8 +50,10 @@ def prepare_agent_responses(self):
3850
steps: list of {"name": "Step name", "status": "status"} dictionaries
3951
"""
4052
nonlocal status
53+
nonlocal job_name
54+
if self.job_type != job_name: # only fake the job we want to fake
55+
return
4156
job_id = int(make_autoname(".#"))
42-
4357
if steps:
4458
needed_steps = frappe.get_all(
4559
"Agent Job Type Step", {"parent": job_name}, pluck="step_name"
@@ -79,9 +93,7 @@ def prepare_agent_responses(self):
7993
f"https://{self.server}:443/agent/jobs/{str(job_id)}",
8094
# TODO: populate steps with data from agent job type #
8195
json={
82-
"data": {
83-
"output": output,
84-
},
96+
"data": data,
8597
# TODO: uncomment lines as needed and make new parameters #
8698
"duration": "00:00:13.496281",
8799
"end": "2023-08-20 18:24:41.506067",
@@ -113,14 +125,16 @@ def prepare_agent_responses(self):
113125
status=200,
114126
)
115127

116-
return prepare_agent_responses
128+
global before_insert
129+
before_insert = fn_appender(before_insert, prepare_agent_responses)
130+
return before_insert
117131

118132

119133
@contextmanager
120134
def fake_agent_job(
121135
job_name: str,
122-
status: Literal["Success", "Pending", "Running", "Failure"],
123-
output: str = "",
136+
status: Literal["Success", "Pending", "Running", "Failure"] = "Success",
137+
data: dict = None,
124138
steps: list[dict] = None,
125139
):
126140
"""Fakes agent job request and response. Also polls the job.
@@ -130,7 +144,7 @@ def fake_agent_job(
130144
with responses.mock, patch.object(
131145
AgentJob,
132146
"before_insert",
133-
fake_agent_job_req(job_name, status, output, steps),
147+
fake_agent_job_req(job_name, status, data, steps),
134148
create=True,
135149
), patch(
136150
"press.press.doctype.agent_job.agent_job.frappe.enqueue_doc",
@@ -144,6 +158,8 @@ def fake_agent_job(
144158
{}
145159
) # due to bug in FF related to only_if_creator docperm
146160
yield
161+
global before_insert
162+
before_insert = lambda self: None # noqa
147163

148164

149165
@patch.object(AgentJob, "enqueue_http_request", new=Mock())

press/press/doctype/proxy_server/test_proxy_server.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818

1919
@patch.object(BaseServer, "after_insert", new=Mock())
20-
@patch.object(ProxyServer, "validate", new=Mock())
20+
@patch.object(ProxyServer, "validate_domains", new=Mock())
2121
def create_test_proxy_server(
2222
hostname: str = "n",
2323
domain: str = "fc.dev",

press/press/doctype/registry_server/registry_server.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ frappe.ui.form.on('Registry Server', {
88
[__('Ping Ansible Unprepared'), 'ping_ansible_unprepared', true],
99
[__('Prepare Server'), 'prepare_server', true, !frm.doc.is_server_setup],
1010
[__('Setup Server'), 'setup_server', true, !frm.doc.is_server_setup],
11+
[
12+
__('Update TLS Certificate'),
13+
'update_tls_certificate',
14+
true,
15+
frm.doc.is_server_setup,
16+
],
1117
[
1218
__('Fetch Keys'),
1319
'fetch_keys',

press/press/doctype/site_migration/site_migration.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33

44
frappe.ui.form.on('Site Migration', {
55
refresh: function (frm) {
6+
frm.set_query('site', () => {
7+
return {
8+
filters: {
9+
status: 'Active',
10+
},
11+
};
12+
});
613
frm.set_query('source_bench', () => {
714
return {
815
filters: {

press/press/doctype/site_migration/site_migration.py

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,11 @@ def add_steps_for_cluster_migration(self):
199199
"method_name": self.backup_source_site.__name__,
200200
"status": "Pending",
201201
},
202+
{
203+
"step_title": self.archive_site_on_destination_server.__doc__,
204+
"method_name": self.archive_site_on_destination_server.__name__,
205+
"status": "Pending",
206+
},
202207
{
203208
"step_title": self.restore_site_on_destination_server.__doc__,
204209
"method_name": self.restore_site_on_destination_server.__name__,
@@ -250,6 +255,11 @@ def add_steps_for_server_migration(self):
250255
"method_name": self.backup_source_site.__name__,
251256
"status": "Pending",
252257
},
258+
{
259+
"step_title": self.archive_site_on_destination_server.__doc__,
260+
"method_name": self.archive_site_on_destination_server.__name__,
261+
"status": "Pending",
262+
},
253263
{
254264
"step_title": self.restore_site_on_destination_server.__doc__,
255265
"method_name": self.restore_site_on_destination_server.__name__,
@@ -312,6 +322,13 @@ def backup_source_site(self):
312322

313323
return frappe.get_doc("Agent Job", backup.job)
314324

325+
def archive_site_on_destination_server(self):
326+
"""Archive site on destination (case of retry)"""
327+
agent = Agent(self.destination_server)
328+
site = frappe.get_doc("Site", self.site)
329+
site.bench = self.destination_bench
330+
return agent.archive_site(site, force=True)
331+
315332
def restore_site_on_destination_server(self):
316333
"""Restore site on destination"""
317334
agent = Agent(self.destination_server)
@@ -369,7 +386,9 @@ def reset_site_status_on_destination(self):
369386
self.run_next_step()
370387
job = None
371388
else:
372-
job = site.update_site_config({"maintenance_mode": 0})
389+
job = site.update_site_config(
390+
{"maintenance_mode": 0}
391+
) # will do run_next_step in callback
373392
site.reload()
374393
site.status = site.status_before_update
375394
site.status_before_update = None
@@ -387,11 +406,11 @@ def adjust_plan_if_required(self):
387406
destination_server_team = frappe.db.get_value(
388407
"Server", self.destination_server, "team"
389408
)
390-
if site.team != destination_server_team:
409+
if site.team == destination_server_team:
410+
site.change_plan("Unlimited")
411+
self.update_next_step_status("Success")
412+
else:
391413
self.update_next_step_status("Skipped")
392-
return
393-
site.change_plan("Unlimited")
394-
self.update_next_step_status("Success")
395414
self.run_next_step()
396415

397416

press/press/doctype/site_migration/test_site_migration.py

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,90 @@
22
# Copyright (c) 2021, Frappe and Contributors
33
# See license.txt
44

5+
import frappe
6+
from unittest.mock import patch
57

6-
# import frappe
7-
import unittest
8+
from press.press.doctype.remote_file.remote_file import RemoteFile
89

10+
from frappe.tests.utils import FrappeTestCase
11+
from press.press.doctype.agent_job.agent_job import poll_pending_jobs
12+
from press.press.doctype.agent_job.test_agent_job import fake_agent_job
913

10-
class TestSiteMigration(unittest.TestCase):
11-
pass
14+
from press.press.doctype.site.test_site import create_test_bench, create_test_site
15+
16+
17+
class TestSiteMigration(FrappeTestCase):
18+
@patch.object(RemoteFile, "download_link", new="http://test.com")
19+
def test_in_cluster_site_migration_goes_through_all_steps_and_updates_site(self):
20+
site = create_test_site()
21+
bench = create_test_bench()
22+
site_migration = frappe.get_doc(
23+
{
24+
"doctype": "Site Migration",
25+
"site": site.name,
26+
"destination_bench": bench.name,
27+
}
28+
).insert()
29+
30+
with fake_agent_job("Update Site Configuration"), fake_agent_job(
31+
"Backup Site",
32+
data={
33+
"backups": {
34+
"database": {
35+
"file": "a.sql.gz",
36+
"path": "/home/frappe/a.sql.gz",
37+
"size": 1674818,
38+
"url": "https://a.com/a.sql.gz",
39+
},
40+
"public": {
41+
"file": "b.tar",
42+
"path": "/home/frappe/b.tar",
43+
"size": 1674818,
44+
"url": "https://a.com/b.tar",
45+
},
46+
"private": {
47+
"file": "a.tar",
48+
"path": "/home/frappe/a.tar",
49+
"size": 1674818,
50+
"url": "https://a.com/a.tar",
51+
},
52+
"site_config": {
53+
"file": "a.json",
54+
"path": "/home/frappe/a.json",
55+
"size": 595,
56+
"url": "https://a.com/json",
57+
},
58+
},
59+
"offsite": {
60+
"a.sql.gz": "bucket.frappe.cloud/2023-10-10/a.sql.gz",
61+
"a.tar": "bucket.frappe.cloud/2023-10-10/a.tar",
62+
"b.tar": "bucket.frappe.cloud/2023-10-10/b.tar",
63+
"a.json": "bucket.frappe.cloud/2023-10-10/a.json",
64+
},
65+
},
66+
), fake_agent_job("New Site from Backup"), fake_agent_job(
67+
"Archive Site"
68+
), fake_agent_job(
69+
"Remove Site from Upstream"
70+
), fake_agent_job(
71+
"Add Site to Upstream"
72+
), fake_agent_job(
73+
"Update Site Configuration"
74+
):
75+
site_migration.start()
76+
poll_pending_jobs()
77+
poll_pending_jobs()
78+
poll_pending_jobs()
79+
poll_pending_jobs()
80+
poll_pending_jobs()
81+
poll_pending_jobs()
82+
poll_pending_jobs()
83+
site_migration.reload()
84+
self.assertEqual(site_migration.status, "Running")
85+
poll_pending_jobs()
86+
site_migration.reload()
87+
self.assertEqual(site_migration.status, "Success")
88+
site.reload()
89+
self.assertEqual(site.status, "Active")
90+
self.assertEqual(site.bench, bench.name)
91+
self.assertEqual(site.server, bench.server)

press/press/doctype/tls_certificate/tls_certificate.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ def update_server_tls_certifcate(server, certificate):
163163
proxysql_admin_password = server.get_password("proxysql_admin_password")
164164
ansible = Ansible(
165165
playbook="tls.yml",
166-
user=server.ssh_user or "root",
167-
port=server.ssh_port or 22,
166+
user=server.get("ssh_user") or "root",
167+
port=server.get("ssh_port") or 22,
168168
server=server,
169169
variables={
170170
"certificate_private_key": certificate.private_key,

0 commit comments

Comments
 (0)