mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Maintenance Mode Update (#6462)
* Adjust maintenance mode backend - Save a timestamp to the setting, after which maintenance mode is not active - Fallback to account for possibility that race condition / exception leaves maintenance mode active * Update docstring * Remove unused import * Add unit tests for maintenance mode
This commit is contained in:
parent
9c93130224
commit
903c65d08a
@ -1,5 +1,6 @@
|
||||
"""Custom backend implementations."""
|
||||
|
||||
import datetime
|
||||
import logging
|
||||
import time
|
||||
|
||||
@ -8,7 +9,6 @@ from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
||||
from maintenance_mode.backends import AbstractStateBackend
|
||||
|
||||
import common.models
|
||||
import InvenTree.ready
|
||||
|
||||
logger = logging.getLogger('inventree')
|
||||
|
||||
@ -16,8 +16,7 @@ logger = logging.getLogger('inventree')
|
||||
class InvenTreeMaintenanceModeBackend(AbstractStateBackend):
|
||||
"""Custom backend for managing state of maintenance mode.
|
||||
|
||||
Stores the current state of the maintenance mode in the database,
|
||||
using an InvenTreeSetting object.
|
||||
Stores a timestamp in the database to determine when maintenance mode will elapse.
|
||||
"""
|
||||
|
||||
SETTING_KEY = '_MAINTENANCE_MODE'
|
||||
@ -30,26 +29,45 @@ class InvenTreeMaintenanceModeBackend(AbstractStateBackend):
|
||||
"""
|
||||
try:
|
||||
setting = common.models.InvenTreeSetting.objects.get(key=self.SETTING_KEY)
|
||||
value = InvenTree.helpers.str2bool(setting.value)
|
||||
value = str(setting.value).strip()
|
||||
except common.models.InvenTreeSetting.DoesNotExist:
|
||||
# Database is accessible, but setting is not available - assume False
|
||||
value = False
|
||||
return False
|
||||
except (IntegrityError, OperationalError, ProgrammingError):
|
||||
# Database is inaccessible - assume we are not in maintenance mode
|
||||
logger.warning('Failed to read maintenance mode state - assuming True')
|
||||
value = True
|
||||
logger.debug('Failed to read maintenance mode state - assuming True')
|
||||
return True
|
||||
|
||||
logger.debug('Maintenance mode state: %s', value)
|
||||
# Extract timestamp from string
|
||||
try:
|
||||
# If the timestamp is in the past, we are now *out* of maintenance mode
|
||||
timestamp = datetime.datetime.fromisoformat(value)
|
||||
return timestamp > datetime.datetime.now()
|
||||
except ValueError:
|
||||
# If the value is not a valid timestamp, assume maintenance mode is not active
|
||||
return False
|
||||
|
||||
return value
|
||||
def set_value(self, value: bool, retries: int = 5, minutes: int = 5):
|
||||
"""Set the state of the maintenance mode.
|
||||
|
||||
def set_value(self, value: bool, retries: int = 5):
|
||||
"""Set the state of the maintenance mode."""
|
||||
Instead of simply writing "true" or "false" to the setting,
|
||||
we write a timestamp to the setting, which is used to determine
|
||||
when maintenance mode will elapse.
|
||||
This ensures that we will always *exit* maintenance mode after a certain time period.
|
||||
"""
|
||||
logger.debug('Setting maintenance mode state: %s', value)
|
||||
|
||||
if value:
|
||||
# Save as isoformat
|
||||
timestamp = datetime.datetime.now() + datetime.timedelta(minutes=minutes)
|
||||
timestamp = timestamp.isoformat()
|
||||
else:
|
||||
# Blank timestamp means maintenance mode is not active
|
||||
timestamp = ''
|
||||
|
||||
while retries > 0:
|
||||
try:
|
||||
common.models.InvenTreeSetting.set_setting(self.SETTING_KEY, value)
|
||||
common.models.InvenTreeSetting.set_setting(self.SETTING_KEY, timestamp)
|
||||
|
||||
# Read the value back to confirm
|
||||
if self.get_value() == value:
|
||||
|
@ -19,6 +19,7 @@ import pint.errors
|
||||
from djmoney.contrib.exchange.exceptions import MissingRate
|
||||
from djmoney.contrib.exchange.models import Rate, convert_money
|
||||
from djmoney.money import Money
|
||||
from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
|
||||
from sesame.utils import get_user
|
||||
|
||||
import InvenTree.conversion
|
||||
@ -1264,3 +1265,55 @@ class MagicLoginTest(InvenTreeTestCase):
|
||||
self.assertEqual(resp.url, '/api/auth/login-redirect/')
|
||||
# And we should be logged in again
|
||||
self.assertEqual(resp.wsgi_request.user, self.user)
|
||||
|
||||
|
||||
class MaintenanceModeTest(InvenTreeTestCase):
|
||||
"""Unit tests for maintenance mode."""
|
||||
|
||||
def test_basic(self):
|
||||
"""Test basic maintenance mode operation."""
|
||||
for value in [False, True, False]:
|
||||
set_maintenance_mode(value)
|
||||
self.assertEqual(get_maintenance_mode(), value)
|
||||
|
||||
# API request is blocked in maintenance mode
|
||||
set_maintenance_mode(True)
|
||||
|
||||
response = self.client.get('/api/')
|
||||
self.assertEqual(response.status_code, 503)
|
||||
|
||||
set_maintenance_mode(False)
|
||||
|
||||
response = self.client.get('/api/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_timestamp(self):
|
||||
"""Test that the timestamp value is interpreted correctly."""
|
||||
KEY = '_MAINTENANCE_MODE'
|
||||
|
||||
# Deleting the setting means maintenance mode is off
|
||||
InvenTreeSetting.objects.filter(key=KEY).delete()
|
||||
|
||||
self.assertFalse(get_maintenance_mode())
|
||||
|
||||
def set_timestamp(value):
|
||||
InvenTreeSetting.set_setting(KEY, value, None)
|
||||
|
||||
# Test blank value
|
||||
set_timestamp('')
|
||||
self.assertFalse(get_maintenance_mode())
|
||||
|
||||
# Test timestamp in the past
|
||||
ts = datetime.now() - timedelta(minutes=10)
|
||||
set_timestamp(ts.isoformat())
|
||||
self.assertFalse(get_maintenance_mode())
|
||||
|
||||
# Test timestamp in the future
|
||||
ts = datetime.now() + timedelta(minutes=10)
|
||||
set_timestamp(ts.isoformat())
|
||||
self.assertTrue(get_maintenance_mode())
|
||||
|
||||
# Set to false, check for empty string
|
||||
set_maintenance_mode(False)
|
||||
self.assertFalse(get_maintenance_mode())
|
||||
self.assertEqual(InvenTreeSetting.get_setting(KEY, None), '')
|
||||
|
Loading…
Reference in New Issue
Block a user