Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 26 additions & 4 deletions press/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def __init__(self, server, server_type="Server"):
self.server = server
self.port = 443 if self.server not in servers_using_alternative_port_for_communication() else 8443

def new_bench(self, bench: "Bench"):
def _get_registry_credentials(self) -> tuple[str, str, str]:
settings = frappe.db.get_value(
"Press Settings",
None,
Expand All @@ -62,15 +62,37 @@ def new_bench(self, bench: "Bench"):
)
cluster = frappe.db.get_value(self.server_type, self.server, "cluster")
registry_url = frappe.db.get_value("Cluster", cluster, "repository") or settings.docker_registry_url
return settings.docker_registry_username, settings.docker_registry_password, registry_url

def setup_bench(self, bench: "Bench", primary_server: str | None = None) -> AgentJob:
docker_registry_username, docker_registry_password, registry_url = self._get_registry_credentials()

data = {
"name": bench.name,
"bench_config": json.loads(bench.bench_config),
"registry": {
"url": registry_url,
"username": docker_registry_username,
"password": docker_registry_password,
},
}

if primary_server:
data.update({"primary_server": primary_server})

self.create_agent_job("Setup Bench", "benches/setup", data=data, bench=bench)

def new_bench(self, bench: "Bench") -> AgentJob:
docker_registry_username, docker_registry_password, registry_url = self._get_registry_credentials()

data = {
"name": bench.name,
"bench_config": json.loads(bench.bench_config),
"common_site_config": json.loads(bench.config),
"registry": {
"url": registry_url,
"username": settings.docker_registry_username,
"password": settings.docker_registry_password,
"username": docker_registry_username,
"password": docker_registry_password,
},
}

Expand All @@ -84,7 +106,7 @@ def new_bench(self, bench: "Bench"):
for m in bench.mounts
]

return self.create_agent_job("New Bench", "benches", data, bench=bench.name)
return self.create_agent_job("New Bench", "benches/new", data, bench=bench.name)

def archive_bench(self, bench):
return self.create_agent_job("Archive Bench", f"benches/{bench.name}/archive", bench=bench.name)
Expand Down
2 changes: 1 addition & 1 deletion press/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ def overview(name):
@frappe.whitelist()
@protected(["Server", "Database Server"])
def archive(name):
server = poly_get_doc(["Server", "Database Server"], name)
server: Server | DatabaseServer = poly_get_doc(["Server", "Database Server"], name)
server.drop_server()


Expand Down
20 changes: 16 additions & 4 deletions press/fixtures/agent_job_type.json
Original file line number Diff line number Diff line change
Expand Up @@ -1111,11 +1111,8 @@
"modified": "2023-11-06 07:28:19.023648",
"name": "New Bench",
"request_method": "POST",
"request_path": "/benches",
"request_path": "/benches/new",
"steps": [
{
"step_name": "Initialize Bench"
},
{
"step_name": "Update Bench Configuration"
},
Expand All @@ -1127,6 +1124,21 @@
}
]
},
{
"disabled_auto_retry": 0,
"docstatus": 0,
"doctype": "Agent Job Type",
"max_retry_count": 6,
"modified": "2023-11-06 07:28:19.023648",
"name": "Setup Bench",
"request_method": "POST",
"request_path": "/benches/setup",
"steps": [
{
"step_name": "Setup Bench"
}
]
},
{
"disabled_auto_retry": 0,
"docstatus": 0,
Expand Down
2 changes: 1 addition & 1 deletion press/playbooks/roles/agent/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
- name: Generate Agent Configuration File
become: yes
become_user: frappe
command: '/home/frappe/agent/env/bin/agent setup config --name {{ server }} --workers {{ workers }} {% if proxy_ip is defined and proxy_ip is truthy %}--proxy-ip {{ proxy_ip }}{% endif %} {% if agent_sentry_dsn is defined and agent_sentry_dsn is truthy %}--sentry-dsn {{ agent_sentry_dsn }}{% endif %}'
command: '/home/frappe/agent/env/bin/agent setup config --name {{ server }} --workers {{ workers }} {% if proxy_ip is defined and proxy_ip is truthy %}--proxy-ip {{ proxy_ip }}{% endif %} {% if agent_sentry_dsn is defined and agent_sentry_dsn is truthy %}--sentry-dsn {{ agent_sentry_dsn }}{% endif %} {% if is_nfs_server is defined and is_nfs_server is truthy %}--is-nfs-server{% endif %}'
args:
chdir: /home/frappe/agent

Expand Down
3 changes: 3 additions & 0 deletions press/press/doctype/agent_job/agent_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,7 @@ def process_job_updates(job_name: str, response_data: dict | None = None): # no
process_archive_bench_job_update,
process_new_bench_job_update,
process_remove_ssh_user_job_update,
process_setup_bench_job_update,
)
from press.press.doctype.code_server.code_server import (
process_archive_code_server_job_update,
Expand Down Expand Up @@ -1006,6 +1007,8 @@ def process_job_updates(job_name: str, response_data: dict | None = None): # no
process_site_migration_job_update(job, site_migration)
elif job.job_type == "Add Upstream to Proxy":
process_new_server_job_update(job)
elif job.job_type == "Setup Bench":
process_setup_bench_job_update(job)
elif job.job_type == "New Bench":
process_new_bench_job_update(job)
elif job.job_type == "Archive Bench":
Expand Down
53 changes: 48 additions & 5 deletions press/press/doctype/bench/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,11 +459,28 @@ def update_bench_config(self, force=False):
agent.update_bench_config(self)

def after_insert(self):
self.create_agent_request()
self.setup_bench()

def setup_bench(self) -> None:
"""Create agent request to copy data and setup bench bind on the correct server"""
benches_on_shared_volume = frappe.db.get_value("Server", self.server, "benches_on_shared_volume")
if benches_on_shared_volume:
nfs_server = frappe.db.get_value(
"NFS Volume Attachment", {"status": "Success", "primary_server": self.server}, "nfs_server"
)
Agent(
server=nfs_server,
server_type="NFS Server",
).setup_bench(
self,
primary_server=self.server,
)
else:
Agent(self.server).setup_bench(self)

def create_agent_request(self):
agent = Agent(self.server)
agent.new_bench(self)
def deploy_bench(self) -> None:
"""Create agent request run the bench on the server request `setup_bench`"""
Agent(self.server).new_bench(self)

def _mark_applied_patch_as_archived(self):
frappe.db.set_value(
Expand Down Expand Up @@ -1166,12 +1183,38 @@ def archive_staging_sites():
StagingSite.archive_expired()


def process_new_bench_job_update(job):
def process_setup_bench_job_update(job):
"""This is a prerequisite to the new bench job however bench will still change
its status according to this jobs. As its successor will only be fired if this job is successful.
"""
bench = Bench("Bench", job.bench)

if job.status == "Success":
# Deploy the bench as soon as the job is successful
bench.deploy_bench()
return

# If the status is anything except success we will update bench status
updated_status = {
"Pending": "Pending",
"Running": "Installing",
"Failure": "Broken",
"Delivery Failure": "Broken",
}[job.status]

if updated_status == bench.status:
# Same status do nothing
return

frappe.db.set_value("Bench", job.bench, "status", updated_status)


def process_new_bench_job_update(job):
bench = Bench("Bench", job.bench)

updated_status = {
# "Pending": "Pending", # Does not make sense to backtrack our status
"Running": "Installing",
"Success": "Active",
"Failure": "Broken",
"Delivery Failure": "Broken",
Expand Down
25 changes: 23 additions & 2 deletions press/press/doctype/nfs_server/nfs_server.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
"virtual_machine",
"is_server_prepared",
"is_server_setup",
"billing_section",
"team",
"plan",
"networking_section",
"ip",
"column_break_9",
Expand Down Expand Up @@ -206,6 +209,23 @@
"fieldtype": "Link",
"label": "Cluster",
"options": "Cluster"
},
{
"fieldname": "team",
"fieldtype": "Link",
"label": "Team",
"options": "Team"
},
{
"fieldname": "billing_section",
"fieldtype": "Section Break",
"label": "Billing"
},
{
"fieldname": "plan",
"fieldtype": "Link",
"label": "Plan",
"options": "Server Plan"
}
],
"links": [
Expand All @@ -226,7 +246,7 @@
"link_fieldname": "nfs_server"
}
],
"modified": "2025-10-15 23:20:56.419708",
"modified": "2025-11-10 14:17:45.639068",
"modified_by": "Administrator",
"module": "Press",
"name": "NFS Server",
Expand All @@ -246,8 +266,9 @@
}
],
"row_format": "Dynamic",
"rows_threshold_for_grid_search": 20,
"sort_field": "modified",
"sort_order": "DESC",
"states": [],
"track_changes": 1
}
}
7 changes: 5 additions & 2 deletions press/press/doctype/nfs_server/nfs_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ class NFSServer(BaseServer):
is_server_prepared: DF.Check
is_server_setup: DF.Check
monitoring_password: DF.Password | None
plan: DF.Link | None
private_ip: DF.Data
private_mac_address: DF.Data | None
private_vlan_id: DF.Data | None
Expand All @@ -38,13 +39,14 @@ class NFSServer(BaseServer):
ssh_port: DF.Int
ssh_user: DF.Data | None
status: DF.Literal["Pending", "Installing", "Active", "Broken", "Archived"]
team: DF.Link | None
tls_certificate_renewal_failed: DF.Check
virtual_machine: DF.Link | None
# end: auto-generated types

def validate(self):
self.validate_agent_password()
# self.validate_monitoring_password()
self.validate_monitoring_password()

def validate_monitoring_password(self):
if not self.monitoring_password:
Expand All @@ -66,7 +68,7 @@ def _setup_server(self):
port=self._ssh_port(),
variables={
"server": self.name,
"workers": 1,
"workers": 8,
"domain": self.domain,
"agent_password": agent_password,
"agent_repository_url": agent_repository_url,
Expand All @@ -75,6 +77,7 @@ def _setup_server(self):
"certificate_private_key": certificate.private_key,
"certificate_full_chain": certificate.full_chain,
"certificate_intermediate_chain": certificate.intermediate_chain,
"is_nfs_server": True,
},
)
play = ansible.run()
Expand Down
15 changes: 15 additions & 0 deletions press/press/doctype/release_group/release_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,14 @@ def update_config(self, config):
self.update_config_in_release_group(sanitized_common_site_config, sanitized_bench_config)
self.update_benches_config()

@property
def has_scaled_up_servers(self) -> bool:
"""Check if this release group has servers that are currently scaled up"""
servers = frappe.db.get_all("Release Group Server", {"parent": self.name}, pluck="server")
return bool(
frappe.db.get_all("Server", {"name": ("IN", servers), "scaled_up": True}, pluck="name"),
)

def update_config_in_release_group(self, common_site_config, bench_config):
"""Updates bench_config and common_site_config in the Release Group

Expand Down Expand Up @@ -688,6 +696,13 @@ def create_deploy_candidate(
return None

self.check_app_server_storage()

if self.has_scaled_up_servers:
frappe.throw(
"Unable to create a new deploy as server(s) in this bench group are currently up scaled!",
frappe.ValidationError,
)

apps = self.get_apps_to_update(apps_to_update)
if apps_to_update is None:
self.validate_dc_apps_against_rg(apps)
Expand Down
19 changes: 19 additions & 0 deletions press/press/doctype/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,19 @@ def _get_app_and_database_servers(self) -> tuple[Server, DatabaseServer]:
@dashboard_whitelist()
def drop_server(self):
app_server, db_server = self._get_app_and_database_servers()

if not app_server.is_primary:
frappe.throw(
"This operation is only allowed on a primary server. Secondary servers cannot be dropped directly.",
frappe.ValidationError,
)

if app_server.benches_on_shared_volume:
frappe.throw(
"This server is currently using a shared volume. Please detach it from the NFS server before proceeding.",
frappe.ValidationError,
)

app_server.archive()
db_server.archive()

Expand Down Expand Up @@ -2429,6 +2442,12 @@ def drop_secondary_server(self) -> None:
@frappe.whitelist()
def setup_secondary_server(self, server_plan: str):
"""Setup secondary server"""
secondary_server_platform = frappe.db.get_value("Server Plan", server_plan, "platform")
if self.platform != secondary_server_platform:
frappe.throw(
"Please select a plan of the same platform as the primary server", frappe.ValidationError
)

if self.doctype == "Database Server" or self.is_secondary:
return

Expand Down
3 changes: 3 additions & 0 deletions press/press/doctype/virtual_machine/virtual_machine.py
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,9 @@ def update_subscription_for_nfs_addon_storage(self, volume: "VirtualMachineVolum
primary_server = frappe.db.get_value(
"NFS Volume Attachment", {"volume_id": volume.volume_id}, "primary_server"
)
if not primary_server:
"""Most likely the root volume"""
return
primary_server: Server = frappe.get_doc("Server", primary_server)
server_plan_disk_size = frappe.db.get_value("Server Plan", primary_server.plan, "disk")

Expand Down
Loading