ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 1 | from __future__ import annotations |
okozachenko1203 | ef49f95 | 2022-05-16 22:27:28 +1000 | [diff] [blame] | 2 | |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 3 | import collections |
| 4 | from datetime import timedelta |
| 5 | from datetime import timezone |
| 6 | |
okozachenko1203 | ef49f95 | 2022-05-16 22:27:28 +1000 | [diff] [blame] | 7 | from openstack.exceptions import HttpException as OpenstackHttpException |
okozachenko | d280136 | 2021-05-05 21:23:46 +0300 | [diff] [blame] | 8 | from openstack.exceptions import ResourceNotFound as OpenstackResourceNotFound |
| 9 | from openstack.exceptions import SDKException as OpenstackSDKException |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 10 | from oslo_log import log |
ricolin | 72c3ce8 | 2023-03-27 15:08:23 +0800 | [diff] [blame] | 11 | from oslo_utils import timeutils |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 12 | |
| 13 | from staffeln.common import constants |
| 14 | from staffeln.common import context |
| 15 | from staffeln.common import openstack |
ricolin | 31181aa | 2023-07-18 13:51:26 +0800 | [diff] [blame] | 16 | from staffeln.common import time as xtime |
okozachenko1203 | d306c65 | 2022-05-16 22:44:49 +1000 | [diff] [blame] | 17 | from staffeln.conductor import result |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 18 | import staffeln.conf |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 19 | from staffeln.i18n import _ |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 20 | from staffeln import objects |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 21 | |
| 22 | CONF = staffeln.conf.CONF |
| 23 | LOG = log.getLogger(__name__) |
| 24 | |
| 25 | BackupMapping = collections.namedtuple( |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 26 | "BackupMapping", |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 27 | [ |
| 28 | "volume_id", |
| 29 | "backup_id", |
| 30 | "project_id", |
| 31 | "instance_id", |
| 32 | "backup_completed", |
| 33 | "incremental", |
ricolin | 031f06b | 2022-11-13 09:18:57 +0800 | [diff] [blame] | 34 | "created_at", |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 35 | ], |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 36 | ) |
| 37 | |
| 38 | QueueMapping = collections.namedtuple( |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 39 | "QueueMapping", |
Rico Lin | 595c41c | 2022-06-14 21:05:27 +0800 | [diff] [blame] | 40 | [ |
| 41 | "volume_id", |
| 42 | "backup_id", |
| 43 | "project_id", |
| 44 | "instance_id", |
| 45 | "backup_status", |
| 46 | "instance_name", |
| 47 | "volume_name", |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 48 | "incremental", |
ricolin | 988159a | 2022-11-08 07:01:27 +0800 | [diff] [blame] | 49 | "reason", |
Rico Lin | 595c41c | 2022-06-14 21:05:27 +0800 | [diff] [blame] | 50 | ], |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 51 | ) |
| 52 | |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 53 | |
okozachenko1203 | 5fd8a30 | 2022-04-07 01:55:29 +1000 | [diff] [blame] | 54 | def retry_auth(func): |
| 55 | """Decorator to reconnect openstack and avoid token rotation""" |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 56 | |
okozachenko1203 | 5fd8a30 | 2022-04-07 01:55:29 +1000 | [diff] [blame] | 57 | def wrapper(self, *args, **kwargs): |
| 58 | try: |
| 59 | return func(self, *args, **kwargs) |
| 60 | except OpenstackHttpException as ex: |
| 61 | if ex.status_code == 403: |
| 62 | LOG.warn(_("Token has been expired or rotated!")) |
| 63 | self.refresh_openstacksdk() |
| 64 | return func(self, *args, **kwargs) |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 65 | |
okozachenko1203 | 5fd8a30 | 2022-04-07 01:55:29 +1000 | [diff] [blame] | 66 | return wrapper |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 67 | |
| 68 | |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 69 | class Backup(object): |
| 70 | """Implmentations of the queue with the sql.""" |
| 71 | |
| 72 | def __init__(self): |
| 73 | self.ctx = context.make_context() |
okozachenko1203 | 5fd8a30 | 2022-04-07 01:55:29 +1000 | [diff] [blame] | 74 | self.refresh_openstacksdk() |
ricolin | 1fe9913 | 2023-02-27 14:13:56 +0800 | [diff] [blame] | 75 | self.result = result.BackupResult(self) |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 76 | self.project_list = {} |
okozachenko | cb31608 | 2021-05-07 21:30:39 +0300 | [diff] [blame] | 77 | |
okozachenko1203 | 5fd8a30 | 2022-04-07 01:55:29 +1000 | [diff] [blame] | 78 | def refresh_openstacksdk(self): |
| 79 | self.openstacksdk = openstack.OpenstackSDK() |
| 80 | |
ricolin | 86576d1 | 2023-01-28 17:27:54 +0800 | [diff] [blame] | 81 | def publish_backup_result(self, purge_on_success=False): |
| 82 | for project_id, project_name in self.result.project_list: |
| 83 | try: |
| 84 | publish_result = self.result.publish(project_id, project_name) |
| 85 | if publish_result and purge_on_success: |
| 86 | # Purge backup queue tasks |
| 87 | self.purge_backups(project_id) |
| 88 | except Exception as ex: # pylint: disable=W0703 |
| 89 | LOG.warn( |
| 90 | "Failed to publish backup result or " |
| 91 | f"purge backup tasks for project {project_id} " |
| 92 | f"{str(ex)}" |
| 93 | ) |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 94 | |
okozachenko | 9eae01d | 2021-05-11 16:47:27 +0300 | [diff] [blame] | 95 | def refresh_backup_result(self): |
| 96 | self.result.initialize() |
| 97 | |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 98 | def get_backups(self, filters=None, **kwargs): |
okozachenko1203 | 4aa8929 | 2022-05-17 01:08:02 +1000 | [diff] [blame] | 99 | return objects.Volume.list( # pylint: disable=E1120 |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 100 | context=self.ctx, filters=filters, **kwargs |
okozachenko1203 | 4aa8929 | 2022-05-17 01:08:02 +1000 | [diff] [blame] | 101 | ) |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 102 | |
okozachenko | fb38560 | 2021-05-07 19:00:47 +0300 | [diff] [blame] | 103 | def get_backup_quota(self, project_id): |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 104 | return self.openstacksdk.get_backup_quota(project_id) |
okozachenko | fb38560 | 2021-05-07 19:00:47 +0300 | [diff] [blame] | 105 | |
ricolin | f81b0db | 2023-03-30 13:56:58 +0800 | [diff] [blame] | 106 | def get_backup_gigabytes_quota(self, project_id): |
| 107 | return self.openstacksdk.get_backup_gigabytes_quota(project_id) |
| 108 | |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 109 | def get_queues(self, filters=None): |
| 110 | """Get the list of volume queue columns from the queue_data table""" |
okozachenko1203 | 4aa8929 | 2022-05-17 01:08:02 +1000 | [diff] [blame] | 111 | queues = objects.Queue.list( # pylint: disable=E1120 |
okozachenko1203 | 85f53e9 | 2022-05-17 00:59:29 +1000 | [diff] [blame] | 112 | context=self.ctx, filters=filters |
okozachenko1203 | 4aa8929 | 2022-05-17 01:08:02 +1000 | [diff] [blame] | 113 | ) |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 114 | return queues |
| 115 | |
ricolin | 4c82df0 | 2023-09-14 11:34:48 +0800 | [diff] [blame] | 116 | def get_queue_task_by_id(self, task_id): |
| 117 | """Get single volume queue task from the queue_data table""" |
| 118 | queue = objects.Queue.get_by_id( # pylint: disable=E1120 |
| 119 | context=self.ctx, id=task_id |
| 120 | ) |
| 121 | return queue |
| 122 | |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 123 | def create_queue(self, old_tasks): |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 124 | """Create the queue of all the volumes for backup |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 125 | |
| 126 | :param old_tasks: Task list not completed in the previous cycle |
| 127 | :type: List<Class objects.Queue> |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 128 | """ |
ricolin | 07db40e | 2023-01-02 06:37:10 +0800 | [diff] [blame] | 129 | |
| 130 | LOG.info("Adding new backup tasks to queue.") |
okozachenko | d280136 | 2021-05-05 21:23:46 +0300 | [diff] [blame] | 131 | # 1. get the old task list, not finished in the last cycle |
| 132 | # and keep till now |
| 133 | old_task_volume_list = [] |
| 134 | for old_task in old_tasks: |
| 135 | old_task_volume_list.append(old_task.volume_id) |
| 136 | |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 137 | # 2. add new tasks in the queue which are not existing in the old task |
| 138 | # list |
ricolin | 07db40e | 2023-01-02 06:37:10 +0800 | [diff] [blame] | 139 | task_list = self.check_instance_volumes() |
| 140 | for task in task_list: |
| 141 | if task.volume_id not in old_task_volume_list: |
| 142 | self._volume_queue(task) |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 143 | |
| 144 | # Backup the volumes attached to which has a specific metadata |
okozachenko | fb38560 | 2021-05-07 19:00:47 +0300 | [diff] [blame] | 145 | def filter_by_server_metadata(self, metadata): |
xuxant02@gmail.com | a0cd61f | 2021-06-22 16:24:29 +0545 | [diff] [blame] | 146 | if CONF.conductor.backup_metadata_key is not None: |
okozachenko1203 | ef49f95 | 2022-05-16 22:27:28 +1000 | [diff] [blame] | 147 | if CONF.conductor.backup_metadata_key not in metadata: |
xuxant02@gmail.com | a0cd61f | 2021-06-22 16:24:29 +0545 | [diff] [blame] | 148 | return False |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 149 | |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 150 | return ( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 151 | metadata[CONF.conductor.backup_metadata_key].lower( |
| 152 | ) == constants.BACKUP_ENABLED_KEY |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 153 | ) |
xuxant02@gmail.com | a0cd61f | 2021-06-22 16:24:29 +0545 | [diff] [blame] | 154 | else: |
Susanta Gautam | 6d0920f | 2021-06-22 16:01:24 +0545 | [diff] [blame] | 155 | return True |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 156 | |
| 157 | # Backup the volumes in in-use and available status |
okozachenko | cb31608 | 2021-05-07 21:30:39 +0300 | [diff] [blame] | 158 | def filter_by_volume_status(self, volume_id, project_id): |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 159 | try: |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 160 | volume = self.openstacksdk.get_volume(volume_id, project_id) |
okozachenko1203 | ef49f95 | 2022-05-16 22:27:28 +1000 | [diff] [blame] | 161 | if volume is None: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 162 | return False |
| 163 | res = volume["status"] in ("available", "in-use") |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 164 | if not res: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 165 | reason = _( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 166 | "Volume %s is not triger new backup task because " |
| 167 | "it is in %s status" % |
| 168 | (volume_id, volume["status"])) |
okozachenko | cb31608 | 2021-05-07 21:30:39 +0300 | [diff] [blame] | 169 | LOG.info(reason) |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 170 | return reason |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 171 | return res |
| 172 | |
okozachenko | d280136 | 2021-05-05 21:23:46 +0300 | [diff] [blame] | 173 | except OpenstackResourceNotFound: |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 174 | return False |
| 175 | |
ricolin | 86576d1 | 2023-01-28 17:27:54 +0800 | [diff] [blame] | 176 | def purge_backups(self, project_id=None): |
| 177 | LOG.info(f"Start pruge backup tasks for project {project_id}") |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 178 | # We can consider make all these in a single DB command |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 179 | success_tasks = self.get_queues( |
ricolin | 86576d1 | 2023-01-28 17:27:54 +0800 | [diff] [blame] | 180 | filters={ |
| 181 | "backup_status": constants.BACKUP_COMPLETED, |
| 182 | "project_id": project_id, |
| 183 | } |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 184 | ) |
| 185 | failed_tasks = self.get_queues( |
ricolin | 86576d1 | 2023-01-28 17:27:54 +0800 | [diff] [blame] | 186 | filters={ |
| 187 | "backup_status": constants.BACKUP_FAILED, |
| 188 | "project_id": project_id, |
| 189 | } |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 190 | ) |
ricolin | da47147 | 2023-05-17 21:41:39 +0800 | [diff] [blame] | 191 | LOG.debug(f"Start purge completed tasks for project {project_id}") |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 192 | for queue in success_tasks: |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 193 | queue.delete_queue() |
| 194 | |
ricolin | da47147 | 2023-05-17 21:41:39 +0800 | [diff] [blame] | 195 | LOG.debug(f"Start purge failed tasks for project {project_id}") |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 196 | for queue in failed_tasks: |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 197 | queue.delete_queue() |
| 198 | |
ricolin | 3a2fad1 | 2023-01-29 17:31:30 +0800 | [diff] [blame] | 199 | def create_failed_backup_obj(self, task): |
| 200 | # Create backup object for failed backups, to make sure we |
| 201 | # will review the delete later when retention triggered. |
| 202 | # Set the created_at time to retention_time ago, |
| 203 | # so it might get check on next retention cycle. |
| 204 | # For customerized retention instances, the backup might check later |
| 205 | # but the process to check the remove will eventually starts. |
| 206 | # Note: 315360000 = 10 years. The create time of an backup object will |
| 207 | # not affect report. |
ricolin | 72c3ce8 | 2023-03-27 15:08:23 +0800 | [diff] [blame] | 208 | threshold_strtime = timeutils.utcnow() - timedelta(seconds=315360000) |
ricolin | 3a2fad1 | 2023-01-29 17:31:30 +0800 | [diff] [blame] | 209 | self._volume_backup( |
| 210 | BackupMapping( |
| 211 | volume_id=task.volume_id, |
| 212 | project_id=task.project_id, |
| 213 | backup_id=task.backup_id, |
| 214 | instance_id=task.instance_id, |
| 215 | backup_completed=0, |
| 216 | incremental=task.incremental, |
| 217 | created_at=threshold_strtime, |
| 218 | ) |
| 219 | ) |
| 220 | |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 221 | # delete all backups forcily regardless of the status |
okozachenko | 2ab48e8 | 2021-05-07 16:55:11 +0300 | [diff] [blame] | 222 | def hard_cancel_backup_task(self, task): |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 223 | try: |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 224 | project_id = task.project_id |
okozachenko | cb31608 | 2021-05-07 21:30:39 +0300 | [diff] [blame] | 225 | reason = _("Cancel backup %s because of timeout." % task.backup_id) |
| 226 | LOG.info(reason) |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 227 | |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 228 | if project_id not in self.project_list: |
| 229 | self.process_non_existing_backup(task) |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 230 | self.openstacksdk.set_project(self.project_list[project_id]) |
| 231 | backup = self.openstacksdk.get_backup(task.backup_id) |
okozachenko1203 | ef49f95 | 2022-05-16 22:27:28 +1000 | [diff] [blame] | 232 | if backup is None: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 233 | return task.delete_queue() |
ricolin | ba8e87f | 2023-06-03 01:24:41 +0800 | [diff] [blame] | 234 | self.openstacksdk.delete_backup(task.backup_id) |
ricolin | 3a2fad1 | 2023-01-29 17:31:30 +0800 | [diff] [blame] | 235 | self.create_failed_backup_obj(task) |
| 236 | |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 237 | task.reason = reason |
| 238 | task.backup_status = constants.BACKUP_FAILED |
| 239 | task.save() |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 240 | |
| 241 | except OpenstackSDKException as e: |
ricolin | 8aa2628 | 2023-01-13 09:36:30 +0800 | [diff] [blame] | 242 | reason = _( |
| 243 | "Backup %s deletion failed. Need delete manually: %s" |
| 244 | % (task.backup_id, str(e)[:64]) |
| 245 | ) |
| 246 | log_msg = _( |
| 247 | "Backup %s deletion failed. Need delete manually: %s" |
| 248 | % (task.backup_id, str(e)) |
| 249 | ) |
ricolin | 75a06aa | 2023-01-11 18:31:16 +0800 | [diff] [blame] | 250 | LOG.warn(log_msg) |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 251 | task.reason = reason |
| 252 | task.backup_status = constants.BACKUP_FAILED |
| 253 | task.save() |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 254 | |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 255 | # delete only available backups: reserved |
okozachenko | 2ab48e8 | 2021-05-07 16:55:11 +0300 | [diff] [blame] | 256 | def soft_remove_backup_task(self, backup_object): |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 257 | try: |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 258 | backup = self.openstacksdk.get_backup(backup_object.backup_id) |
okozachenko1203 | ef49f95 | 2022-05-16 22:27:28 +1000 | [diff] [blame] | 259 | if backup is None: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 260 | LOG.info( |
ricolin | d016f3e | 2024-11-01 16:16:50 +0800 | [diff] [blame] | 261 | f"Backup {backup_object.backup_id} is removed from " |
| 262 | "Openstack or cinder-backup is not existing in the cloud." |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 263 | ) |
okozachenko | 6302495 | 2021-05-10 20:04:25 +0300 | [diff] [blame] | 264 | return backup_object.delete_backup() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 265 | if backup["status"] in ("available"): |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 266 | self.openstacksdk.delete_backup(backup_object.backup_id) |
ricolin | 082804c | 2023-01-28 16:02:57 +0800 | [diff] [blame] | 267 | # Don't remove backup until it's officially removed from Cinder |
| 268 | # backup_object.delete_backup() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 269 | elif backup["status"] in ("error", "error_restoring"): |
ricolin | d456d08 | 2023-01-17 04:03:41 +0800 | [diff] [blame] | 270 | # Try to remove it from cinder |
| 271 | self.openstacksdk.delete_backup(backup_object.backup_id) |
ricolin | 082804c | 2023-01-28 16:02:57 +0800 | [diff] [blame] | 272 | # Don't remove backup until it's officially removed from Cinder |
| 273 | # backup_object.delete_backup() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 274 | else: # "deleting", "restoring" |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 275 | LOG.info( |
ricolin | d016f3e | 2024-11-01 16:16:50 +0800 | [diff] [blame] | 276 | f"Rotation for the backup {backup_object.backup_id} " |
| 277 | "is skipped in this cycle " |
| 278 | f"because it is in {backup['status']} status" |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 279 | ) |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 280 | |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 281 | except OpenstackSDKException as e: |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 282 | LOG.warn( |
| 283 | f"Backup {backup_object.backup_id} deletion failed. {str(e)}" |
| 284 | ) |
ricolin | d456d08 | 2023-01-17 04:03:41 +0800 | [diff] [blame] | 285 | # We don't delete backup object if any exception occured |
| 286 | # backup_object.delete_backup() |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 287 | return False |
| 288 | |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 289 | # delete all backups forcily regardless of the status |
ricolin | 5583c88 | 2023-01-11 22:06:08 +0800 | [diff] [blame] | 290 | def hard_remove_volume_backup(self, backup_object, skip_inc_err=False): |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 291 | try: |
okozachenko | b875cbe | 2021-05-11 19:29:51 +0300 | [diff] [blame] | 292 | project_id = backup_object.project_id |
| 293 | if project_id not in self.project_list: |
ricolin | d456d08 | 2023-01-17 04:03:41 +0800 | [diff] [blame] | 294 | LOG.warn( |
ricolin | d016f3e | 2024-11-01 16:16:50 +0800 | [diff] [blame] | 295 | f"Project {project_id} for backup " |
| 296 | f"{backup_object.backup_id} is not existing in " |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 297 | "Openstack. Please check your access right to this " |
| 298 | "project. " |
| 299 | "Skip this backup from remove now and will retry later.") |
| 300 | # Don't remove backup object, keep it and retry on next |
| 301 | # periodic task backup_object.delete_backup() |
ricolin | d456d08 | 2023-01-17 04:03:41 +0800 | [diff] [blame] | 302 | return |
okozachenko | b875cbe | 2021-05-11 19:29:51 +0300 | [diff] [blame] | 303 | |
okozachenko | 5eadfef | 2021-05-12 13:55:45 +0300 | [diff] [blame] | 304 | self.openstacksdk.set_project(self.project_list[project_id]) |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 305 | backup = self.openstacksdk.get_backup( |
| 306 | uuid=backup_object.backup_id, project_id=project_id |
| 307 | ) |
okozachenko1203 | ef49f95 | 2022-05-16 22:27:28 +1000 | [diff] [blame] | 308 | if backup is None: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 309 | LOG.info( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 310 | f"Backup {backup_object.backup_id} is removed from " |
| 311 | "Openstack or cinder-backup is not existing in the " |
| 312 | "cloud. Start removing backup object from Staffeln.") |
okozachenko | 6302495 | 2021-05-10 20:04:25 +0300 | [diff] [blame] | 313 | return backup_object.delete_backup() |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 314 | |
ricolin | ba8e87f | 2023-06-03 01:24:41 +0800 | [diff] [blame] | 315 | self.openstacksdk.delete_backup(uuid=backup_object.backup_id) |
ricolin | 082804c | 2023-01-28 16:02:57 +0800 | [diff] [blame] | 316 | # Don't remove backup until it's officially removed from Cinder |
| 317 | # backup_object.delete_backup() |
ricolin | 75a06aa | 2023-01-11 18:31:16 +0800 | [diff] [blame] | 318 | except Exception as e: |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 319 | if ( |
| 320 | skip_inc_err and ( |
| 321 | "Incremental backups exist for this backup" in str(e)) |
| 322 | ): |
ricolin | 082804c | 2023-01-28 16:02:57 +0800 | [diff] [blame] | 323 | LOG.debug(str(e)) |
ricolin | 5583c88 | 2023-01-11 22:06:08 +0800 | [diff] [blame] | 324 | else: |
ricolin | 082804c | 2023-01-28 16:02:57 +0800 | [diff] [blame] | 325 | LOG.info( |
ricolin | d016f3e | 2024-11-01 16:16:50 +0800 | [diff] [blame] | 326 | f"Backup {backup_object.backup_id} deletion failed. " |
| 327 | "Skip this backup from remove now and will retry later." |
okozachenko1203 | aaeb047 | 2022-05-17 02:28:15 +1000 | [diff] [blame] | 328 | ) |
ricolin | 082804c | 2023-01-28 16:02:57 +0800 | [diff] [blame] | 329 | LOG.debug(f"deletion failed {str(e)}") |
okozachenko | a6dcdc0 | 2021-05-10 14:13:26 +0300 | [diff] [blame] | 330 | |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 331 | # Don't remove backup object, keep it and retry on next |
| 332 | # periodic task backup_object.delete_backup() |
okozachenko | ca2b37a | 2021-05-06 20:38:02 +0300 | [diff] [blame] | 333 | |
okozachenko | b875cbe | 2021-05-11 19:29:51 +0300 | [diff] [blame] | 334 | def update_project_list(self): |
| 335 | projects = self.openstacksdk.get_projects() |
| 336 | for project in projects: |
| 337 | self.project_list[project.id] = project |
| 338 | |
ricolin | 0c10d97 | 2023-01-14 07:10:30 +0800 | [diff] [blame] | 339 | def _is_backup_required(self, volume_id): |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 340 | """Decide if the backup required based on the backup history |
ricolin | 0c10d97 | 2023-01-14 07:10:30 +0800 | [diff] [blame] | 341 | |
| 342 | If there is any backup created during certain time, |
| 343 | will not trigger new backup request. |
| 344 | This will judge on CONF.conductor.backup_min_interval |
| 345 | |
| 346 | :param volume_id: Target volume id |
| 347 | :type: uuid string |
| 348 | |
| 349 | :return: if new backup required |
| 350 | :return type: bool |
| 351 | """ |
| 352 | # select * from backup order by Id; |
| 353 | try: |
| 354 | if CONF.conductor.backup_min_interval == 0: |
| 355 | # Ignore backup interval |
| 356 | return True |
| 357 | interval = CONF.conductor.backup_min_interval |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 358 | threshold_strtime = timeutils.utcnow() - timedelta( |
| 359 | seconds=interval |
| 360 | ) |
ricolin | 0c10d97 | 2023-01-14 07:10:30 +0800 | [diff] [blame] | 361 | backups = self.get_backups( |
| 362 | filters={ |
| 363 | "volume_id__eq": volume_id, |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 364 | "created_at__gt": threshold_strtime.astimezone( |
| 365 | timezone.utc |
| 366 | ), |
ricolin | 0c10d97 | 2023-01-14 07:10:30 +0800 | [diff] [blame] | 367 | } |
| 368 | ) |
| 369 | if backups: |
| 370 | return False |
| 371 | except Exception as e: |
| 372 | LOG.debug( |
| 373 | _( |
| 374 | "Failed to get backup history to decide backup is " |
| 375 | "required or not. Reason: %s" % str(e) |
| 376 | ) |
| 377 | ) |
| 378 | return True |
| 379 | |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 380 | def _is_incremental(self, volume_id): |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 381 | """Decide the backup method based on the backup history |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 382 | |
| 383 | It queries to select the last N backups from backup table and |
| 384 | decide backup type as full if there is no full backup. |
| 385 | N equals to CONF.conductor.full_backup_depth. |
| 386 | |
| 387 | :param volume_id: Target volume id |
| 388 | :type: uuid string |
| 389 | |
| 390 | :return: if backup method is incremental or not |
| 391 | :return type: bool |
| 392 | """ |
ricolin | 415281c | 2022-12-29 03:44:34 +0800 | [diff] [blame] | 393 | # select * from backup order by Id DESC LIMIT 2; |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 394 | try: |
ricolin | 7de4b0a | 2023-01-13 09:29:09 +0800 | [diff] [blame] | 395 | if CONF.conductor.full_backup_depth == 0: |
| 396 | return False |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 397 | backups = self.get_backups( |
| 398 | filters={"volume_id__eq": volume_id}, |
| 399 | limit=CONF.conductor.full_backup_depth, |
| 400 | sort_key="id", |
| 401 | sort_dir="desc", |
| 402 | ) |
| 403 | for bk in backups: |
| 404 | if bk.incremental: |
| 405 | continue |
| 406 | else: |
| 407 | return True |
| 408 | except Exception as e: |
| 409 | LOG.debug( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 410 | "Failed to get backup history to decide backup " |
| 411 | f"method. Reason: {e}" |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 412 | ) |
| 413 | return False |
| 414 | |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 415 | def check_instance_volumes(self): |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 416 | """Retrieves volume list to backup |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 417 | |
| 418 | Get the list of all the volumes from the project using openstacksdk. |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 419 | Function first list all the servers in the project and get the volumes |
| 420 | that are attached to the instance. |
ricolin | 0c10d97 | 2023-01-14 07:10:30 +0800 | [diff] [blame] | 421 | |
| 422 | Generate backup candidate list for later create tasks in queue |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 423 | """ |
| 424 | queues_map = [] |
okozachenko1203 | 3b2f354 | 2022-05-16 19:33:46 +1000 | [diff] [blame] | 425 | self.refresh_openstacksdk() |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 426 | projects = self.openstacksdk.get_projects() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 427 | for project in projects: |
okozachenko | cb31608 | 2021-05-07 21:30:39 +0300 | [diff] [blame] | 428 | empty_project = True |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 429 | self.project_list[project.id] = project |
okozachenko1203 | 3b2f354 | 2022-05-16 19:33:46 +1000 | [diff] [blame] | 430 | try: |
| 431 | servers = self.openstacksdk.get_servers(project_id=project.id) |
| 432 | except OpenstackHttpException as ex: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 433 | LOG.warn( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 434 | f"Failed to list servers in project {project.id}. " |
| 435 | f"{str(ex)} (status code: {ex.status_code})." |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 436 | ) |
okozachenko1203 | 3b2f354 | 2022-05-16 19:33:46 +1000 | [diff] [blame] | 437 | continue |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 438 | for server in servers: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 439 | if not self.filter_by_server_metadata(server.metadata): |
| 440 | continue |
okozachenko | cb31608 | 2021-05-07 21:30:39 +0300 | [diff] [blame] | 441 | if empty_project: |
| 442 | empty_project = False |
| 443 | self.result.add_project(project.id, project.name) |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 444 | for volume in server.attached_volumes: |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 445 | filter_result = self.filter_by_volume_status( |
ricolin | 66a0a9d | 2022-11-07 07:49:31 +0800 | [diff] [blame] | 446 | volume["id"], project.id |
| 447 | ) |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 448 | |
| 449 | if not filter_result: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 450 | continue |
ricolin | 0c10d97 | 2023-01-14 07:10:30 +0800 | [diff] [blame] | 451 | backup_required = self._is_backup_required(volume["id"]) |
| 452 | if not backup_required: |
| 453 | continue |
| 454 | |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 455 | if "name" not in volume or not volume["name"]: |
okozachenko1203 | a95bdcf | 2022-07-08 00:29:12 +1000 | [diff] [blame] | 456 | volume_name = volume["id"] |
| 457 | else: |
| 458 | volume_name = volume["name"][:100] |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 459 | if filter_result is True: |
| 460 | backup_status = constants.BACKUP_PLANNED |
| 461 | reason = None |
| 462 | else: |
| 463 | backup_status = constants.BACKUP_FAILED |
| 464 | reason = filter_result |
ricolin | ea4d82a | 2023-01-03 06:25:34 +0800 | [diff] [blame] | 465 | incremental = self._is_incremental(volume["id"]) |
ricolin | ccb497d | 2023-01-02 07:51:57 +0800 | [diff] [blame] | 466 | backup_method = "Incremental" if incremental else "Full" |
ricolin | 6536796 | 2023-01-02 08:55:41 +0800 | [diff] [blame] | 467 | LOG.info( |
ricolin | ccb497d | 2023-01-02 07:51:57 +0800 | [diff] [blame] | 468 | "Prapering %s backup task for volume %s", |
| 469 | backup_method, |
ricolin | ea4d82a | 2023-01-03 06:25:34 +0800 | [diff] [blame] | 470 | volume["id"], |
ricolin | ccb497d | 2023-01-02 07:51:57 +0800 | [diff] [blame] | 471 | ) |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 472 | queues_map.append( |
| 473 | QueueMapping( |
okozachenko | 179cd57 | 2021-05-06 22:23:53 +0300 | [diff] [blame] | 474 | project_id=project.id, |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 475 | volume_id=volume["id"], |
| 476 | backup_id="NULL", |
| 477 | instance_id=server.id, |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 478 | backup_status=backup_status, |
Rico Lin | 595c41c | 2022-06-14 21:05:27 +0800 | [diff] [blame] | 479 | # Only keep the last 100 chars of instance_name and |
| 480 | # volume_name for forming backup_name |
| 481 | instance_name=server.name[:100], |
okozachenko1203 | a95bdcf | 2022-07-08 00:29:12 +1000 | [diff] [blame] | 482 | volume_name=volume_name, |
ricolin | ccb497d | 2023-01-02 07:51:57 +0800 | [diff] [blame] | 483 | incremental=incremental, |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 484 | reason=reason, |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 485 | ) |
| 486 | ) |
| 487 | return queues_map |
| 488 | |
ricolin | 031f06b | 2022-11-13 09:18:57 +0800 | [diff] [blame] | 489 | def collect_instance_retention_map(self): |
| 490 | """Retrieves instance backup retention map""" |
| 491 | |
| 492 | retention_map = {} |
| 493 | # No customized retention. |
| 494 | if not CONF.conductor.retention_metadata_key: |
| 495 | return retention_map |
| 496 | self.refresh_openstacksdk() |
| 497 | |
| 498 | try: |
| 499 | servers = self.openstacksdk.get_servers(all_projects=True) |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 500 | except OpenstackHttpException as ex: |
| 501 | servers = [] |
| 502 | LOG.warn( |
| 503 | f"Failed to list servers for all projects. " |
| 504 | f"{str(ex)} (status code: {ex.status_code})." |
| 505 | ) |
ricolin | 031f06b | 2022-11-13 09:18:57 +0800 | [diff] [blame] | 506 | |
| 507 | for server in servers: |
| 508 | if CONF.conductor.retention_metadata_key in server.metadata: |
ricolin | 31181aa | 2023-07-18 13:51:26 +0800 | [diff] [blame] | 509 | server_retention_time = server.metadata[ |
ricolin | 031f06b | 2022-11-13 09:18:57 +0800 | [diff] [blame] | 510 | CONF.conductor.retention_metadata_key |
| 511 | ].lower() |
ricolin | 31181aa | 2023-07-18 13:51:26 +0800 | [diff] [blame] | 512 | if xtime.regex.fullmatch(server_retention_time): |
| 513 | LOG.debug( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 514 | f"Found retention time ({server_retention_time}) " |
| 515 | f"defined for server {server.id}, " |
| 516 | "Adding it retention reference map.") |
ricolin | 31181aa | 2023-07-18 13:51:26 +0800 | [diff] [blame] | 517 | retention_map[server.id] = server_retention_time |
| 518 | else: |
| 519 | LOG.info( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 520 | f"Server retention time for instance {server.id} is " |
| 521 | "incorrect. Please follow " |
| 522 | "'<YEARS>y<MONTHS>m<WEEKS>w<DAYS>d<HOURS>" |
| 523 | "h<MINUTES>min<SECONDS>s' format.") |
ricolin | 031f06b | 2022-11-13 09:18:57 +0800 | [diff] [blame] | 524 | return retention_map |
| 525 | |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 526 | def _volume_queue(self, task): |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 527 | """Commits one backup task to queue table |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 528 | |
| 529 | :param task: One backup task |
| 530 | :type: QueueMapping |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 531 | """ |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 532 | volume_queue = objects.Queue(self.ctx) |
| 533 | volume_queue.backup_id = task.backup_id |
| 534 | volume_queue.volume_id = task.volume_id |
| 535 | volume_queue.instance_id = task.instance_id |
Susanta Gautam | c406111 | 2021-05-10 02:39:23 +0000 | [diff] [blame] | 536 | volume_queue.project_id = task.project_id |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 537 | volume_queue.backup_status = task.backup_status |
Rico Lin | 595c41c | 2022-06-14 21:05:27 +0800 | [diff] [blame] | 538 | volume_queue.instance_name = task.instance_name |
| 539 | volume_queue.volume_name = task.volume_name |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 540 | # NOTE(Oleks): Backup mode is inherited from backup service. |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 541 | # Need to keep and navigate backup mode history, to decide a different |
| 542 | # mode per volume |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 543 | volume_queue.incremental = task.incremental |
ricolin | 415281c | 2022-12-29 03:44:34 +0800 | [diff] [blame] | 544 | |
| 545 | backup_method = "Incremental" if task.incremental else "Full" |
| 546 | LOG.info( |
| 547 | _( |
| 548 | ("Schedule %s backup task for volume %s.") |
| 549 | % (backup_method, task.volume_id) |
| 550 | ) |
| 551 | ) |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 552 | return volume_queue.create() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 553 | |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 554 | def create_volume_backup(self, task): |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 555 | """Initiate the backup of the volume |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 556 | |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 557 | :param task: Provide the map of the volume that needs |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 558 | backup. |
| 559 | This function will call the backupup api and change the |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 560 | backup_status and backup_id in the task queue table. |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 561 | """ |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 562 | project_id = task.project_id |
ricolin | 72c3ce8 | 2023-03-27 15:08:23 +0800 | [diff] [blame] | 563 | timestamp = int(timeutils.utcnow().timestamp()) |
Rico Lin | 595c41c | 2022-06-14 21:05:27 +0800 | [diff] [blame] | 564 | # Backup name allows max 255 chars of string |
okozachenko1203 | a95bdcf | 2022-07-08 00:29:12 +1000 | [diff] [blame] | 565 | backup_name = ("%(instance_name)s_%(volume_name)s_%(timestamp)s") % { |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 566 | "instance_name": task.instance_name, |
| 567 | "volume_name": task.volume_name, |
Rico Lin | 595c41c | 2022-06-14 21:05:27 +0800 | [diff] [blame] | 568 | "timestamp": timestamp, |
| 569 | } |
| 570 | |
| 571 | # Make sure we don't exceed max size of backup_name |
| 572 | backup_name = backup_name[:255] |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 573 | if task.backup_id == "NULL": |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 574 | try: |
okozachenko | 179cd57 | 2021-05-06 22:23:53 +0300 | [diff] [blame] | 575 | # NOTE(Alex): no need to wait because we have a cycle time out |
okozachenko1203 | d22dd2d | 2021-06-08 16:14:02 +0200 | [diff] [blame] | 576 | if project_id not in self.project_list: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 577 | LOG.warn( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 578 | _( |
| 579 | "Project ID %s is not existing in project list" |
| 580 | % project_id |
| 581 | ) |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 582 | ) |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 583 | self.process_non_existing_backup(task) |
okozachenko1203 | d22dd2d | 2021-06-08 16:14:02 +0200 | [diff] [blame] | 584 | return |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 585 | self.openstacksdk.set_project(self.project_list[project_id]) |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 586 | backup_method = "Incremental" if task.incremental else "Full" |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 587 | LOG.info( |
| 588 | _( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 589 | ( |
| 590 | "%s Backup (name: %s) for volume %s creating " |
| 591 | "in project %s" |
| 592 | ) |
| 593 | % ( |
| 594 | backup_method, |
| 595 | backup_name, |
| 596 | task.volume_id, |
| 597 | project_id, |
| 598 | ) |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 599 | ) |
| 600 | ) |
| 601 | volume_backup = self.openstacksdk.create_backup( |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 602 | volume_id=task.volume_id, |
Rico Lin | 595c41c | 2022-06-14 21:05:27 +0800 | [diff] [blame] | 603 | project_id=project_id, |
| 604 | name=backup_name, |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 605 | incremental=task.incremental, |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 606 | ) |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 607 | task.backup_id = volume_backup.id |
ricolin | b9e10b6 | 2023-01-03 05:32:35 +0800 | [diff] [blame] | 608 | task.backup_status = constants.BACKUP_WIP |
| 609 | task.save() |
okozachenko | d280136 | 2021-05-05 21:23:46 +0300 | [diff] [blame] | 610 | except OpenstackSDKException as error: |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 611 | inc_err_msg = ( |
| 612 | "No backups available to do an incremental backup" |
| 613 | ) |
ricolin | 75a06aa | 2023-01-11 18:31:16 +0800 | [diff] [blame] | 614 | if inc_err_msg in str(error): |
| 615 | LOG.info( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 616 | "Retry to create full backup for volume %s instead of " |
| 617 | "incremental." % |
| 618 | task.volume_id) |
ricolin | 75a06aa | 2023-01-11 18:31:16 +0800 | [diff] [blame] | 619 | task.incremental = False |
| 620 | task.save() |
| 621 | else: |
| 622 | reason = _( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 623 | "Backup (name: %s) creation for the volume %s " |
| 624 | "failled. %s" % |
| 625 | (backup_name, task.volume_id, str(error)[ |
| 626 | :64])) |
ricolin | 75a06aa | 2023-01-11 18:31:16 +0800 | [diff] [blame] | 627 | LOG.warn( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 628 | "Backup (name: %s) creation for the volume %s " |
| 629 | "failled. %s" % |
| 630 | (backup_name, task.volume_id, str(error))) |
ricolin | 75a06aa | 2023-01-11 18:31:16 +0800 | [diff] [blame] | 631 | task.reason = reason |
| 632 | task.backup_status = constants.BACKUP_FAILED |
| 633 | task.save() |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 634 | # Added extra exception as OpenstackSDKException does not handle |
| 635 | # the keystone unauthourized issue. |
xuxant02@gmail.com | 79b4397 | 2021-06-21 21:36:57 +0545 | [diff] [blame] | 636 | except Exception as error: |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 637 | reason = _( |
Rico Lin | 595c41c | 2022-06-14 21:05:27 +0800 | [diff] [blame] | 638 | "Backup (name: %s) creation for the volume %s failled. %s" |
ricolin | 1d81a47 | 2023-01-10 05:12:10 +0800 | [diff] [blame] | 639 | % (backup_name, task.volume_id, str(error)[:64]) |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 640 | ) |
ricolin | 75a06aa | 2023-01-11 18:31:16 +0800 | [diff] [blame] | 641 | LOG.warn( |
| 642 | "Backup (name: %s) creation for the volume %s failled. %s" |
| 643 | % (backup_name, task.volume_id, str(error)) |
| 644 | ) |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 645 | task.reason = reason |
| 646 | task.backup_status = constants.BACKUP_FAILED |
| 647 | task.save() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 648 | else: |
okozachenko1203 | 3b2f354 | 2022-05-16 19:33:46 +1000 | [diff] [blame] | 649 | # Backup planned task cannot have backup_id in the same cycle. |
| 650 | # Remove this task from the task list |
okozachenko1203 | 0295070 | 2022-10-22 03:34:16 +1100 | [diff] [blame] | 651 | task.delete_queue() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 652 | |
okozachenko | a6dcdc0 | 2021-05-10 14:13:26 +0300 | [diff] [blame] | 653 | # backup gen was not created |
| 654 | def process_pre_failed_backup(self, task): |
| 655 | # 1.notify via email |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 656 | reason = _( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 657 | "The backup creation for the volume %s was prefailed." |
| 658 | % task.volume_id |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 659 | ) |
okozachenko1203 | aaeb047 | 2022-05-17 02:28:15 +1000 | [diff] [blame] | 660 | LOG.warn(reason) |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 661 | task.reason = reason |
| 662 | task.backup_status = constants.BACKUP_FAILED |
| 663 | task.save() |
okozachenko | a6dcdc0 | 2021-05-10 14:13:26 +0300 | [diff] [blame] | 664 | |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 665 | def process_failed_backup(self, task): |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 666 | # 1. notify via email |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 667 | reason = ( |
| 668 | f"The status of backup for the volume {task.volume_id} is error." |
| 669 | ) |
okozachenko1203 | aaeb047 | 2022-05-17 02:28:15 +1000 | [diff] [blame] | 670 | LOG.warn(reason) |
okozachenko | f731e62 | 2021-05-10 17:38:30 +0300 | [diff] [blame] | 671 | # 2. delete backup generator |
okozachenko1203 | aaeb047 | 2022-05-17 02:28:15 +1000 | [diff] [blame] | 672 | try: |
ricolin | ba8e87f | 2023-06-03 01:24:41 +0800 | [diff] [blame] | 673 | self.openstacksdk.delete_backup(uuid=task.backup_id) |
ricolin | 3a2fad1 | 2023-01-29 17:31:30 +0800 | [diff] [blame] | 674 | self.create_failed_backup_obj(task) |
okozachenko1203 | aaeb047 | 2022-05-17 02:28:15 +1000 | [diff] [blame] | 675 | except OpenstackHttpException as ex: |
ricolin | 75a06aa | 2023-01-11 18:31:16 +0800 | [diff] [blame] | 676 | LOG.warn( |
okozachenko1203 | aaeb047 | 2022-05-17 02:28:15 +1000 | [diff] [blame] | 677 | _( |
ricolin | 7a5582f | 2024-11-01 21:11:14 +0800 | [diff] [blame^] | 678 | "Failed to delete volume backup %s. %s. Need " |
| 679 | "to delete manually." % (task.backup_id, str(ex)) |
okozachenko1203 | aaeb047 | 2022-05-17 02:28:15 +1000 | [diff] [blame] | 680 | ) |
| 681 | ) |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 682 | task.reason = reason |
| 683 | task.backup_status = constants.BACKUP_FAILED |
| 684 | task.save() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 685 | |
okozachenko | 179cd57 | 2021-05-06 22:23:53 +0300 | [diff] [blame] | 686 | def process_non_existing_backup(self, task): |
| 687 | task.delete_queue() |
| 688 | |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 689 | def process_available_backup(self, task): |
| 690 | LOG.info("Backup of the volume %s is successful." % task.volume_id) |
| 691 | # 1. save success backup in the backup table |
| 692 | self._volume_backup( |
| 693 | BackupMapping( |
| 694 | volume_id=task.volume_id, |
okozachenko | 179cd57 | 2021-05-06 22:23:53 +0300 | [diff] [blame] | 695 | project_id=task.project_id, |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 696 | backup_id=task.backup_id, |
| 697 | instance_id=task.instance_id, |
| 698 | backup_completed=1, |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 699 | incremental=task.incremental, |
ricolin | 72c3ce8 | 2023-03-27 15:08:23 +0800 | [diff] [blame] | 700 | created_at=timeutils.utcnow(), |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 701 | ) |
| 702 | ) |
Oleksandr Kozachenko | 9685c15 | 2022-11-02 14:19:18 +0100 | [diff] [blame] | 703 | task.backup_status = constants.BACKUP_COMPLETED |
| 704 | task.save() |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 705 | |
| 706 | def process_using_backup(self, task): |
okozachenko | cb31608 | 2021-05-07 21:30:39 +0300 | [diff] [blame] | 707 | # treat same as the available backup for now |
| 708 | self.process_available_backup(task) |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 709 | |
| 710 | def check_volume_backup_status(self, queue): |
| 711 | """Checks the backup status of the volume |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 712 | |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 713 | :params: queue: Provide the map of the volume that needs backup |
| 714 | status checked. |
| 715 | Call the backups api to see if the backup is successful. |
| 716 | """ |
okozachenko | 6302495 | 2021-05-10 20:04:25 +0300 | [diff] [blame] | 717 | project_id = queue.project_id |
okozachenko | ed4c487 | 2021-05-10 17:58:37 +0300 | [diff] [blame] | 718 | |
okozachenko | 6302495 | 2021-05-10 20:04:25 +0300 | [diff] [blame] | 719 | # The case in which the error produced before backup gen created. |
| 720 | if queue.backup_id == "NULL": |
| 721 | self.process_pre_failed_backup(queue) |
| 722 | return |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 723 | if project_id not in self.project_list: |
| 724 | self.process_non_existing_backup(queue) |
okozachenko1203 | aaeb047 | 2022-05-17 02:28:15 +1000 | [diff] [blame] | 725 | return |
okozachenko | 6302495 | 2021-05-10 20:04:25 +0300 | [diff] [blame] | 726 | self.openstacksdk.set_project(self.project_list[project_id]) |
| 727 | backup_gen = self.openstacksdk.get_backup(queue.backup_id) |
okozachenko | ed4c487 | 2021-05-10 17:58:37 +0300 | [diff] [blame] | 728 | |
okozachenko1203 | ef49f95 | 2022-05-16 22:27:28 +1000 | [diff] [blame] | 729 | if backup_gen is None: |
okozachenko | 6302495 | 2021-05-10 20:04:25 +0300 | [diff] [blame] | 730 | # TODO(Alex): need to check when it is none |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 731 | LOG.info( |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 732 | _( |
| 733 | "[Beta] Backup status of %s is returning none." |
| 734 | % (queue.backup_id) |
| 735 | ) |
okozachenko1203 | fa747f2 | 2022-05-16 20:13:54 +1000 | [diff] [blame] | 736 | ) |
okozachenko | 179cd57 | 2021-05-06 22:23:53 +0300 | [diff] [blame] | 737 | self.process_non_existing_backup(queue) |
okozachenko | 6302495 | 2021-05-10 20:04:25 +0300 | [diff] [blame] | 738 | return |
| 739 | if backup_gen.status == "error": |
| 740 | self.process_failed_backup(queue) |
| 741 | elif backup_gen.status == "available": |
| 742 | self.process_available_backup(queue) |
| 743 | elif backup_gen.status == "creating": |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 744 | LOG.info( |
| 745 | "Waiting for backup of %s to be completed" % queue.volume_id |
| 746 | ) |
okozachenko | 6302495 | 2021-05-10 20:04:25 +0300 | [diff] [blame] | 747 | else: # "deleting", "restoring", "error_restoring" status |
| 748 | self.process_using_backup(queue) |
| 749 | |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 750 | def _volume_backup(self, task): |
| 751 | # matching_backups = [ |
ricolin | e884f12 | 2024-11-01 16:28:13 +0800 | [diff] [blame] | 752 | # g for g in self.available_backups |
| 753 | # if g.backup_id == task.backup_id |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 754 | # ] |
| 755 | # if not matching_backups: |
| 756 | volume_backup = objects.Volume(self.ctx) |
| 757 | volume_backup.backup_id = task.backup_id |
| 758 | volume_backup.volume_id = task.volume_id |
| 759 | volume_backup.instance_id = task.instance_id |
Susanta Gautam | c406111 | 2021-05-10 02:39:23 +0000 | [diff] [blame] | 760 | volume_backup.project_id = task.project_id |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 761 | volume_backup.backup_completed = task.backup_completed |
okozachenko1203 | a6f8342 | 2022-11-02 02:23:54 +1100 | [diff] [blame] | 762 | volume_backup.incremental = task.incremental |
okozachenko | 6d277e3 | 2021-05-05 20:23:32 +0300 | [diff] [blame] | 763 | volume_backup.create() |