
While current package name "disaster_recovery" is a valid slug inside of the Horizon, when it comes to the Python package naming, it is not conforming with Horizon Dashboard naming pattern, where all existing dashboard packages end their names on `dashboard` or `ui`. Current naming might break Horizon installation logic by some common deployment tools, like OpenStack-Ansible. To prevent that we rename the package given the opportunity, that project is currently in Inactive state and major changes can be made to it. Change-Id: I965b545b6aada866ab5c6eb2279154b70bc5dff1
228 lines
7.2 KiB
Python
228 lines
7.2 KiB
Python
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import logging
|
|
|
|
import datetime
|
|
import re
|
|
|
|
from functools import wraps
|
|
|
|
from django.urls import reverse
|
|
from django.template.defaultfilters import date as django_date
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from horizon import exceptions
|
|
from horizon import get_user_home
|
|
from oslo_utils import uuidutils
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def create_dict(**kwargs):
|
|
"""Create a dict only with values that exists so we avoid send keys with
|
|
|
|
None values
|
|
"""
|
|
return {k: v for k, v in kwargs.items() if v}
|
|
|
|
|
|
def timestamp_to_string(ts):
|
|
return django_date(
|
|
datetime.datetime.fromtimestamp(int(ts)),
|
|
'SHORT_DATETIME_FORMAT')
|
|
|
|
|
|
def create_dummy_id():
|
|
"""Generate a dummy id for documents generated by the scheduler.
|
|
|
|
This is needed when the scheduler creates jobs with actions attached
|
|
directly, those actions are not registered in the db.
|
|
"""
|
|
return uuidutils.generate_uuid(dashed=False)
|
|
|
|
|
|
def get_action_ids(ids):
|
|
"""Return an ordered list of actions for a new job
|
|
|
|
"""
|
|
ids = ids.split('===')
|
|
return [i for i in ids if i]
|
|
|
|
|
|
def assign_and_remove(source_dict, dest_dict, key):
|
|
"""Assign a value to a destination dict from a source dict
|
|
|
|
if the key exists
|
|
"""
|
|
if key in source_dict:
|
|
dest_dict[key] = source_dict.pop(key)
|
|
|
|
|
|
class SessionObject(object):
|
|
def __init__(self, session_id, description, status, jobs,
|
|
start_datetime, interval, end_datetime):
|
|
self.session_id = session_id
|
|
self.id = session_id
|
|
self.description = description
|
|
self.status = status
|
|
self.jobs = jobs or []
|
|
self.schedule_start_date = start_datetime
|
|
self.schedule_end_date = end_datetime
|
|
self.schedule_interval = interval
|
|
|
|
|
|
class JobObject(object):
|
|
def __init__(self, job_id, description, result, event, client_id='_'):
|
|
self.job_id = job_id
|
|
self.id = job_id
|
|
self.description = description
|
|
self._result = result
|
|
self._event = event
|
|
# Checking if client_id composed like <tenant_id>_<hostname>
|
|
if re.search("^[a-z0-9]{32}_.+", client_id):
|
|
self.client_id = client_id.split('_')[1]
|
|
else:
|
|
self.client_id = client_id
|
|
|
|
@property
|
|
def event(self):
|
|
return self._event or 'stop'
|
|
|
|
@property
|
|
def result(self):
|
|
return self._result or 'pending'
|
|
|
|
|
|
class JobsInSessionObject(object):
|
|
def __init__(self, job_id, session_id, client_id, result):
|
|
self.job_id = job_id
|
|
self.session_id = session_id
|
|
self.id = session_id
|
|
self.client_id = client_id
|
|
self.result = result or 'pending'
|
|
|
|
|
|
class ActionObject(object):
|
|
def __init__(self, action_id=None, action=None, backup_name=None,
|
|
job_id=None):
|
|
|
|
# action basic info
|
|
self.id = action_id
|
|
self.action_id = action_id or create_dummy_id()
|
|
self.action = action or 'backup'
|
|
self.backup_name = backup_name or 'no backup name available'
|
|
self.job_id = job_id
|
|
|
|
|
|
class ActionObjectDetail(object):
|
|
def __init__(self, action_id=None, action=None, backup_name=None,
|
|
path_to_backup=None, storage=None, mode=None, container=None,
|
|
mandatory=None, max_retries=None, max_retries_interval=None):
|
|
|
|
# action basic info
|
|
self.id = action_id
|
|
self.action_id = action_id or create_dummy_id()
|
|
self.action = action or 'backup'
|
|
self.backup_name = backup_name or 'no backup name available'
|
|
self.path_to_backup = path_to_backup
|
|
self.storage = storage or 'swift'
|
|
self.mode = mode or 'fs'
|
|
self.container = container
|
|
|
|
# action rules
|
|
self.mandatory = mandatory
|
|
self.max_retries = max_retries
|
|
self.max_retries_interval = max_retries_interval
|
|
|
|
|
|
class BackupObject(object):
|
|
def __init__(self, backup_id=None, action=None, time_stamp=None,
|
|
backup_name=None, backup_media=None, path_to_backup=None,
|
|
hostname=None, level=None, container=None,
|
|
curr_backup_level=None, encrypted=None,
|
|
total_broken_links=None, excluded_files=None, storage=None,
|
|
ssh_host=None, ssh_key=None, ssh_username=None,
|
|
ssh_port=None, mode=None):
|
|
self.backup_id = backup_id
|
|
self.id = backup_id
|
|
self.backup_name = backup_name
|
|
self.action = action or 'backup'
|
|
self.time_stamp = time_stamp
|
|
self.backup_media = backup_media or 'fs'
|
|
self.path_to_backup = path_to_backup
|
|
self.hostname = hostname
|
|
self.container = container
|
|
self.level = level
|
|
self.curr_backup_level = curr_backup_level or 0
|
|
self.encrypted = encrypted
|
|
self.total_broken_links = total_broken_links or 0
|
|
self.excluded_files = excluded_files
|
|
self.storage = storage
|
|
self.ssh_host = ssh_host
|
|
self.ssh_key = ssh_key
|
|
self.ssh_username = ssh_username
|
|
self.ssh_port = ssh_port or 22
|
|
self.mode = mode or 'fs'
|
|
|
|
|
|
class ClientObject(object):
|
|
def __init__(self, hostname, client_id, client_uuid):
|
|
self.hostname = hostname
|
|
self.client_id = client_id
|
|
self.uuid = client_uuid
|
|
self.id = client_id
|
|
|
|
|
|
def shield(message, redirect=''):
|
|
"""decorator to reduce boilerplate try except blocks for horizon functions
|
|
|
|
:param message: a str error message
|
|
:param redirect: a str with the redirect namespace without including
|
|
horizon:disaster_recovery:
|
|
eg. @shield('error', redirect='jobs:index')
|
|
"""
|
|
def wrap(function):
|
|
|
|
@wraps(function)
|
|
def wrapped_function(view, *args, **kwargs):
|
|
|
|
try:
|
|
return function(view, *args, **kwargs)
|
|
except Exception as error:
|
|
LOG.error(error.message)
|
|
namespace = "horizon:disaster_recovery:"
|
|
r = reverse("{0}{1}".format(namespace, redirect))
|
|
|
|
if view.request.path == r:
|
|
# To avoid an endless loop, we must not redirect to the
|
|
# same page on which the error happened
|
|
user_home = get_user_home(view.request.user)
|
|
exceptions.handle(view.request, _(error.message),
|
|
redirect=user_home)
|
|
else:
|
|
exceptions.handle(view.request, _(error.message),
|
|
redirect=r)
|
|
|
|
return wrapped_function
|
|
return wrap
|
|
|
|
|
|
def timestamp_to_iso(ts):
|
|
"""Generate an iso date from time stamp
|
|
|
|
:param ts: time stamp
|
|
:return: iso date
|
|
"""
|
|
return datetime.datetime.fromtimestamp(ts).isoformat()
|