222 lines
7.1 KiB
Python
222 lines
7.1 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 uuid
|
|
import re
|
|
|
|
from functools import wraps
|
|
|
|
from django.core.urlresolvers import reverse
|
|
from django.template.defaultfilters import date as django_date
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
from horizon import exceptions
|
|
from horizon import get_user_home
|
|
|
|
|
|
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 uuid.uuid4().hex
|
|
|
|
|
|
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, _(message),
|
|
redirect=user_home)
|
|
else:
|
|
exceptions.handle(view.request, _(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()
|