diff --git a/frappe_manager/commands.py b/frappe_manager/commands.py index 972fa7c8..b3b38659 100644 --- a/frappe_manager/commands.py +++ b/frappe_manager/commands.py @@ -615,7 +615,10 @@ def update( choices=['yes', 'no'], ) if should_restart == 'yes': - bench.restart_frappe_server() + # bench.restart_frappe_server() + richprint.change_head("Restarting frappe server") + bench.restart_supervisor_service('frappe') + richprint.print("Restarted frappe server") if bench_config_save: bench.save_bench_config() @@ -641,3 +644,41 @@ def reset( verbose = ctx.obj['verbose'] bench = Bench.get_object(benchname, services_manager) bench.reset(admin_pass) + + +@app.command() +def restart( + ctx: typer.Context, + benchname: Annotated[ + Optional[str], + typer.Argument( + help="Name of the bench.", autocompletion=sites_autocompletion_callback, callback=sitename_callback + ), + ] = None, + web: Annotated[ + bool, + typer.Option(help="Restart web service i.e socketio and frappe server."), + ] = True, + workers: Annotated[ + bool, + typer.Option(help="Restart worker services i.e schedule and all workers."), + ] = True, + redis: Annotated[ + bool, + typer.Option(help="Restart redis services."), + ] = False, +): + """Restart bench services.""" + + services_manager = ctx.obj["services"] + verbose = ctx.obj['verbose'] + bench = Bench.get_object(benchname, services_manager) + + if web: + bench.restart_web_containers_services() + + if workers: + bench.restart_workers_containers_services() + + if redis: + bench.restart_redis_services_containers() diff --git a/frappe_manager/compose_project/exceptions.py b/frappe_manager/compose_project/exceptions.py index c9f0ca33..7a3f31ad 100644 --- a/frappe_manager/compose_project/exceptions.py +++ b/frappe_manager/compose_project/exceptions.py @@ -40,7 +40,7 @@ def __init__( class DockerComposeProjectFailedToRestartError(Exception): def __init__( - self, compose_path: Path, services: List[str], message='Failed to pull compose services {} images.' + self, compose_path: Path, services: List[str], message='Failed to restart compose services {} images.' ) -> None: self.compose_path = compose_path self.services = services diff --git a/frappe_manager/site_manager/bench_operations.py b/frappe_manager/site_manager/bench_operations.py index b21d9b1e..9280605d 100644 --- a/frappe_manager/site_manager/bench_operations.py +++ b/frappe_manager/site_manager/bench_operations.py @@ -2,6 +2,7 @@ from pathlib import Path from typing import Dict, List, Optional, Tuple from frappe_manager import STABLE_APP_BRANCH_MAPPING_LIST +from frappe_manager.compose_project.compose_project import ComposeProject from frappe_manager.docker_wrapper.DockerException import DockerException from frappe_manager.docker_wrapper.subprocess_output import SubprocessOutput from frappe_manager.site_manager.site_exceptions import ( @@ -47,7 +48,7 @@ def create_fm_bench(self): self.bench_install_apps(self.bench.bench_config.apps_list) - self.frappe_container_run( + self.container_run( "rm -rf /workspace/frappe-bench/archived", BenchOperationException(self.bench.name, "Failed to remove /workspace/frappe-bench/archived directory."), ) @@ -72,18 +73,16 @@ def create_bench_site(self): new_site_command = " ".join(new_site_command) - self.frappe_container_run( - new_site_command, raise_exception_obj=BenchOperationBenchSiteCreateFailed(self.bench.name) - ) + self.container_run(new_site_command, raise_exception_obj=BenchOperationBenchSiteCreateFailed(self.bench.name)) - self.frappe_container_run( + self.container_run( " ".join(self.bench_cli_cmd + [f"use {self.bench.name}"]), raise_exception_obj=BenchOperationException( self.bench.name, f"Failed to set {self.bench.name} as default site." ), ) - self.frappe_container_run( + self.container_run( " ".join(self.bench_cli_cmd + [f"--site {self.bench.name} scheduler enable"]), raise_exception_obj=BenchOperationException( self.bench.name, f"Failed to enable {self.bench.name}'s scheduler." @@ -103,23 +102,31 @@ def is_required_services_available(self): if output.combined: richprint.print(output.combined[-1].replace('wait-for-it: ', ''), highlight=False) - def frappe_container_run( + def container_run( self, command: str, raise_exception_obj: Optional[BenchOperationException] = None, capture_output: bool = False, user: str = "frappe", workdir="/workspace/frappe-bench", + service: str = 'frappe', + compose_project_obj: Optional[ComposeProject] = None, ): + + if compose_project_obj: + compose_project: ComposeProject = compose_project_obj + else: + compose_project: ComposeProject = self.bench.compose_project + try: if capture_output: - output: SubprocessOutput = self.bench.compose_project.docker.compose.exec( - service="frappe", command=command, user=user, workdir=workdir, stream=not capture_output + output: SubprocessOutput = compose_project.docker.compose.exec( + service=service, command=command, user=user, workdir=workdir, stream=not capture_output ) return output else: - output: Iterable[Tuple[str, bytes]] = self.bench.compose_project.docker.compose.exec( - service="frappe", command=command, workdir=workdir, user=user, stream=not capture_output + output: Iterable[Tuple[str, bytes]] = compose_project.docker.compose.exec( + service=service, command=command, workdir=workdir, user=user, stream=not capture_output ) richprint.live_lines(output) @@ -143,7 +150,7 @@ def change_frappeverse_prebaked_app_branch(self, app: str, branch: str): bench_name=self.bench.name, app=app, branch=self.bench.bench_config.frappe_branch ) - self.frappe_container_run(command=change_frappe_branch_command, raise_exception_obj=exception) + self.container_run(command=change_frappe_branch_command, raise_exception_obj=exception) richprint.print(f"Configured {app} app's branch -> {self.bench.bench_config.frappe_branch}") @@ -163,7 +170,7 @@ def setup_supervisor(self, force: bool = False): bench_setup_supervisor_exception = BenchOperationException( self.bench.name, "Failed to configure supervisor." ) - self.frappe_container_run(bench_setup_supervisor_command, bench_setup_supervisor_exception) + self.container_run(bench_setup_supervisor_command, bench_setup_supervisor_exception) self.split_supervisor_config() richprint.print("Configured supervisor configs") @@ -196,12 +203,10 @@ def split_supervisor_config(self): self.bench.logger.info(f"Split supervisor conf {section_name} => {file_name}") def setup_frappe_server_config(self): - bench_serve_help_output: Optional[SubprocessOutput] = self.frappe_container_run( + bench_serve_help_output: Optional[SubprocessOutput] = self.container_run( " ".join(self.bench_cli_cmd + ["serve --help"]), capture_output=True ) - bench_dev_server_script_output = self.frappe_container_run( - "cat /opt/user/bench-dev-server", capture_output=True - ) + bench_dev_server_script_output = self.container_run("cat /opt/user/bench-dev-server", capture_output=True) import re if "host" in " ".join(bench_serve_help_output.combined): @@ -213,8 +218,8 @@ def setup_frappe_server_config(self): r"--port \d+", "--port 80", " ".join(bench_dev_server_script_output.combined) ) - self.frappe_container_run(f'echo "{new_bench_dev_server_script}" > /opt/user/bench-dev-server.sh') - self.frappe_container_run("chmod +x /opt/user/bench-dev-server.sh", user='root') + self.container_run(f'echo "{new_bench_dev_server_script}" > /opt/user/bench-dev-server.sh') + self.container_run("chmod +x /opt/user/bench-dev-server.sh", user='root') def bench_install_apps(self, apps_lists, already_installed_apps: Dict = STABLE_APP_BRANCH_MAPPING_LIST): to_install_apps = [x["app"] for x in apps_lists] @@ -274,7 +279,7 @@ def bench_build(self, app_list: Optional[List[str]] = None): build_exception = BenchOperationBenchBuildFailed(bench_name=self.bench.name, apps=app_list) build_cmd = " ".join(build_cmd) - self.frappe_container_run(build_cmd, build_exception) + self.container_run(build_cmd, build_exception) def bench_install_app_env( self, app: str, branch: Optional[str] = None, overwrite: bool = True, skip_assets: bool = False @@ -289,7 +294,7 @@ def bench_install_app_env( app_install_env_command = " ".join(app_install_env_command) app_install_exception = BenchOperationBenchInstallAppInPythonEnvFailed(bench_name=self.bench.name, app_name=app) - self.frappe_container_run( + self.container_run( app_install_env_command, raise_exception_obj=app_install_exception, ) @@ -304,7 +309,7 @@ def bench_rm_app_env(self, app: str, no_backup: bool = True, force: bool = True) app_rm_env_command = " ".join(app_rm_env_command) - self.frappe_container_run( + self.container_run( app_rm_env_command, raise_exception_obj=BenchOperationBenchRemoveAppFromPythonEnvFailed( bench_name=self.bench.name, app_name=app @@ -316,7 +321,7 @@ def bench_install_app_site(self, app: str): app_install_site_command += ["install-app", app] app_install_site_command = " ".join(app_install_site_command) - self.frappe_container_run( + self.container_run( app_install_site_command, raise_exception_obj=BenchOperationBenchAppInSiteFailed(bench_name=self.bench.name, app_name=app), ) @@ -326,7 +331,7 @@ def is_bench_site_exists(self, bench_site_name: str): return site_path.exists() def wait_for_required_service(self, host: str, port: int, timeout: int = 120): - return self.frappe_container_run( + return self.container_run( f"wait-for-it -t {timeout} {host}:{port}", raise_exception_obj=BenchOperationWaitForRequiredServiceFailed( bench_name=self.bench.name, host=host, port=port, timeout=timeout @@ -375,7 +380,7 @@ def reset_bench_site(self, admin_password: str): reset_bench_site_command = " ".join(reset_bench_site_command) - self.frappe_container_run( + self.container_run( reset_bench_site_command, raise_exception_obj=BenchOperationException( bench_name=self.bench.name, message=f'Failed to reset bench site {self.bench.name}.' diff --git a/frappe_manager/site_manager/site.py b/frappe_manager/site_manager/site.py index 217aecf2..e8f98fca 100644 --- a/frappe_manager/site_manager/site.py +++ b/frappe_manager/site_manager/site.py @@ -152,7 +152,9 @@ def sync_bench_config_configuration(self): self.admin_tools.disable() richprint.print("Disabled Admin-tools.") - self.restart_frappe_server() + richprint.change_head("Restarting frappe server") + self.restart_supervisor_service('frappe') + richprint.print("Restarted frappe server") def save_bench_config(self): richprint.change_head("Saving bench config changes") @@ -163,14 +165,6 @@ def save_bench_config(self): def exists(self): return self.path.exists() - @property - def frappe_container_name_as_hex(self) -> str: - """ - Returns the hexadecimal representation of the frappe container name. - """ - container_name = self.compose_project.compose_file_manager.get_container_names() - return container_name["frappe"].encode().hex() - def create(self, is_template_bench: bool = False): """ Creates a new bench using the provided template inputs. @@ -861,8 +855,6 @@ def logs(self, follow: bool, service: Optional[SiteServicesEnum] = None): except KeyboardInterrupt: richprint.stdout.print("Detected CTRL+C. Exiting..") - # for log_file in log_files: - # log_file.close() def attach_to_bench(self, user: str, extensions: List[str], workdir: str, debugger: bool = False): """ @@ -883,7 +875,8 @@ def attach_to_bench(self, user: str, extensions: List[str], workdir: str, debugg # TODO todo this should be exception richprint.exit("Visual Studio Code binary i.e 'code' is not accessible via cli.") - container_hex = self.frappe_container_name_as_hex + container_name = self.compose_project.compose_file_manager.get_container_names() + container_hex = container_name["frappe"].encode().hex() vscode_cmd = shlex.join( [ @@ -1058,16 +1051,6 @@ def sync_admin_tools_compose(self): restart_required = self.admin_tools.enable(force_recreate_container=True) return restart_required - def restart_frappe_server(self): - richprint.change_head("Restarting frappe server") - restart_command = 'supervisorctl -c /opt/user/supervisord.conf restart all' - - try: - self.compose_project.docker.compose.exec('frappe', restart_command, user='frappe', stream=False) - except DockerException as e: - raise BenchException("frappe", "Faild to restart frappe server.") - richprint.print("Restarted frappe server.") - def frappe_service_run_command(self, command: str): try: self.compose_project.docker.compose.exec('frappe', command, user='frappe', stream=False) @@ -1210,3 +1193,70 @@ def reset(self, admin_password: Optional[str] = None): self.set_bench_site_config({'admin_password': admin_pass}) richprint.print(f"Reset bench site {self.name}") + + def restart_supervisor_service(self, service: str, compose_project_obj: Optional[ComposeProject] = None): + restart_supervisor_command = 'supervisorctl -c /opt/user/supervisord.conf restart all' + exception = BenchOperationException(self.name, message=f'Failed to restart supervisor for {service} service') + + if not compose_project_obj: + compose_project_obj = self.compose_project + + if not compose_project_obj.is_service_running(service): + richprint.error(text=f'Service [blue]{service}[/blue] not running.') + return False + + self.benchops.container_run( + command=restart_supervisor_command, + raise_exception_obj=exception, + service=service, + compose_project_obj=compose_project_obj, + ) + return True + + def restart_web_containers_services(self): + """Restarts frappe server and socketio containers""" + + # restart frappe server and socketio + web_services = [ + SiteServicesEnum.frappe.value, + SiteServicesEnum.socketio.value, + ] + + restart_supervisor_command = 'supervisorctl -c /opt/user/supervisord.conf restart all' + + for service in web_services: + richprint.change_head(f"Restarting web services - {service}") + is_restarted = self.restart_supervisor_service(service) + if is_restarted: + richprint.print(f"Restarted web services - {service}") + + def restart_redis_services_containers(self): + """Restarts redis containers""" + + redis_services = [ + SiteServicesEnum.redis_cache.value, + SiteServicesEnum.redis_queue.value, + SiteServicesEnum.redis_socketio.value, + ] + richprint.change_head(f"Restarting redis services - {' '.join(redis_services)}") + self.compose_project.restart_service(services=redis_services) + richprint.print(f"Restarted redis services - {' '.join(redis_services)}") + + def restart_workers_containers_services(self): + """Restarts workers and schedule containers""" + + # restart schduler + worker_services = [SiteServicesEnum.schedule.value] + + for service in worker_services: + richprint.change_head(f"Restarting worker service - {service}") + is_restarted = self.restart_supervisor_service(service) + if is_restarted: + richprint.print(f"Restarted worker services - {service}") + + worker_services = self.workers.compose_project.compose_file_manager.get_services_list() + for service in worker_services: + richprint.change_head(f"Restarting worker service - {service}") + is_restarted = self.restart_supervisor_service(service, compose_project_obj=self.workers.compose_project) + if is_restarted: + richprint.print(f"Restarted worker services - {service}") diff --git a/frappe_manager/utils/examples.json b/frappe_manager/utils/examples.json index 44771fcb..a777c2ef 100644 --- a/frappe_manager/utils/examples.json +++ b/frappe_manager/utils/examples.json @@ -271,5 +271,17 @@ "code": "" } ] + }, + "restart": { + "examples": [ + { + "desc": "Restart web services only", + "code": "--web" + }, + { + "desc": "Restart workers and web services", + "code": "--web --workers" + } + ] } }