update_manager: Implement auto refresh

The slower system package update will now only executed by the auto-refresh routine.  Moonraker will check for updates roughly every 2 hours, however system packages updates will only occur between 1am and 4am local time.

If a print is in progress any attempt to refresh or update will be aborted.

Signed-off-by:  Eric Callahan <arksine.code@gmail.com>
This commit is contained in:
Arksine 2021-01-03 15:56:41 -05:00
parent 2bb9128b5b
commit 5b9f637c2d
1 changed files with 77 additions and 6 deletions

View File

@ -14,7 +14,7 @@ import io
import asyncio import asyncio
import time import time
import tornado.gen import tornado.gen
from tornado.ioloop import IOLoop from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.httpclient import AsyncHTTPClient from tornado.httpclient import AsyncHTTPClient
from tornado.locks import Event, Condition, Lock from tornado.locks import Event, Condition, Lock
@ -25,6 +25,13 @@ SUPPLEMENTAL_CFG_PATH = os.path.join(
APT_CMD = "sudo DEBIAN_FRONTEND=noninteractive apt-get" APT_CMD = "sudo DEBIAN_FRONTEND=noninteractive apt-get"
SUPPORTED_DISTROS = ["debian"] SUPPORTED_DISTROS = ["debian"]
# Check For Updates Every 2 Hours
UPDATE_REFRESH_TIME = 7200000
# Refresh APT Repo no sooner than 12 hours
MIN_PKG_UPDATE_INTERVAL = 43200
# Refresh APT Repo no later than 5am
MAX_PKG_UPDATE_HOUR = 5
class UpdateManager: class UpdateManager:
def __init__(self, config): def __init__(self, config):
self.server = config.get_server() self.server = config.get_server()
@ -61,6 +68,12 @@ class UpdateManager:
self.cmd_request_lock = Lock() self.cmd_request_lock = Lock()
self.is_refreshing = False self.is_refreshing = False
# Auto Status Refresh
self.last_package_refresh_time = 0
self.refresh_cb = PeriodicCallback(
self._handle_auto_refresh, UPDATE_REFRESH_TIME)
self.refresh_cb.start()
self.server.register_endpoint( self.server.register_endpoint(
"/machine/update/moonraker", ["POST"], "/machine/update/moonraker", ["POST"],
self._handle_update_request) self._handle_update_request)
@ -97,7 +110,60 @@ class UpdateManager:
return return
self.updaters['klipper'] = GitUpdater(self, "klipper", kpath, env) self.updaters['klipper'] = GitUpdater(self, "klipper", kpath, env)
async def _check_klippy_printing(self):
klippy_apis = self.server.lookup_plugin('klippy_apis')
result = await klippy_apis.query_objects(
{'print_stats': None}, default={})
pstate = result.get('print_stats', {}).get('state', "")
return pstate.lower() == "printing"
async def _handle_auto_refresh(self):
if await self._check_klippy_printing():
# Don't Refresh during a print
logging.info("Klippy is printing, auto refresh aborted")
return
vinfo = {}
need_refresh_all = not self.is_refreshing
async with self.cmd_request_lock:
self.is_refreshing = True
cur_time = time.time()
cur_hour = time.localtime(cur_time).tm_hour
time_diff = cur_time - self.last_package_refresh_time
try:
# Update packages if it has been more than 12 hours
# and the local time is between 12AM and 5AM
if time_diff > MIN_PKG_UPDATE_INTERVAL and \
cur_hour <= MAX_PKG_UPDATE_HOUR:
self.last_package_refresh_time = cur_time
sys_updater = self.updaters['system']
await sys_updater.refresh(True)
vinfo['system'] = sys_updater.get_update_status()
for name, updater in list(self.updaters.items()):
if name in vinfo:
# System was refreshed and added to version info
continue
if need_refresh_all:
ret = updater.refresh()
if asyncio.iscoroutine(ret):
await ret
if hasattr(updater, "get_update_status"):
vinfo[name] = updater.get_update_status()
except Exception:
logging.exception("Unable to Refresh Status")
return
finally:
self.is_refreshing = False
uinfo = {
'version_info': vinfo,
'github_rate_limit': self.gh_rate_limit,
'github_requests_remaining': self.gh_limit_remaining,
'github_limit_reset_time': self.gh_limit_reset_time,
'busy': self.current_update is not None}
self.server.send_event("update_manager:update_refreshed", uinfo)
async def _handle_update_request(self, web_request): async def _handle_update_request(self, web_request):
if await self._check_klippy_printing():
raise self.server.error("Update Refused: Klippy is printing")
app = web_request.get_endpoint().split("/")[-1] app = web_request.get_endpoint().split("/")[-1]
inc_deps = web_request.get_boolean('include_deps', False) inc_deps = web_request.get_boolean('include_deps', False)
if self.current_update is not None and \ if self.current_update is not None and \
@ -118,9 +184,12 @@ class UpdateManager:
async def _handle_status_request(self, web_request): async def _handle_status_request(self, web_request):
check_refresh = web_request.get_boolean('refresh', False) check_refresh = web_request.get_boolean('refresh', False)
# Don't refresh if an update is currently in progress, # Don't refresh if a print is currently in progress or
# just return current state # if an update is in progress. Just return the current
check_refresh &= self.current_update is None # state
if self.current_update is not None or \
await self._check_klippy_printing():
check_refresh = False
need_refresh = False need_refresh = False
if check_refresh: if check_refresh:
# If there is an outstanding request processing a # If there is an outstanding request processing a
@ -294,6 +363,7 @@ class UpdateManager:
def close(self): def close(self):
self.http_client.close() self.http_client.close()
self.refresh_cb.stop()
class GitUpdater: class GitUpdater:
@ -631,7 +701,7 @@ class PackageUpdater:
self.refresh_condition = None self.refresh_condition = None
IOLoop.current().spawn_callback(self.refresh) IOLoop.current().spawn_callback(self.refresh)
async def refresh(self): async def refresh(self, fetch_packages=False):
# TODO: Use python-apt python lib rather than command line for updates # TODO: Use python-apt python lib rather than command line for updates
if self.refresh_condition is None: if self.refresh_condition is None:
self.refresh_condition = Condition() self.refresh_condition = Condition()
@ -639,7 +709,8 @@ class PackageUpdater:
self.refresh_condition.wait() self.refresh_condition.wait()
return return
try: try:
await self.execute_cmd(f"{APT_CMD} update", timeout=300.) if fetch_packages:
await self.execute_cmd(f"{APT_CMD} update", timeout=300.)
res = await self.execute_cmd_with_response( res = await self.execute_cmd_with_response(
"apt list --upgradable") "apt list --upgradable")
pkg_list = [p.strip() for p in res.split("\n") if p.strip()] pkg_list = [p.strip() for p in res.split("\n") if p.strip()]