Add freezer protection plugin for karbor
Change-Id: I619b06ff33fb4339a5851098867a46298a9aeb51 blueprint: freezer-protection-plugin
This commit is contained in:
parent
e9160d22ce
commit
bf752c4a84
|
@ -0,0 +1,74 @@
|
|||
# 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.
|
||||
|
||||
from freezerclient.v1 import client as freezer_client
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from karbor.common import config
|
||||
from karbor.services.protection.clients import utils
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
SERVICE = "freezer"
|
||||
freezer_client_opts = [
|
||||
cfg.StrOpt(SERVICE + '_endpoint',
|
||||
help='URL of the freezer endpoint.'),
|
||||
cfg.StrOpt(SERVICE + '_catalog_info',
|
||||
default='backup:freezer:publicURL',
|
||||
help='Info to match when looking for freezer in the service '
|
||||
'catalog. Format is: separated values of the form: '
|
||||
'<service_type>:<service_name>:<endpoint_type> - '
|
||||
'Only used if freezer_endpoint is unset'),
|
||||
cfg.StrOpt(SERVICE + '_ca_cert_file',
|
||||
help='Location of the CA certificate file '
|
||||
'to use for client requests in SSL connections.'),
|
||||
cfg.BoolOpt(SERVICE + '_auth_insecure',
|
||||
default=False,
|
||||
help='Bypass verification of server certificate when '
|
||||
'making SSL connection to Freezer.'),
|
||||
]
|
||||
|
||||
CONFIG_GROUP = '%s_client' % SERVICE
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(config.service_client_opts, group=CONFIG_GROUP)
|
||||
CONF.register_opts(config.keystone_client_opts, group=CONFIG_GROUP)
|
||||
CONF.register_opts(freezer_client_opts, group=CONFIG_GROUP)
|
||||
CONF.set_default('service_name', 'freezer', CONFIG_GROUP)
|
||||
CONF.set_default('service_type', 'backup', CONFIG_GROUP)
|
||||
|
||||
FREEZERCLIENT_VERSION = '3'
|
||||
|
||||
|
||||
def create(context, conf, **kwargs):
|
||||
conf.register_opts(freezer_client_opts, group=CONFIG_GROUP)
|
||||
|
||||
client_config = conf[CONFIG_GROUP]
|
||||
url = utils.get_url(SERVICE, context, client_config,
|
||||
append_project_fmt='%(url)s/%(project)s', **kwargs)
|
||||
|
||||
if kwargs.get('session'):
|
||||
return freezer_client.Client(version=FREEZERCLIENT_VERSION,
|
||||
session=kwargs.get('session'),
|
||||
endpoint=url
|
||||
)
|
||||
args = {
|
||||
'project_id': context.project_id,
|
||||
'project_name': context.project_name,
|
||||
'cacert': client_config.freezer_ca_cert_file,
|
||||
'insecure': client_config.freezer_auth_insecure,
|
||||
'endpoint': url,
|
||||
'token': context.auth_token,
|
||||
'version': FREEZERCLIENT_VERSION,
|
||||
'auth_url': client_config.auth_uri
|
||||
}
|
||||
return freezer_client.Client(**args)
|
|
@ -0,0 +1,471 @@
|
|||
# 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 random
|
||||
|
||||
from functools import partial
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
|
||||
from karbor.common import constants
|
||||
from karbor import exception
|
||||
from karbor.services.protection.client_factory import ClientFactory
|
||||
from karbor.services.protection import protection_plugin
|
||||
from karbor.services.protection.protection_plugins import utils
|
||||
from karbor.services.protection.protection_plugins.volume import \
|
||||
volume_freezer_plugin_schemas
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
freezer_backup_opts = [
|
||||
cfg.IntOpt(
|
||||
'poll_interval', default=20,
|
||||
help='Poll interval for Freezer Backup Resource status.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'scheduler_client_id', default=None,
|
||||
help='The freezer scheduler client id to schedule the jobs'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'container', default='karbor',
|
||||
help='The container for Freezer backup storage.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'storage', default='swift',
|
||||
help='The storage type for Freezer backup storage.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'ssh_key',
|
||||
help='The ssh key for Freezer ssh driver.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'ssh_username',
|
||||
help='The ssh user name for Freezer ssh driver.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'ssh_host',
|
||||
help='The ssh host for Freezer ssh driver.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'ssh_port',
|
||||
help='The ssh port for Freezer ssh driver.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'endpoint',
|
||||
help='The storage endpoint for Freezer S3 driver.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'access_key',
|
||||
help='The storage access key for Freezer S3 driver.'
|
||||
),
|
||||
cfg.StrOpt(
|
||||
'secret_key',
|
||||
help='The storage secret key for Freezer S3 driver.'
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
def get_job_status(freezer_job_operation, job_id):
|
||||
LOG.debug('Polling freezer job status, job_id: {0}'.format(job_id))
|
||||
job_status = freezer_job_operation.get_status(job_id)
|
||||
LOG.debug('Polled freezer job status, job_id: {0}, job_status: {1}'.format(
|
||||
job_id, job_status
|
||||
))
|
||||
return job_status
|
||||
|
||||
|
||||
class FreezerStorage(object):
|
||||
def __init__(self, storage_type, storage_path, **kwargs):
|
||||
self.storage_type = storage_type
|
||||
self.storage_path = storage_path
|
||||
self.config = kwargs
|
||||
|
||||
def get_storage(self):
|
||||
|
||||
storage = {
|
||||
'storage': self.storage_type,
|
||||
'container': self.storage_path
|
||||
}
|
||||
|
||||
if self.storage_type == 's3':
|
||||
storage['endpoint'] = self.config.get('endpoint', None)
|
||||
storage['access_key'] = self.config.get('access_key', None)
|
||||
storage['secret_key'] = self.config.get('secret_key', None)
|
||||
|
||||
if self.storage_type == 'ssh':
|
||||
storage['ssh_key'] = self.config.get('ssh_key', None)
|
||||
storage['ssh_port'] = self.config.get('ssh_port', None)
|
||||
storage['ssh_username'] = self.config.get('ssh_username', None)
|
||||
storage['ssh_host'] = self.config.get('ssh_host', None)
|
||||
|
||||
return storage
|
||||
|
||||
|
||||
class FreezerTask(object):
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
self.client = ClientFactory.create_client('freezer', self.context)
|
||||
|
||||
def _client(self):
|
||||
return self.client
|
||||
|
||||
def get(self, job_id):
|
||||
return self._client().jobs.get(job_id)
|
||||
|
||||
def get_status(self, job_id):
|
||||
return self._client().jobs.get(job_id).get('job_schedule',
|
||||
{}).get('result')
|
||||
|
||||
def create(self, backup_name, storage, description, resource,
|
||||
action_type, scheduler_client_id):
|
||||
return self._build(backup_name, storage, description,
|
||||
resource, action_type, scheduler_client_id)
|
||||
|
||||
def create_delete_job(self, job):
|
||||
for job_action in job['job_actions']:
|
||||
job_action['freezer_action']['action'] = 'admin'
|
||||
job_action['freezer_action']['remove_older_than'] = '-1'
|
||||
job_id = self._client().jobs.create(job)
|
||||
self._client().jobs.start_job(job_id)
|
||||
return job_id, job
|
||||
|
||||
def create_restore_job(self, job):
|
||||
for job_action in job['job_actions']:
|
||||
job_action['freezer_action']['action'] = 'restore'
|
||||
job_id = self._client().jobs.create(job)
|
||||
self._client().jobs.start_job(job_id)
|
||||
return job_id, job
|
||||
|
||||
def delete(self, job_id):
|
||||
actions = self.actions(job_id)
|
||||
for action in actions:
|
||||
self._client().actions.delete(action.get('action_id'))
|
||||
return self._client().jobs.delete(job_id)
|
||||
|
||||
def actions(self, job_id):
|
||||
job = self.get(job_id)
|
||||
if not job:
|
||||
return []
|
||||
return job.get('job_actions', [])
|
||||
|
||||
def _build(self, backup_name, storage, description,
|
||||
resource, action_type, scheduler_client_id):
|
||||
client_id = scheduler_client_id if scheduler_client_id else \
|
||||
FreezerSchedulerClient(self._client()).get_random_client_id()
|
||||
job = {
|
||||
'description': resource.id if not description else description,
|
||||
'job_actions': [self._build_action(
|
||||
backup_name=backup_name,
|
||||
storage=storage,
|
||||
resource=resource,
|
||||
action_type=action_type,
|
||||
)],
|
||||
'client_id': client_id
|
||||
}
|
||||
|
||||
job_id = self._client().jobs.create(job)
|
||||
self._client().jobs.start_job(job_id)
|
||||
return job_id, job
|
||||
|
||||
@staticmethod
|
||||
def _build_action(backup_name, storage, resource, action_type):
|
||||
backup_name = backup_name.replace(' ', '_')
|
||||
action = {
|
||||
'backup_name': backup_name,
|
||||
'action': action_type,
|
||||
'mode': 'cinder',
|
||||
'cinder_vol_id': resource.id
|
||||
}
|
||||
|
||||
action = dict(action, **storage.get_storage())
|
||||
|
||||
if action_type == 'admin':
|
||||
action['remove_older_than'] = '-1'
|
||||
|
||||
return {'freezer_action': action}
|
||||
|
||||
|
||||
class FreezerSchedulerClient(object):
|
||||
"""Freezer scheduler to schedule the jobs.
|
||||
|
||||
All the freezer scheduler clients should be able to schedule jobs
|
||||
which resource type is nova instance or cinder volume.
|
||||
"""
|
||||
|
||||
def __init__(self, freezer_client):
|
||||
self.client = freezer_client
|
||||
|
||||
def get_random_client_id(self):
|
||||
clients = self.client.clients.list()
|
||||
if len(clients) < 1:
|
||||
raise Exception('No freezer-scheduler client exist')
|
||||
client_index = random.randint(0, len(clients) - 1)
|
||||
return [
|
||||
c.get('client', {}).get('client_id') for c in clients
|
||||
][client_index]
|
||||
|
||||
|
||||
class ProtectOperation(protection_plugin.Operation):
|
||||
def __init__(self, poll_interval, freezer_storage, scheduler_client_id):
|
||||
super(ProtectOperation, self).__init__()
|
||||
self._poll_interval = poll_interval
|
||||
self._scheduler_client_id = scheduler_client_id
|
||||
self.freezer_storage = freezer_storage
|
||||
|
||||
def on_main(self, checkpoint, resource, context, parameters, **kwargs):
|
||||
resource_id = resource.id
|
||||
bank_section = checkpoint.get_resource_bank_section(resource_id)
|
||||
|
||||
LOG.info('Creating freezer protection backup, resource_id: {0}'
|
||||
.format(resource_id))
|
||||
bank_section.update_object('status',
|
||||
constants.RESOURCE_STATUS_PROTECTING)
|
||||
|
||||
backup_name = parameters.get('backup_name', 'backup{0}'
|
||||
.format(resource_id))
|
||||
description = parameters.get('description', None)
|
||||
self.freezer_storage.storage_path = "{0}/{1}".format(
|
||||
self.freezer_storage.storage_path, checkpoint.id)
|
||||
job_id, job_info = None, None
|
||||
freezer_task = FreezerTask(context)
|
||||
try:
|
||||
job_id, job_info = freezer_task.create(
|
||||
backup_name=backup_name,
|
||||
storage=self.freezer_storage,
|
||||
description=description,
|
||||
resource=resource,
|
||||
action_type='backup',
|
||||
scheduler_client_id=self._scheduler_client_id
|
||||
)
|
||||
LOG.debug('Creating freezer backup job successful, job_id: {0}'
|
||||
.format(job_id))
|
||||
is_success = utils.status_poll(
|
||||
partial(get_job_status, freezer_task, job_id),
|
||||
interval=self._poll_interval,
|
||||
success_statuses={'success'},
|
||||
failure_statuses={'fail'},
|
||||
ignore_statuses={'aborted', ''},
|
||||
ignore_unexpected=True
|
||||
)
|
||||
|
||||
if is_success is not True:
|
||||
LOG.error("The status of freezer job (id: {0}) is invalid."
|
||||
.format(job_id))
|
||||
raise exception.CreateResourceFailed(
|
||||
name="Freezer Backup FreezerTask",
|
||||
reason="The status of freezer job is invalid.",
|
||||
resource_id=resource_id,
|
||||
resource_type=resource.type)
|
||||
|
||||
resource_definition = {
|
||||
'job_id': job_id,
|
||||
'job_info': job_info
|
||||
}
|
||||
|
||||
bank_section.update_object("metadata", resource_definition)
|
||||
|
||||
bank_section.update_object("status",
|
||||
constants.RESOURCE_STATUS_AVAILABLE)
|
||||
|
||||
except exception.CreateResourceFailed as e:
|
||||
LOG.error('Error creating backup (resource_id: {0}, reason: {1})'
|
||||
.format(resource_id, e))
|
||||
if job_id:
|
||||
freezer_task.delete(job_id)
|
||||
bank_section.update_object('status',
|
||||
constants.RESOURCE_STATUS_ERROR)
|
||||
raise
|
||||
LOG.debug('Finish creating freezer backup resource')
|
||||
freezer_task.delete(job_id)
|
||||
|
||||
|
||||
class RestoreOperation(protection_plugin.Operation):
|
||||
def __init__(self, poll_interval):
|
||||
super(RestoreOperation, self).__init__()
|
||||
self._poll_interval = poll_interval
|
||||
|
||||
def on_main(self, checkpoint, resource, context, parameters, **kwargs):
|
||||
resource_id = resource.id
|
||||
bank_section = checkpoint.get_resource_bank_section(resource_id)
|
||||
LOG.info("Creating freezer protection backup, resource_id: {0}"
|
||||
.format(resource_id))
|
||||
|
||||
resource_metadata = bank_section.get_object('metadata')
|
||||
freezer_job_info = resource_metadata.get('job_info', None)
|
||||
if not freezer_job_info:
|
||||
raise exception.RestoreResourceFailed(
|
||||
name='Freezer Backup FreezerTask',
|
||||
reason='The content of freezer job is invalid.',
|
||||
resource_id=resource_id,
|
||||
resource_type=resource.type
|
||||
)
|
||||
freezer_task = FreezerTask(context)
|
||||
job_id, job_info = None, None
|
||||
try:
|
||||
job_id, job_info = freezer_task.create_restore_job(
|
||||
freezer_job_info
|
||||
)
|
||||
is_success = utils.status_poll(
|
||||
partial(get_job_status, freezer_task, job_id),
|
||||
interval=self._poll_interval,
|
||||
success_statuses={'success'},
|
||||
failure_statuses={'fail'},
|
||||
ignore_statuses={'aborted', ''},
|
||||
ignore_unexpected=True
|
||||
)
|
||||
|
||||
if is_success is not True:
|
||||
LOG.error("The status of freezer job (id: {0}) is invalid."
|
||||
.format(job_id))
|
||||
raise exception.RestoreResourceFailed(
|
||||
name="Freezer Backup FreezerTask",
|
||||
reason="The status of freezer job is invalid.",
|
||||
resource_id=resource_id,
|
||||
resource_type=resource.type
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
LOG.error("Restore freezer backup resource failed, resource_type:"
|
||||
"{0}, resource_id: {1}"
|
||||
.format(resource.type, resource.id))
|
||||
if job_id:
|
||||
freezer_task.delete(job_id)
|
||||
raise exception.RestoreResourceFailed(
|
||||
name="Freezer Backup FreezerTask",
|
||||
reason=e,
|
||||
resource_id=resource_id,
|
||||
resource_type=resource.type
|
||||
)
|
||||
LOG.debug('Finish restoring freezer backup resource')
|
||||
freezer_task.delete(job_id)
|
||||
|
||||
|
||||
class DeleteOperation(protection_plugin.Operation):
|
||||
def __init__(self, poll_interval):
|
||||
super(DeleteOperation, self).__init__()
|
||||
self._poll_interval = poll_interval
|
||||
|
||||
def on_main(self, checkpoint, resource, context, parameters, **kwargs):
|
||||
resource_id = resource.id
|
||||
bank_section = checkpoint.get_resource_bank_section(resource_id)
|
||||
LOG.info("Deleting freezer protection backup, resource_id: {0}"
|
||||
.format(resource_id))
|
||||
|
||||
bank_section.update_object('status',
|
||||
constants.RESOURCE_STATUS_DELETING)
|
||||
resource_metadata = bank_section.get_object('metadata')
|
||||
freezer_task_info = resource_metadata.get('job_info', None)
|
||||
if not freezer_task_info:
|
||||
raise exception.DeleteResourceFailed(
|
||||
name='Freezer Backup FreezerTask',
|
||||
reason='The content of freezer job is invalid.',
|
||||
resource_id=resource_id,
|
||||
resource_type=resource.type
|
||||
)
|
||||
|
||||
freezer_job_operation = FreezerTask(context)
|
||||
job_id, job_info = None, None
|
||||
try:
|
||||
job_id, job_info = freezer_job_operation.create_delete_job(
|
||||
freezer_task_info
|
||||
)
|
||||
|
||||
is_success = utils.status_poll(
|
||||
partial(get_job_status, freezer_job_operation, job_id),
|
||||
interval=self._poll_interval,
|
||||
success_statuses={'success'},
|
||||
failure_statuses={'fail'},
|
||||
ignore_statuses={'aborted', ''},
|
||||
ignore_unexpected=True
|
||||
)
|
||||
|
||||
if is_success is not True:
|
||||
LOG.error("The status of freezer job (id: {0}) is invalid."
|
||||
.format(job_id))
|
||||
raise exception.CreateResourceFailed(
|
||||
name="Freezer Backup FreezerTask",
|
||||
reason="The status of freezer job is invalid.",
|
||||
resource_id=resource_id,
|
||||
resource_type=resource.type
|
||||
)
|
||||
except Exception as e:
|
||||
LOG.error("Delete freezer backup resource failed, resource_type:"
|
||||
"{0}, resource_id: {1}"
|
||||
.format(resource.type, resource.id))
|
||||
if job_id:
|
||||
freezer_job_operation.delete(job_id)
|
||||
raise exception.DeleteResourceFailed(
|
||||
name="Freezer Backup FreezerTask",
|
||||
reason=e,
|
||||
resource_id=resource_id,
|
||||
resource_type=resource.type
|
||||
)
|
||||
LOG.debug('Finish deleting freezer backup resource')
|
||||
bank_section.delete_object('metadata')
|
||||
bank_section.update_object('status',
|
||||
constants.RESOURCE_STATUS_DELETED)
|
||||
freezer_job_operation.delete(job_id)
|
||||
|
||||
|
||||
class FreezerProtectionPlugin(protection_plugin.ProtectionPlugin):
|
||||
_SUPPORT_RESOURCE_TYPES = [constants.VOLUME_RESOURCE_TYPE]
|
||||
|
||||
def __init__(self, config=None):
|
||||
super(FreezerProtectionPlugin, self).__init__(config)
|
||||
self._config.register_opts(freezer_backup_opts,
|
||||
'freezer_protection_plugin')
|
||||
self._plugin_config = self._config.freezer_protection_plugin
|
||||
self._poll_interval = self._plugin_config.poll_interval
|
||||
self._scheduler_client_id = self._plugin_config.scheduler_client_id
|
||||
self._freezer_storage = FreezerStorage(
|
||||
storage_type=self._plugin_config.storage,
|
||||
storage_path=self._plugin_config.container,
|
||||
endpoint=self._plugin_config.endpoint,
|
||||
access_key=self._plugin_config.access_key,
|
||||
secret_key=self._plugin_config.secret_key,
|
||||
ssh_key=self._plugin_config.ssh_key,
|
||||
ssh_port=self._plugin_config.ssh_port,
|
||||
ssh_username=self._plugin_config.ssh_username,
|
||||
ssh_host=self._plugin_config.ssh_host
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def get_supported_resources_types(cls):
|
||||
return cls._SUPPORT_RESOURCE_TYPES
|
||||
|
||||
@classmethod
|
||||
def get_options_schema(cls, resource_type):
|
||||
return volume_freezer_plugin_schemas.OPTIONS_SCHEMA
|
||||
|
||||
@classmethod
|
||||
def get_restore_schema(cls, resource_type):
|
||||
return volume_freezer_plugin_schemas.RESTORE_SCHEMA
|
||||
|
||||
@classmethod
|
||||
def get_saved_info_schema(cls, resource_type):
|
||||
return volume_freezer_plugin_schemas.SAVED_INFO_SCHEMA
|
||||
|
||||
@classmethod
|
||||
def get_saved_info(cls, metadata_store, resource):
|
||||
pass
|
||||
|
||||
def get_protect_operation(self, resource):
|
||||
return ProtectOperation(self._poll_interval,
|
||||
self._freezer_storage,
|
||||
self._scheduler_client_id
|
||||
)
|
||||
|
||||
def get_restore_operation(self, resource):
|
||||
return RestoreOperation(self._poll_interval)
|
||||
|
||||
def get_delete_operation(self, resource):
|
||||
return DeleteOperation(self._poll_interval)
|
|
@ -0,0 +1,51 @@
|
|||
# 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.
|
||||
|
||||
OPTIONS_SCHEMA = {
|
||||
"title": "Freezer Protection Options",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"backup_name": {
|
||||
"type": "string",
|
||||
"title": "Backup Name",
|
||||
"description": "The name of the backup.",
|
||||
"default": None
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"title": "Description",
|
||||
"description": "The description of the backup."
|
||||
}
|
||||
},
|
||||
"required": ["backup_name"]
|
||||
}
|
||||
|
||||
RESTORE_SCHEMA = {
|
||||
"title": "Freezer Protection Restore",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"restore_name": {
|
||||
"type": "string",
|
||||
"title": "Restore Resource Name",
|
||||
"description": "The name of the restore resource ",
|
||||
"default": None
|
||||
},
|
||||
},
|
||||
"required": ["restore_name"]
|
||||
}
|
||||
|
||||
SAVED_INFO_SCHEMA = {
|
||||
"title": "Freezer Protection Saved Info",
|
||||
"type": "object",
|
||||
"properties": {},
|
||||
"required": []
|
||||
}
|
|
@ -0,0 +1,190 @@
|
|||
# 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 collections
|
||||
|
||||
from karbor.common import constants
|
||||
from karbor.context import RequestContext
|
||||
from karbor.resource import Resource
|
||||
from karbor.services.protection.bank_plugin import Bank
|
||||
from karbor.services.protection.bank_plugin import BankPlugin
|
||||
from karbor.services.protection.bank_plugin import BankSection
|
||||
|
||||
from karbor.services.protection.protection_plugins.volume import \
|
||||
volume_freezer_plugin_schemas
|
||||
from karbor.services.protection.protection_plugins.volume.\
|
||||
volume_freezer_plugin import FreezerProtectionPlugin
|
||||
|
||||
from karbor.tests import base
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture
|
||||
|
||||
|
||||
class FakeBankPlugin(BankPlugin):
|
||||
def update_object(self, key, value):
|
||||
return
|
||||
|
||||
def get_object(self, key):
|
||||
return
|
||||
|
||||
def list_objects(self, prefix=None, limit=None, marker=None,
|
||||
sort_dir=None):
|
||||
return
|
||||
|
||||
def delete_object(self, key):
|
||||
return
|
||||
|
||||
def get_owner_id(self):
|
||||
return
|
||||
|
||||
|
||||
fake_bank = Bank(FakeBankPlugin())
|
||||
fake_bank_section = BankSection(bank=fake_bank, section="fake")
|
||||
|
||||
ResourceNode = collections.namedtuple(
|
||||
"ResourceNode",
|
||||
["value",
|
||||
"child_nodes"]
|
||||
)
|
||||
|
||||
Job = collections.namedtuple(
|
||||
"Job",
|
||||
["job_schedule"]
|
||||
)
|
||||
|
||||
|
||||
def call_hooks(operation, checkpoint, resource, context, parameters, **kwargs):
|
||||
def noop(*args, **kwargs):
|
||||
pass
|
||||
|
||||
hooks = (
|
||||
'on_prepare_begin',
|
||||
'on_prepare_finish',
|
||||
'on_main',
|
||||
'on_complete',
|
||||
)
|
||||
for hook_name in hooks:
|
||||
hook = getattr(operation, hook_name, noop)
|
||||
hook(checkpoint, resource, context, parameters, **kwargs)
|
||||
|
||||
|
||||
class FakeCheckpoint(object):
|
||||
def __init__(self):
|
||||
self.bank_section = fake_bank_section
|
||||
self.id = "fake_id"
|
||||
|
||||
def get_resource_bank_section(self, resource_id):
|
||||
return self.bank_section
|
||||
|
||||
|
||||
class VolumeFreezerProtectionPluginTest(base.TestCase):
|
||||
def setUp(self):
|
||||
super(VolumeFreezerProtectionPluginTest, self).setUp()
|
||||
|
||||
plugin_config = cfg.ConfigOpts()
|
||||
plugin_config_fixture = self.useFixture(fixture.Config(plugin_config))
|
||||
plugin_config_fixture.load_raw_values(
|
||||
group='volume_freezer_plugin',
|
||||
poll_interval=0,
|
||||
)
|
||||
|
||||
self.plugin = FreezerProtectionPlugin(plugin_config)
|
||||
|
||||
self.cntxt = RequestContext(user_id='demo',
|
||||
project_id='fake_project_id',
|
||||
auth_token='fake_token')
|
||||
self.freezer_client = mock.MagicMock()
|
||||
self.checkpoint = FakeCheckpoint()
|
||||
|
||||
def test_get_options_schema(self):
|
||||
options_schema = self.plugin.get_options_schema(
|
||||
constants.VOLUME_RESOURCE_TYPE)
|
||||
self.assertEqual(options_schema,
|
||||
volume_freezer_plugin_schemas.OPTIONS_SCHEMA)
|
||||
|
||||
def test_get_restore_schema(self):
|
||||
options_schema = self.plugin.get_restore_schema(
|
||||
constants.VOLUME_RESOURCE_TYPE)
|
||||
self.assertEqual(options_schema,
|
||||
volume_freezer_plugin_schemas.RESTORE_SCHEMA)
|
||||
|
||||
def test_get_saved_info_schema(self):
|
||||
options_schema = self.plugin.get_saved_info_schema(
|
||||
constants.VOLUME_RESOURCE_TYPE)
|
||||
self.assertEqual(options_schema,
|
||||
volume_freezer_plugin_schemas.SAVED_INFO_SCHEMA)
|
||||
|
||||
@mock.patch('karbor.services.protection.protection_plugins.volume.'
|
||||
'volume_freezer_plugin.utils.status_poll')
|
||||
@mock.patch('karbor.services.protection.clients.freezer.create')
|
||||
def test_create_backup(self, mock_freezer_create, mock_status_poll):
|
||||
resource = Resource(id="123",
|
||||
type=constants.VOLUME_RESOURCE_TYPE,
|
||||
name='fake')
|
||||
|
||||
fake_bank_section.update_object = mock.MagicMock()
|
||||
|
||||
protect_operation = self.plugin.get_protect_operation(resource)
|
||||
mock_freezer_create.return_value = self.freezer_client
|
||||
|
||||
self.freezer_client.clients.list = mock.MagicMock()
|
||||
self.freezer_client.clients.list.return_value = [
|
||||
{
|
||||
'cliend_id': 'fake_client_id'
|
||||
}
|
||||
]
|
||||
|
||||
self.freezer_client.jobs.create = mock.MagicMock()
|
||||
self.freezer_client.jobs.create.return_value = "123"
|
||||
self.freezer_client.jobs.delete = mock.MagicMock()
|
||||
mock_status_poll.return_value = True
|
||||
call_hooks(protect_operation, self.checkpoint, resource, self.cntxt,
|
||||
{})
|
||||
|
||||
@mock.patch('karbor.services.protection.protection_plugins.volume.'
|
||||
'volume_freezer_plugin.utils.status_poll')
|
||||
@mock.patch('karbor.services.protection.clients.freezer.create')
|
||||
def test_delete_backup(self, mock_freezer_create, mock_status_poll):
|
||||
resource = Resource(id="123",
|
||||
type=constants.VOLUME_RESOURCE_TYPE,
|
||||
name='fake')
|
||||
delete_operation = self.plugin.get_delete_operation(resource)
|
||||
fake_bank_section.update_object = mock.MagicMock()
|
||||
fake_bank_section.get_object = mock.MagicMock()
|
||||
fake_bank_section.get_object.return_value = {
|
||||
'job_info': {
|
||||
'description': '123',
|
||||
'job_actions': [{
|
||||
'freezer_action': {
|
||||
'backup_name': 'test',
|
||||
'action': 'backup',
|
||||
'mode': 'cinder',
|
||||
'cinder_vol_id': 'test',
|
||||
'storage': 'swift',
|
||||
'container': 'karbor/123'
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
mock_freezer_create.return_value = self.freezer_client
|
||||
self.freezer_client.jobs.create = mock.MagicMock()
|
||||
self.freezer_client.jobs.create.return_value = '321'
|
||||
self.freezer_client.jobs.delete = mock.MagicMock()
|
||||
mock_status_poll.return_value = True
|
||||
call_hooks(delete_operation, self.checkpoint, resource, self.cntxt,
|
||||
{})
|
||||
|
||||
def test_get_supported_resources_types(self):
|
||||
types = self.plugin.get_supported_resources_types()
|
||||
self.assertEqual(types,
|
||||
[constants.VOLUME_RESOURCE_TYPE])
|
|
@ -26,6 +26,7 @@ oslo.service>=1.10.0 # Apache-2.0
|
|||
oslo.versionedobjects>=1.17.0 # Apache-2.0
|
||||
Paste # MIT
|
||||
PasteDeploy>=1.5.0 # MIT
|
||||
python-freezerclient>=1.3.0 # Apache-2.0
|
||||
python-glanceclient>=2.8.0 # Apache-2.0
|
||||
python-novaclient>=9.1.0 # Apache-2.0
|
||||
python-cinderclient>=3.2.0 # Apache-2.0
|
||||
|
|
|
@ -38,6 +38,7 @@ karbor.protections =
|
|||
karbor-swift-bank-plugin = karbor.services.protection.bank_plugins.swift_bank_plugin:SwiftBankPlugin
|
||||
karbor-fs-bank-plugin = karbor.services.protection.bank_plugins.file_system_bank_plugin:FileSystemBankPlugin
|
||||
karbor-s3-bank-plugin = karbor.services.protection.bank_plugins.s3_bank_plugin:S3BankPlugin
|
||||
karbor-volume-freezer-plugin = karbor.services.protection.protection_plugins.volume.volume_freezer_plugin:FreezerProtectionPlugin
|
||||
karbor-volume-protection-plugin = karbor.services.protection.protection_plugins.volume.cinder_protection_plugin:CinderBackupProtectionPlugin
|
||||
karbor-volume-snapshot-plugin = karbor.services.protection.protection_plugins.volume.volume_snapshot_plugin:VolumeSnapshotProtectionPlugin
|
||||
karbor-image-protection-plugin = karbor.services.protection.protection_plugins.image.image_protection_plugin:GlanceProtectionPlugin
|
||||
|
|
|
@ -17,6 +17,7 @@ python-swiftclient>=3.2.0 # Apache-2.0
|
|||
python-glanceclient>=2.8.0 # Apache-2.0
|
||||
python-novaclient>=9.1.0 # Apache-2.0
|
||||
python-cinderclient>=3.2.0 # Apache-2.0
|
||||
python-freezerclient>=1.3.0 # Apache-2.0
|
||||
python-karborclient>=0.6.0 # Apache-2.0
|
||||
python-neutronclient>=6.3.0 # Apache-2.0
|
||||
python-troveclient>=2.2.0 # Apache-2.0
|
||||
|
|
Loading…
Reference in New Issue