mirror of
https://github.com/inventree/InvenTree.git
synced 2025-04-28 19:46:46 +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."""
|
"""Custom backend implementations."""
|
||||||
|
|
||||||
|
import datetime
|
||||||
import logging
|
import logging
|
||||||
import time
|
import time
|
||||||
|
|
||||||
@ -8,7 +9,6 @@ from django.db.utils import IntegrityError, OperationalError, ProgrammingError
|
|||||||
from maintenance_mode.backends import AbstractStateBackend
|
from maintenance_mode.backends import AbstractStateBackend
|
||||||
|
|
||||||
import common.models
|
import common.models
|
||||||
import InvenTree.ready
|
|
||||||
|
|
||||||
logger = logging.getLogger('inventree')
|
logger = logging.getLogger('inventree')
|
||||||
|
|
||||||
@ -16,8 +16,7 @@ logger = logging.getLogger('inventree')
|
|||||||
class InvenTreeMaintenanceModeBackend(AbstractStateBackend):
|
class InvenTreeMaintenanceModeBackend(AbstractStateBackend):
|
||||||
"""Custom backend for managing state of maintenance mode.
|
"""Custom backend for managing state of maintenance mode.
|
||||||
|
|
||||||
Stores the current state of the maintenance mode in the database,
|
Stores a timestamp in the database to determine when maintenance mode will elapse.
|
||||||
using an InvenTreeSetting object.
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
SETTING_KEY = '_MAINTENANCE_MODE'
|
SETTING_KEY = '_MAINTENANCE_MODE'
|
||||||
@ -30,26 +29,45 @@ class InvenTreeMaintenanceModeBackend(AbstractStateBackend):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
setting = common.models.InvenTreeSetting.objects.get(key=self.SETTING_KEY)
|
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:
|
except common.models.InvenTreeSetting.DoesNotExist:
|
||||||
# Database is accessible, but setting is not available - assume False
|
# Database is accessible, but setting is not available - assume False
|
||||||
value = False
|
return False
|
||||||
except (IntegrityError, OperationalError, ProgrammingError):
|
except (IntegrityError, OperationalError, ProgrammingError):
|
||||||
# Database is inaccessible - assume we are not in maintenance mode
|
# Database is inaccessible - assume we are not in maintenance mode
|
||||||
logger.warning('Failed to read maintenance mode state - assuming True')
|
logger.debug('Failed to read maintenance mode state - assuming True')
|
||||||
value = 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):
|
Instead of simply writing "true" or "false" to the setting,
|
||||||
"""Set the state of the maintenance mode."""
|
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)
|
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:
|
while retries > 0:
|
||||||
try:
|
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
|
# Read the value back to confirm
|
||||||
if self.get_value() == value:
|
if self.get_value() == value:
|
||||||
|
@ -19,6 +19,7 @@ import pint.errors
|
|||||||
from djmoney.contrib.exchange.exceptions import MissingRate
|
from djmoney.contrib.exchange.exceptions import MissingRate
|
||||||
from djmoney.contrib.exchange.models import Rate, convert_money
|
from djmoney.contrib.exchange.models import Rate, convert_money
|
||||||
from djmoney.money import Money
|
from djmoney.money import Money
|
||||||
|
from maintenance_mode.core import get_maintenance_mode, set_maintenance_mode
|
||||||
from sesame.utils import get_user
|
from sesame.utils import get_user
|
||||||
|
|
||||||
import InvenTree.conversion
|
import InvenTree.conversion
|
||||||
@ -1264,3 +1265,55 @@ class MagicLoginTest(InvenTreeTestCase):
|
|||||||
self.assertEqual(resp.url, '/api/auth/login-redirect/')
|
self.assertEqual(resp.url, '/api/auth/login-redirect/')
|
||||||
# And we should be logged in again
|
# And we should be logged in again
|
||||||
self.assertEqual(resp.wsgi_request.user, self.user)
|
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…
x
Reference in New Issue
Block a user