Skip to content

Commit b9fc5e0

Browse files
committed
Add WIP change for migrating old executions field data which utilizes the old
and and very slow field type to the new field type. This is really an optional script since all the new objects will already utilize new field type and most users only care about new / recent executions (old executions are not retrieved that often so that taking a bit longer is not the end of the world).
1 parent 3b444e3 commit b9fc5e0

File tree

1 file changed

+135
-0
lines changed

1 file changed

+135
-0
lines changed
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python
2+
# Copyright 2021 The StackStorm Authors.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
"""
17+
Migration which which migrates data for existing objects in the database which utilize
18+
EscapedDictField or EscapedDynamicField and have been updated to use new JsonDictField.
19+
20+
Migration step is idempotent and can be retried on failures.
21+
22+
Keep in mind that running this migration script is optional and it may take a long time of you have
23+
a lot of very large objects in the database (aka executions) - reading a lot of data from the
24+
database using the old field types is slow and CPU intensive.
25+
26+
New field type is automatically used for all the new objects when upgrading to v3.5 so migration is
27+
optional because in most cases users are viewing recent / new executions and not old ones which may
28+
still utilize old field typo which is slow to read / write.
29+
30+
Right now the script utilizes no concurrency and performs migration one object by one. That's done
31+
for simplicity reasons and also to avoid massive CPU usage spikes when running this script with
32+
large concurrency on large objects.
33+
34+
Keep in mind that only "completed" objects are processes - this means Executions in "final" states
35+
(succeeded, failed, timeout, etc.).
36+
37+
We determine if execution is utilizing old format if it doesn't contain "result_size" field which
38+
was added along with the new field type.
39+
40+
Actual migrating simply involves reading + re-saving the whole object to the database - everything
41+
is handled by the mongoengine and new field abastraction.
42+
43+
TODO: Also add support for migrating (trigger instances and workflow related objects - low
44+
priority and for those objects we don't have a "result_size" attribute so it's not totally trivial
45+
to determine if object utilizes old field type (we could simply use some date threshold and migrate
46+
everything before that or execute raw pymongo query which searches for specifial string in the
47+
field value.)
48+
"""
49+
50+
import sys
51+
import traceback
52+
53+
from mongoengine.queryset.visitor import Q
54+
55+
from st2common import config
56+
from st2common.service_setup import db_setup
57+
from st2common.service_setup import db_teardown
58+
from st2common.models.db.execution import ActionExecutionDB
59+
from st2common.models.db.liveaction import LiveActionDB
60+
from st2common.persistence.execution import ActionExecution
61+
from st2common.persistence.liveaction import LiveAction
62+
from st2common.constants import action as action_constants
63+
64+
65+
def migrate_executions() -> None:
66+
"""
67+
Perform migrations for execution related objects (ActionExecutionDB, LiveActionDB).
68+
"""
69+
# 1. Migrate ActionExecutionDB objects
70+
execution_dbs = ActionExecution.query(
71+
Q(result_size__not__exists=True)
72+
& Q(status__in=action_constants.LIVEACTION_COMPLETED_STATES)
73+
)
74+
75+
if not execution_dbs:
76+
print("Found no ActionExecutionDB objects to migrate.")
77+
return None
78+
79+
print("Will migrate %s ActionExecutionDB objects" % (len(execution_dbs)))
80+
81+
for execution_db in execution_dbs:
82+
# Migrate corresponding LiveAction object
83+
print("Migrating ActionExecutionDB with id %s" % (execution_db.id))
84+
85+
# This is a bit of a "hack", but it's the easiest way to tell mongoengine that a specific
86+
# field has been updated and should be saved. If we don't do, nothing will be re-saved on
87+
# .save() call due to mongoengine only trying to save what has changed to make it more
88+
# efficient instead of always re-saving the whole object.
89+
execution_db._mark_as_changed("result")
90+
execution_db._mark_as_changed("result_size")
91+
92+
# print(getattr(execution_db, "_changed_fields", []))
93+
execution_db.save()
94+
print("ActionExecutionDB with id %s has been migrated" % (execution_db.id))
95+
96+
try:
97+
liveaction_db = LiveAction.get_by_id(execution_db.liveaction["id"])
98+
except Exception:
99+
# If liveaction for some reason doesn't exist (would likely represent corrupted data) we
100+
# simply ignore that error since it's not fatal.
101+
continue
102+
103+
liveaction_db._mark_as_changed("result")
104+
105+
# print(getattr(liveaction_db, "_changed_fields", []))
106+
liveaction_db.save()
107+
print("Related LiveActionDB with id %s has been migrated" % (liveaction_db.id))
108+
print("")
109+
110+
111+
def migrate_objects() -> None:
112+
print("Migrating affected database objects to utilize new field type")
113+
migrate_executions()
114+
115+
116+
def main():
117+
config.parse_args()
118+
119+
db_setup()
120+
121+
try:
122+
migrate_objects()
123+
print("SUCCESS: All database objects migrated successfully.")
124+
exit_code = 0
125+
except Exception as e:
126+
print("ABORTED: Objects migration aborted on first failure: %s" % (str(e)))
127+
traceback.print_exc()
128+
exit_code = 1
129+
130+
db_teardown()
131+
sys.exit(exit_code)
132+
133+
134+
if __name__ == "__main__":
135+
main()

0 commit comments

Comments
 (0)