Skip to content

Commit 9b317ab

Browse files
committed
Allow downgrading to 2.11 from 3.x
There were two things blocking this: 1) The revision heads map didn't have any 2.11.x versions in it, so the previous implementation of `_get_version_revision` was only looking within the same <major.minor> pathc version. We change it to rely on the fact that our pre-commit checks ensure this map is ordered, and iterate over the dictionary reversed, and when we find the first thing less than the target revision we use that (direct equal is handled already above) 2) The `ab_*` tables not existing were blocking the migration. Part of this is now fixable manually with apache#54227, but I have decided that since FAB was required and the only option in 2.x, so I have decided to just create the tables if they are missing In order to try and cope with possible future changes I create the tables at the latest version and then downgrade to the oldest known revision. This is all handled in a `reset_to_2_x()` method on the FABDBManager, with a fallback to just blindly create the tables from the ORM for versions of the provider that don't yet have that function.
1 parent 5981ae2 commit 9b317ab

4 files changed

Lines changed: 54 additions & 27 deletions

File tree

airflow-core/src/airflow/cli/commands/db_command.py

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,29 +50,35 @@ def resetdb(args):
5050
db.resetdb(skip_init=args.skip_init)
5151

5252

53-
def _get_version_revision(
54-
version: str, recursion_limit: int = 10, revision_heads_map: dict[str, str] | None = None
55-
) -> str | None:
53+
def _get_version_revision(version: str, revision_heads_map: dict[str, str] | None = None) -> str | None:
5654
"""
57-
Recursively search for the revision of the given version in revision_heads_map.
55+
Search for the revision of the given version in revision_heads_map.
5856
5957
This searches given revision_heads_map for the revision of the given version, recursively
6058
searching for the previous version if the given version is not found.
59+
60+
``revision_heads_map`` must already be sorted in the dict in ascending order for this function to work. No
61+
checks are made that this is true
6162
"""
6263
if revision_heads_map is None:
6364
revision_heads_map = _REVISION_HEADS_MAP
65+
# Exact match found, we can just return it
6466
if version in revision_heads_map:
6567
return revision_heads_map[version]
66-
try:
67-
major, minor, patch = map(int, version.split("."))
68-
except ValueError:
69-
return None
70-
new_version = f"{major}.{minor}.{patch - 1}"
71-
recursion_limit -= 1
72-
if recursion_limit <= 0:
73-
# Prevent infinite recursion as I can't imagine 10 successive versions without migration
68+
69+
wanted = tuple(map(int, version.split(".")))
70+
# Else, we walk backwards in the revision map until we find a version that is < the target
71+
for revision, head in reversed(revision_heads_map.items()):
72+
try:
73+
current = tuple(map(int, revision.split(".")))
74+
except ValueError:
75+
log.debug("Unable to parse HEAD revision", exc_info=True)
76+
return None
77+
78+
if current < wanted:
79+
return head
80+
else:
7481
return None
75-
return _get_version_revision(new_version, recursion_limit)
7682

7783

7884
def run_db_migrate_command(args, command, revision_heads_map: dict[str, str]):

airflow-core/src/airflow/utils/db.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,21 +1189,21 @@ def downgrade(*, to_revision, from_revision=None, show_sql_only=False, session:
11891189
config = _get_alembic_config()
11901190
# Check if downgrade is less than 3.0.0 and requires that `ab_user` fab table is present
11911191
if _revision_greater(config, _REVISION_HEADS_MAP["2.10.3"], to_revision):
1192-
unitest_mode = conf.getboolean("core", "unit_test_mode")
1193-
if unitest_mode:
1194-
try:
1195-
from airflow.providers.fab.auth_manager.models.db import FABDBManager
1196-
1197-
dbm = FABDBManager(session)
1198-
dbm.initdb()
1199-
except ImportError:
1200-
log.warning("Import error occurred while importing FABDBManager. Skipping the check.")
1201-
return
1202-
if not inspect(settings.engine).has_table("ab_user") and not unitest_mode:
1203-
raise AirflowException(
1204-
"Downgrade to revision less than 3.0.0 requires that `ab_user` table is present. "
1205-
"Please add FabDBManager to [core] external_db_managers and run fab migrations before proceeding"
1192+
try:
1193+
from airflow.providers.fab.auth_manager.models.db import FABDBManager
1194+
except ImportError:
1195+
# Raise the error with a new message
1196+
raise RuntimeError(
1197+
"Import error occurred while importing FABDBManager. We need that to exist before we can "
1198+
"downgrade to <3.0.0"
12061199
)
1200+
dbm = FABDBManager(session)
1201+
if hasattr(dbm, "reset_to_2_x"):
1202+
dbm.reset_to_2_x()
1203+
else:
1204+
# Older version before we added that function, it only has a single migration so we can just
1205+
# created
1206+
dbm.create_db_from_orm()
12071207
with create_global_lock(session=session, lock=DBLocks.MIGRATIONS):
12081208
if show_sql_only:
12091209
log.warning("Generating sql scripts for manual migration.")

airflow-core/tests/unit/cli/commands/test_db_command.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,3 +658,19 @@ def test_confirm_in_drop_archived_records_command(self, mock_drop_archived_recor
658658
)
659659
db_command.drop_archived(args)
660660
mock_drop_archived_records.assert_called_once_with(table_names=None, needs_confirm=expected)
661+
662+
663+
def test_get_version_revision():
664+
heads: dict[str, str] = {
665+
"2.10.0": "22ed7efa9da2",
666+
"2.10.3": "5f2621c13b39",
667+
"3.0.0": "29ce7909c52b",
668+
"3.0.3": "fe199e1abd77",
669+
"3.1.0": "808787349f22",
670+
}
671+
672+
assert db_command._get_version_revision("3.1.0", heads) == "808787349f22"
673+
assert db_command._get_version_revision("3.1.1", heads) == "808787349f22"
674+
assert db_command._get_version_revision("2.11.1", heads) == "5f2621c13b39"
675+
assert db_command._get_version_revision("2.10.1", heads) == "22ed7efa9da2"
676+
assert db_command._get_version_revision("2.0.0", heads) is None

providers/fab/src/airflow/providers/fab/auth_manager/models/db.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ def create_db_from_orm(self):
5959
super().create_db_from_orm()
6060
_get_flask_db(settings.SQL_ALCHEMY_CONN).create_all()
6161

62+
def reset_to_2_x(self):
63+
self.create_db_from_orm()
64+
# And ensure it's at the oldest version
65+
self.downgrade(_REVISION_HEADS_MAP["1.4.0"])
66+
6267
def upgradedb(self, to_revision=None, from_revision=None, show_sql_only=False):
6368
"""Upgrade the database."""
6469
if from_revision and not show_sql_only:

0 commit comments

Comments
 (0)