Explorar el Código

Restore explicit PostgreSQL 15 check in upgrade.sh

Jeremy Stretch hace 1 día
padre
commit
560e5e18f4
Se han modificado 3 ficheros con 107 adiciones y 1 borrados
  1. 5 1
      netbox/core/apps.py
  2. 43 0
      netbox/core/checks.py
  3. 59 0
      netbox/core/tests/test_checks.py

+ 5 - 1
netbox/core/apps.py

@@ -29,7 +29,11 @@ class CoreConfig(AppConfig):
 
     def ready(self):
         from core.api import schema  # noqa: F401
-        from core.checks import check_duplicate_indexes, check_redis_version  # noqa: F401
+        from core.checks import (  # noqa: F401
+            check_duplicate_indexes,
+            check_postgresql_version,
+            check_redis_version,
+        )
         from netbox import context_managers  # noqa: F401
         from netbox.models.features import register_models
         from netbox.search import signals as search_signals  # noqa: F401

+ 43 - 0
netbox/core/checks.py

@@ -1,10 +1,12 @@
 from django.apps import apps
 from django.core.cache import cache
 from django.core.checks import Error, Tags, register
+from django.db import NotSupportedError, connection
 from django.db.models import Index, UniqueConstraint
 
 __all__ = (
     'check_duplicate_indexes',
+    'check_postgresql_version',
     'check_redis_version',
 )
 
@@ -43,6 +45,47 @@ def check_duplicate_indexes(app_configs, **kwargs):
     return errors
 
 
+@register(Tags.database)
+def check_postgresql_version(app_configs, **kwargs):
+    """
+    Report an error if the PostgreSQL version is less than 15.
+    """
+    errors = []
+    try:
+        with connection.cursor() as cursor:
+            cursor.execute('SHOW server_version_num')
+            row = cursor.fetchone()
+            pg_version = int(row[0])
+    except NotSupportedError:
+        # Django's own backend check rejects the connection outright when the server predates the
+        # minimum version Django supports (BaseDatabaseWrapper.init_connection_state()), so the query
+        # above never runs. Report the requirement here rather than letting the raw exception surface
+        # as a traceback: management commands run system checks with databases=None, which skips
+        # Django's equivalent check, so this is the only opportunity to report it cleanly.
+        errors.append(
+            Error(
+                'The configured PostgreSQL version is not supported. NetBox requires PostgreSQL 15 or later.',
+                hint='Please upgrade to PostgreSQL 15 or later.',
+                id='netbox.E001',
+            )
+        )
+    except Exception:
+        # The database may be unreachable (e.g. when running checks before it has been provisioned).
+        # Leave the version unverified rather than reporting a spurious error.
+        pass
+    else:
+        if pg_version < 150000:
+            major_version = pg_version // 10000
+            errors.append(
+                Error(
+                    f'PostgreSQL {major_version} is not supported. NetBox requires PostgreSQL 15 or later.',
+                    hint='Please upgrade to PostgreSQL 15 or later.',
+                    id='netbox.E001',
+                )
+            )
+    return errors
+
+
 @register(Tags.caches)
 def check_redis_version(app_configs, **kwargs):
     """

+ 59 - 0
netbox/core/tests/test_checks.py

@@ -0,0 +1,59 @@
+from unittest.mock import MagicMock, patch
+
+from django.db import NotSupportedError
+from django.test import TestCase
+
+from core.checks import check_postgresql_version
+
+
+class PostgreSQLVersionCheckTestCase(TestCase):
+    """
+    Test the system check which enforces NetBox's minimum PostgreSQL version.
+    """
+    @staticmethod
+    def mock_cursor(server_version_num):
+        """
+        Return a patcher for connection.cursor() yielding the given `SHOW server_version_num` result.
+        """
+        cursor = MagicMock()
+        cursor.fetchone.return_value = (str(server_version_num),)
+        context = MagicMock()
+        context.__enter__.return_value = cursor
+        return patch('core.checks.connection.cursor', return_value=context)
+
+    def test_supported_version(self):
+        """
+        No error is reported for PostgreSQL 15 or later.
+        """
+        for version in (150000, 160002, 170000):
+            with self.subTest(version=version), self.mock_cursor(version):
+                self.assertEqual(check_postgresql_version(None), [])
+
+    def test_unsupported_version(self):
+        """
+        An error is reported for PostgreSQL 14 and earlier.
+        """
+        with self.mock_cursor(140010):
+            errors = check_postgresql_version(None)
+        self.assertEqual(len(errors), 1)
+        self.assertEqual(errors[0].id, 'netbox.E001')
+        self.assertIn('PostgreSQL 14 is not supported', errors[0].msg)
+
+    def test_connection_rejected_by_django(self):
+        """
+        Django's backend refuses to connect at all when the server predates its own minimum supported
+        version, so the version query never runs. The check must still report the requirement.
+        """
+        error = NotSupportedError('PostgreSQL 15 or later is required (found 14.10).')
+        with patch('core.checks.connection.cursor', side_effect=error):
+            errors = check_postgresql_version(None)
+        self.assertEqual(len(errors), 1)
+        self.assertEqual(errors[0].id, 'netbox.E001')
+        self.assertIn('NetBox requires PostgreSQL 15 or later', errors[0].msg)
+
+    def test_database_unavailable(self):
+        """
+        An unreachable database leaves the version unverified rather than reporting a spurious error.
+        """
+        with patch('core.checks.connection.cursor', side_effect=Exception('could not connect to server')):
+            self.assertEqual(check_postgresql_version(None), [])