|
| 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