ruạṛ
# Copyright (c) Cloud Linux Software, Inc # Licensed under CLOUD LINUX LICENSE AGREEMENT # http://cloudlinux.com/docs/LICENCE.TXT import functools import json import os import time from . import config, constants, log_utils, utils from .py23 import json_loads_nstr if False: # pragma: no cover from typing import Any, Callable, Dict # noqa: F401 STATUS_CHANGE_GAP_DELAY = 5 * 60 # 5 minute def touch_status_gap_file(filename='.kcarestatus'): status_filepath = os.path.join(constants.PATCH_CACHE, filename) utils.atomic_write(status_filepath, utils.timestamp_str()) def status_gap_passed(filename='.kcarestatus'): status_filepath = os.path.join(constants.PATCH_CACHE, filename) if os.path.isfile(status_filepath): with open(status_filepath, 'r') as sfile: try: timestamp = int(sfile.read()) if int(timestamp) + config.STATUS_CHANGE_GAP + STATUS_CHANGE_GAP_DELAY > time.time(): return False except Exception: pass return True def _check_component(component): # type: (str) -> None if component not in ('kernel', 'libcare'): raise ValueError('Unknown update status component: {0}'.format(component)) def _load_update_status(): # type: () -> Dict[str, Any] content = utils.read_file(constants.UPDATE_STATUS_PATH) if content is None: return {} try: result = json_loads_nstr(content) # type: Dict[str, Any] return result except (ValueError, TypeError): log_utils.kcarelog.warning('Failed to parse update status file') return {} def save_update_status(component, error): # type: (str, str) -> None _check_component(component) try: data = _load_update_status() data[component] = { 'error': error, 'timestamp': int(time.time()), } utils.atomic_write(constants.UPDATE_STATUS_PATH, json.dumps(data)) except Exception: log_utils.kcarelog.warning('Failed to save update status', exc_info=True) def _error_status(err): # type: (Exception) -> str # Prefer the short, fixed status label that KcareError subclasses set # (e.g. 'bad signature') for compact, groupable telemetry. When the status # is already echoed in the message (HTTPError's "HTTP Error 414: ...") the # message is the more useful value. When a numeric HTTP status is *not* in # the message, compose a readable "HTTP Error: <code>" rather than a bare # code. str() also keeps an int status subscriptable on read-back. message = str(err) status = str(getattr(err, 'status', '')) if status and status not in message: if status.isdigit(): return 'HTTP Error: {0}'.format(status) return status return message def track_update_status(component): # type: (str) -> Callable[..., Any] _check_component(component) def decorator(fn): # type: (Callable[..., Any]) -> Callable[..., Any] @functools.wraps(fn) def inner(*args, **kwargs): # type: (Any, Any) -> Any try: result = fn(*args, **kwargs) except Exception as err: save_update_status(component, error=_error_status(err)) raise save_update_status(component, error='') return result return inner return decorator def read_update_error(component): # type: (str) -> str _check_component(component) try: data = _load_update_status() # str() guards against status files written by older clients that # persisted a non-string error (e.g. an int HTTP status like 414). error = data.get(component, {}).get('error', '') # type: Any return str(error)[: constants.UPDATE_ERROR_MAX_LENGTH] except Exception: log_utils.kcarelog.warning('Failed to read update status', exc_info=True) return ''
cải xoăn