Delaying reboot/shutdown with asyncio sleep to give API time to shut down.

This commit is contained in:
Roland 2023-08-18 11:55:14 -06:00
parent 5ffb8bee8d
commit 4cc00a9cb1
No known key found for this signature in database
GPG key ID: 9D6DBD3ACF11AB93
9 changed files with 71 additions and 49 deletions

View file

@ -32,6 +32,14 @@ function handleEvents(events) {
eventToast('Refresh completed', 'Files have been refreshed.');
}
if (event === 'shutdown') {
eventToast('Shutdown', message, 'warning');
}
if (event === 'shutdown_failed') {
eventToast('Shutdown Failed', message, 'error');
}
if (event === 'downloads_disabled') {
eventToast('Downloads Disabled', message, 'info');
}

View file

@ -21,6 +21,13 @@ export function ShutdownButton() {
time: 5000,
type: "error",
})
} else if (response && response['code'] === 'NATIVE_ONLY') {
toast({
title: 'Shutdown Failed',
description: 'Cannot shutdown while running in Docker',
time: 5000,
type: "error",
})
} else if (response !== null) {
toast({
title: 'Shutdown Failed',
@ -58,13 +65,20 @@ export function RestartButton() {
const handleRestart = async () => {
try {
const response = await postRestart();
if (response && response['code'] === 'RESTART_FAILED') {
if (response && response['code'] === 'SHUTDOWN_FAILED') {
toast({
title: 'Restart Failed',
description: response['error'],
time: 5000,
type: "error",
})
} else if (response && response['code'] === 'NATIVE_ONLY') {
toast({
title: 'Restart Failed',
description: 'Cannot restart while running in Docker',
time: 5000,
type: "error",
})
} else if (response !== null) {
toast({
title: 'Restart Failed',

View file

@ -67,8 +67,7 @@ cat >/etc/sudoers.d/90-wrolpi <<'EOF'
%wrolpi ALL= NOPASSWD:/usr/bin/systemctl stop wrolpi-app.service
%wrolpi ALL= NOPASSWD:/usr/bin/systemctl start wrolpi-app.service
%wrolpi ALL= NOPASSWD:/opt/wrolpi/scripts/import_map.sh
%wrolpi ALL= NOPASSWD:/usr/sbin/poweroff
%wrolpi ALL= NOPASSWD:/usr/sbin/reboot
%wrolpi ALL= NOPASSWD:/usr/sbin/shutdown
EOF
chmod 660 /etc/sudoers.d/90-wrolpi

View file

@ -89,8 +89,7 @@ cat >/etc/sudoers.d/90-wrolpi <<'EOF'
%wrolpi ALL= NOPASSWD:/usr/bin/systemctl stop wrolpi-app.service
%wrolpi ALL= NOPASSWD:/usr/bin/systemctl start wrolpi-app.service
%wrolpi ALL= NOPASSWD:/opt/wrolpi/scripts/import_map.sh
%wrolpi ALL= NOPASSWD:/usr/sbin/poweroff
%wrolpi ALL= NOPASSWD:/usr/sbin/reboot
%wrolpi ALL= NOPASSWD:/usr/sbin/shutdown
EOF
chmod 660 /etc/sudoers.d/90-wrolpi
# Verify this new file is valid.

View file

@ -99,8 +99,7 @@ cat >/etc/sudoers.d/90-wrolpi <<'EOF'
%wrolpi ALL= NOPASSWD:/usr/bin/systemctl stop wrolpi-app.service
%wrolpi ALL= NOPASSWD:/usr/bin/systemctl start wrolpi-app.service
%wrolpi ALL= NOPASSWD:/opt/wrolpi/scripts/import_map.sh
%wrolpi ALL= NOPASSWD:/usr/sbin/poweroff
%wrolpi ALL= NOPASSWD:/usr/sbin/reboot
%wrolpi ALL= NOPASSWD:/usr/sbin/shutdown
EOF
chmod 660 /etc/sudoers.d/90-wrolpi
# Verify this new file is valid.

View file

@ -3,8 +3,9 @@ import enum
import subprocess
from wrolpi.cmd import NMCLI_BIN, CPUFREQ_INFO_BIN, SUDO_BIN, CPUFREQ_SET_BIN
from wrolpi.common import logger, WROLPI_CONFIG, get_warn_once
from wrolpi.errors import RestartFailed, ShutdownFailed
from wrolpi.common import logger, WROLPI_CONFIG, get_warn_once, background_task
from wrolpi.errors import ShutdownFailed
from wrolpi.events import Events
from wrolpi.vars import DEFAULT_CPU_FREQUENCY, DOCKERIZED
logger = logger.getChild(__name__)
@ -53,7 +54,7 @@ def enable_hotspot():
logger.info(f'Hotspot status: {status}')
if status == HotspotStatus.connected:
logger.info('Hotspot already connected. Restarting hotspot.')
logger.info('Hotspot already connected. Rebooting hotspot.')
disable_hotspot()
return enable_hotspot()
elif status == HotspotStatus.disconnected:
@ -145,39 +146,39 @@ def throttle_cpu_off() -> bool:
logger.error('Could not disable CPU throttle', exc_info=True)
async def restart():
"""Restart the WROLPi.
async def shutdown(reboot: bool = False, delay: int = 10):
"""Shutdown or reboot the WROLPi. Schedules an asyncio sleep to delay the shutdown.
@raise RestartFailed: If restart is not possible, or if sudo command failed."""
@raise ShutdownFailed: If not possible, or if sudo command failed."""
if DOCKERIZED:
raise RestartFailed('Cannot restart because we are dockerized')
raise ShutdownFailed('Cannot Reboot because we are dockerized')
try:
cmd = f'{SUDO_BIN} reboot'
proc = await asyncio.create_subprocess_shell(cmd, stderr=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
except subprocess.CalledProcessError as e:
raise RestartFailed() from e
reboot = '--reboot' if reboot else ''
if proc.returncode != 0:
raise RestartFailed(f'Got return code {proc.returncode}')
async def _():
await asyncio.sleep(delay)
try:
cmd = f'{SUDO_BIN} shutdown {reboot}'
proc = await asyncio.create_subprocess_shell(cmd, stderr=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
except subprocess.CalledProcessError as e:
if reboot:
Events.send_shutdown_failed('Reboot failed')
else:
Events.send_shutdown_failed('Shutdown failed')
raise ShutdownFailed() from e
async def shutdown():
"""Shutdown the WROLPi.
if proc.returncode != 0:
if reboot:
Events.send_shutdown_failed('Reboot failed')
else:
Events.send_shutdown_failed('Shutdown failed')
raise ShutdownFailed(f'Got return code {proc.returncode}')
@raise ShutdownFailed: If shutdown is not possible, or if sudo command failed."""
if DOCKERIZED:
raise ShutdownFailed('Cannot shutdown because we are dockerized')
try:
cmd = f'{SUDO_BIN} poweroff'
proc = await asyncio.create_subprocess_shell(cmd, stderr=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE)
stdout, stderr = await proc.communicate()
except subprocess.CalledProcessError as e:
raise ShutdownFailed() from e
if proc.returncode != 0:
raise ShutdownFailed(f'Got return code {proc.returncode}')
background_task(_())
if reboot:
Events.send_shutdown(f'Reboot in {delay} seconds')
else:
Events.send_shutdown(f'Shutdown in {delay} seconds')

View file

@ -121,12 +121,6 @@ class HotspotPasswordTooShort(APIError):
status = HTTPStatus.BAD_REQUEST
class RestartFailed(APIError):
code = 'RESTART_FAILED'
summary = 'Unable to restart the WROLPi'
status = HTTPStatus.INTERNAL_SERVER_ERROR
class ShutdownFailed(APIError):
code = 'SHUTDOWN_FAILED'
summary = 'Unable to shutdown the WROLPi'

View file

@ -73,6 +73,14 @@ class Events:
def send_map_import_failed(message: str = None):
send_event('map_import_failed', message, subject='map')
@staticmethod
def send_shutdown(message: str = None):
send_event('shutdown', message, subject='shutdown')
@staticmethod
def send_shutdown_failed(message: str = None):
send_event('shutdown_failed', message, subject='shutdown')
def log_event(event: str, message: str = None, action: str = None, subject: str = None):
log = f'{event=}'

View file

@ -25,7 +25,7 @@ from wrolpi.common import logger, get_config, wrol_mode_enabled, Base, get_media
from wrolpi.dates import now, strptime
from wrolpi.downloader import download_manager
from wrolpi.errors import WROLModeEnabled, APIError, HotspotError, InvalidDownload, \
HotspotPasswordTooShort, ShutdownFailed, RestartFailed
HotspotPasswordTooShort, NativeOnly
from wrolpi.events import get_events, Events
from wrolpi.files.lib import get_file_statistics, estimate_search
from wrolpi.vars import API_HOST, API_PORT, DOCKERIZED, API_DEBUG, API_ACCESS_LOG, API_WORKERS, API_AUTO_RELOAD, \
@ -504,16 +504,16 @@ async def post_search_estimate(_: Request, body: schema.SearchEstimateRequest):
@api_bp.post('/restart')
async def post_restart(_: Request):
if DOCKERIZED:
raise RestartFailed(f'Cannot restart when running in Docker')
raise NativeOnly(f'Cannot restart when running in Docker')
await admin.restart()
await admin.shutdown(reboot=True)
return response.empty(HTTPStatus.NO_CONTENT)
@api_bp.post('/shutdown')
async def post_shutdown(_: Request):
if DOCKERIZED:
raise ShutdownFailed(f'Cannot shutdown when running in Docker')
raise NativeOnly(f'Cannot shutdown when running in Docker')
await admin.shutdown()
return response.empty(HTTPStatus.NO_CONTENT)