Skip to content

Commit 2458eab

Browse files
hjanottreicdaluisa-beerboom4echowpeb-adr
authored
[rel-db] Initial migration (#3152)
* Add initial migration * Add new migration system and migration states in version table * deprecate 'progress' and remove 'clear-collectionfield-tables' routes * set decimals to minimum if below minimum * use migration tables for initial migration * centralize MODELS in InternalHelper * hard coded ORIGIN_COLLECTIONS * unskip initial import tests * delete migrations core * use advisory locks instead in schema creation * source environment variables in interactive shell --------- Co-authored-by: Danny <dreichelt@intevation.de> Co-authored-by: reicda <165993199+reicda@users.noreply.github.com> Co-authored-by: Luisa <luisa.beerboom@intevation.de> Co-authored-by: Alexej <33332102+4echow@users.noreply.github.com> Co-authored-by: luisa-beerboom <101706784+luisa-beerboom@users.noreply.github.com> Co-authored-by: peb-adr <adrichter97@gmail.com>
1 parent 0eee5d9 commit 2458eab

51 files changed

Lines changed: 7011 additions & 2364 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/docker-compose/docker-compose.prod.yml

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
1-
version: "3"
21
services:
32
backend-action:
4-
build: ../..
3+
build:
4+
context: ../..
5+
target: "prod"
6+
args:
7+
CONTEXT: "prod"
58
image: openslides-backend
69
ports:
710
- "9002:9002"
@@ -14,7 +17,11 @@ services:
1417
secrets:
1518
- postgres_password
1619
backend-presenter:
17-
build: ../..
20+
build:
21+
context: ../..
22+
target: "prod"
23+
args:
24+
CONTEXT: "prod"
1825
image: openslides-backend
1926
ports:
2027
- "9003:9003"

.github/workflows/continuous_integration.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ jobs:
2323

2424
steps:
2525
- uses: actions/checkout@v6
26+
with:
27+
submodules: recursive
2628

2729
- name: Create secrets for datastore
2830
run: mkdir secrets && echo -n "openslides" > secrets/postgres_password
@@ -64,6 +66,7 @@ jobs:
6466
- uses: actions/checkout@v6
6567
with:
6668
path: openslides-backend
69+
submodules: recursive
6770

6871
- name: Set env vars
6972
working-directory: openslides-backend/requirements

Makefile

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ check-all: validate-models-yml check-models check-initial-data-json check-exampl
4545

4646
# Models
4747

48+
generate-schema:
49+
make -C meta/dev generate-relational-schema
50+
51+
generate-db: | generate-schema create-database-with-schema
52+
4853
generate-models:
4954
python cli/generate_models.py $(MODELS_PATH)
5055
black openslides_backend/models/models.py

cli/create_schema.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,37 @@
1-
from openslides_backend.services.postgresql.create_schema import create_schema
1+
from psycopg import Connection, rows, sql
2+
3+
from openslides_backend.services.postgresql.create_schema import (
4+
create_db,
5+
create_schema,
6+
)
7+
from openslides_backend.services.postgresql.db_connection_handling import (
8+
env,
9+
get_unpooled_db_connection,
10+
)
11+
from openslides_backend.shared.exceptions import DatabaseException
212

313

414
def main() -> None:
5-
create_schema()
15+
connection: Connection[rows.DictRow]
16+
try:
17+
connection = get_unpooled_db_connection(env.DATABASE_NAME, False)
18+
except DatabaseException:
19+
create_db()
20+
connection = get_unpooled_db_connection(env.DATABASE_NAME, False)
21+
with connection:
22+
with connection.cursor() as cursor:
23+
lock_int = 13371234564223
24+
cursor.execute(
25+
sql.SQL("SELECT pg_advisory_lock({lock_int});").format(
26+
lock_int=lock_int
27+
)
28+
)
29+
create_schema()
30+
cursor.execute(
31+
sql.SQL("SELECT pg_advisory_unlock({lock_int});").format(
32+
lock_int=lock_int
33+
)
34+
)
635

736

837
if __name__ == "__main__":

cli/generate_models.py

Lines changed: 12 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,7 @@
44
from textwrap import dedent
55
from typing import Any, Optional
66

7-
from cli.util.util import (
8-
ROOT,
9-
assert_equal,
10-
open_output,
11-
open_yml_file,
12-
parse_arguments,
13-
)
7+
from cli.util.util import ROOT, assert_equal, open_output, parse_arguments
148
from meta.dev.src.helper_get_names import (
159
FieldSqlErrorType,
1610
HelperGetNames,
@@ -76,8 +70,6 @@
7670
"""
7771
)
7872

79-
MODELS: dict[str, dict[str, Any]] = {}
80-
8173

8274
def main() -> None:
8375
"""
@@ -101,10 +93,8 @@ def main() -> None:
10193
to: some_model/some_attribute_id
10294
"""
10395
args = parse_arguments(SOURCE)
104-
global MODELS
105-
MODELS = open_yml_file(args.filename)
10696

107-
InternalHelper.MODELS = MODELS
97+
InternalHelper.read_models_yml(SOURCE)
10898

10999
# Load and parse models.yml
110100
with open_output(DESTINATION, args.check) as dest:
@@ -114,7 +104,7 @@ def main() -> None:
114104
+ ", ".join(mixin.__name__ for mixin in MODEL_MIXINS.values())
115105
+ "\n"
116106
)
117-
for collection, fields in MODELS.items():
107+
for collection, fields in InternalHelper.MODELS.items():
118108
if collection.startswith("_"):
119109
continue
120110
model = Model(collection, fields)
@@ -132,7 +122,7 @@ def get_model_field(collection: str, field_name: str) -> str | dict:
132122
Helper function the get a specific model field. Used to create generic relations.
133123
"""
134124

135-
model = MODELS.get(collection)
125+
model = InternalHelper.MODELS.get(collection)
136126
if model is None:
137127
raise ValueError(f"Collection {collection} does not exist.")
138128
value = model.get(field_name)
@@ -376,16 +366,21 @@ def get_view_field_state_write_fields(
376366
else:
377367
write_fields = (table_name, field1, field2, [])
378368
elif "generic-relation-list" in (field_type, foreign_type):
379-
write_fields = self.get_write_fields_for_generic(own, foreign_fields)
369+
write_fields = self.get_write_fields_for_generic(
370+
own, foreign_fields, primary
371+
)
380372

381373
assert error == "", error
382374

383375
return is_view_field, primary, write_fields
384376

385377
def get_write_fields_for_generic(
386-
self, own: TableFieldType, foreign_fields: list[TableFieldType]
378+
self, own: TableFieldType, foreign_fields: list[TableFieldType], primary: bool
387379
) -> tuple[str, str, str, list[str]] | None:
388-
table_name = HelperGetNames.get_gm_table_name(own)
380+
if primary:
381+
table_name = HelperGetNames.get_gm_table_name(own)
382+
else:
383+
table_name = HelperGetNames.get_gm_table_name(foreign_fields[0])
389384
field1 = f"{own.table}_{own.ref_column}"
390385
field2 = own.intermediate_column
391386
return (

data/example-data.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"_migration_index": 74,
2+
"_migration_index": 100,
33
"gender":{
44
"1":{
55
"id": 1,

data/initial-data.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"_migration_index": 74,
2+
"_migration_index": 100,
33
"gender":{
44
"1":{
55
"id": 1,

dev/.bashrc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
if [ -f /etc/bash_completion ]; then source /etc/bash_completion; fi
22
alias ls='ls --color=auto'
33
alias l='ls -CF'
4+
5+
source scripts/export_database_variables.sh

dev/entrypoint.sh

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,19 @@
33
printf "enter entrypoint.sh"
44
set -e
55

6-
printf "\nOpenslides Database:\n"
6+
printf "\nOpenslides DBMS:\n"
77
printf "Export env variables for database.\n"
88
source scripts/export_database_variables.sh
99

1010
meta/dev/scripts/wait-for-database.sh
11-
printf "Database is started.\n"
11+
printf "DBMS is started.\n"
1212

13-
printf "Create schema.\n"
13+
printf "Calling cli/create_schema.py ...\n"
1414
python cli/create_schema.py
1515
printf "\n"
1616

17-
# TODO: Re-add this code
18-
# printf "\nMigrations:\n"
19-
# python openslides_backend/migrations/migrate.py finalize
20-
# printf "\n"
17+
printf "\nMigrations:\n"
18+
python openslides_backend/migrations/migrate.py finalize
19+
printf "\n"
2120

2221
exec "$@"

docs/migration_route.md

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,23 @@
33
Migrations are available via the internal route `/internal/migrations/` with the following payload:
44
```js
55
{
6-
"cmd": "migrate" | "finalize" | "reset" | "clear-collectionfield-tables" | "stats" | "progress",
6+
"cmd": "migrate" | "finalize" | "reset" | "stats",
77
"verbose": bool
88
}
99
```
1010

11-
All commands except `progress` are directly translated the the respective method calls to the datastore module as well as the `verbose` flag. While a migration is running (either via `migrate` or `finalize`), no other commands are permitted except `progress`. The `progress` command is an additional command only available in the backend which reports the progress of a long-running migration (which is executed in a thread).
11+
While a migration is running (either via `migrate` or `finalize`), no other commands are permitted except `stats`. The `stats` command reports the progress of a long-running migration (which is executed in a thread).
1212

1313
The output of all commands except `stats` is the following (for a successful request):
1414
```js
1515
enum MigrationState {
16-
MIGRATION_RUNNING = "migration_running"
1716
MIGRATION_REQUIRED = "migration_required"
17+
MIGRATION_RUNNING = "migration_running"
18+
MIGRATION_FAILED = "migration_failed"
1819
FINALIZATION_REQUIRED = "finalization_required"
19-
NO_MIGRATION_REQUIRED = "no_migration_required"
20+
FINALIZATION_RUNNING = "finalization_running"
21+
FINALIZED = "finalized"
22+
FINALIZATION_FAILED = "finalization_failed"
2023
}
2124

2225
{
@@ -30,20 +33,20 @@ enum MigrationState {
3033
```
3134
`output` always contains the full output of the migration command up to this point. `exception` contains the thrown exception, if any, which can only be the case if the command is finished (meaning `status != "migration_running"`). After issuing a migration command, it is waited a short period of time for the thread to finish, so the status can be all of these things for any command (e.g. after calling `migrate`, the returned status can be either `MIGRATION_RUNNING` if the migrations did not finish directly or `FINALIZATION_REQUIRED` if the migration is already done).
3235

33-
The output of migration commands is stored until a new migration command is issued, meaning repeated `progress` requests after a finished command will always return the same result with the full output.
34-
3536
The `stats` return value is the following:
3637
```js
3738
{
3839
"success": True,
3940
"stats": {
4041
"status": MigrationState,
42+
"output": str, // Optional
43+
"exception": str, // Optional
4144
"current_migration_index": int,
4245
"target_migration_index": int,
43-
"positions": int,
44-
"events": int,
45-
"partially_migrated_positions": int,
46-
"fully_migrated_positions": int
46+
"migratable_models": {
47+
"count": int,
48+
"migrated": int
49+
}
4750
}
4851
}
4952
```

0 commit comments

Comments
 (0)