# 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 oslo_config import cfg from oslo_log import log as logging from oslo_service import loopingcall from oslo_utils import uuidutils from karbor.common import constants from karbor.services.protection import client_factory from karbor.services.protection import resource_flow from karbor.services.protection import restore_heat from taskflow import task sync_status_opts = [ cfg.IntOpt('sync_status_interval', default=20, help='update protection status interval') ] CONF = cfg.CONF CONF.register_opts(sync_status_opts) LOG = logging.getLogger(__name__) class InitiateRestoreTask(task.Task): def execute(self, restore, *args, **kwargs): LOG.debug("Initiate restore restore_id: %s", restore.id) restore['status'] = constants.RESTORE_STATUS_IN_PROGRESS restore.save() def revert(self, restore, *args, **kwargs): LOG.debug("Failed to restore restore_id: %s", restore.id) restore['status'] = constants.RESTORE_STATUS_FAILURE restore.save() class CompleteRestoreTask(task.Task): def execute(self, restore, *args, **kwargs): LOG.debug("Complete restore restore_id: %s", restore.id) restore['status'] = constants.RESTORE_STATUS_SUCCESS restore.save() class CreateHeatTask(task.Task): default_provides = ['heat_client', 'heat_template'] def execute(self, context, heat_conf): LOG.info('Creating Heat template. Target: "%s"' % heat_conf.get('auth_url', '(None)')) heat_client = client_factory.ClientFactory.create_client( 'heat', context=context, **heat_conf) heat_template = restore_heat.HeatTemplate() return (heat_client, heat_template) class CreateStackTask(task.Task): default_provides = 'stack_id' def execute(self, heat_client, heat_template): stack_name = "restore_%s" % uuidutils.generate_uuid() if heat_template.len() == 0: LOG.info('Not creating Heat stack, no resources in template') return None LOG.info('Creating Heat stack, stack_name: %s', stack_name) try: body = heat_client.stacks.create( stack_name=stack_name, template=heat_template.to_dict()) LOG.debug('Created stack with id: %s', body['stack']['id']) return body['stack']['id'] except Exception: LOG.error("use heat to create stack failed") raise class SyncRestoreStatusTask(task.Task): def execute(self, stack_id, heat_client, restore): if stack_id is None: LOG.info('Not syncing Heat stack status, stack is empty') return LOG.info('Syncing Heat stack status, stack_id: %s', stack_id) self._restore = restore sync_status_loop = loopingcall.FixedIntervalLoopingCall( self._sync_status, heat_client, stack_id) sync_status_loop.start(interval=CONF.sync_status_interval) sync_status_loop.wait() def _sync_status(self, heat_client, stack_id): try: stack = heat_client.stacks.get(stack_id) except Exception: LOG.debug('Heat error getting stack, stack_id: %s', stack_id) raise stack_status = getattr(stack, 'stack_status') if stack_status == 'CREATE_IN_PROGRESS': LOG.debug('Heat stack status: in progress, stack_id: %s', stack_id) elif stack_status == 'CREATE_COMPLETE': LOG.info('Heat stack status: complete, stack_id: %s', stack_id) self._update_resource_status(heat_client, stack_id) raise loopingcall.LoopingCallDone() else: LOG.info('Heat stack status: failure, stack_id: %s', stack_id) self._update_resource_status(heat_client, stack_id) raise def _update_resource_status(self, heat_client, stack_id): LOG.debug('Updating resources status from heat stack (stack_id: %s)', stack_id) try: resources = heat_client.resources.list(stack_id) for resource in resources: heat_to_karbor_map = { 'CREATE_COMPLETE': constants.RESOURCE_STATUS_AVAILABLE, 'CREATE_IN_PROGRESS': constants.RESOURCE_STATUS_RESTORING, 'CREATE_FAILED': constants.RESOURCE_STATUS_ERROR, } reason = resource.resource_status_reason if ( resource.resource_status == 'CREATE_FAILED' ) else None self._restore.update_resource_status( resource.resource_type, resource.physical_resource_id, heat_to_karbor_map[resource.resource_status], reason, ) self._restore.save() except Exception as e: LOG.warning('Unable to update resources status from heat stack. ' 'Reason: %s', e) def get_flow(context, workflow_engine, checkpoint, provider, restore, restore_auth): target = restore.get('restore_target', None) heat_conf = {} if target is not None: heat_conf["auth_url"] = target if restore_auth is not None: auth_type = restore_auth.get("type", None) if auth_type == "password": heat_conf["username"] = restore_auth["username"] heat_conf["password"] = restore_auth["password"] resource_graph = checkpoint.resource_graph parameters = restore.parameters flow_name = "Restore_" + checkpoint.id restore_flow = workflow_engine.build_flow(flow_name, 'linear') plugins = provider.load_plugins() resources_task_flow = resource_flow.build_resource_flow( operation_type=constants.OPERATION_RESTORE, context=context, workflow_engine=workflow_engine, resource_graph=resource_graph, plugins=plugins, parameters=parameters ) workflow_engine.add_tasks( restore_flow, InitiateRestoreTask(), CreateHeatTask(inject={'context': context, 'heat_conf': heat_conf}), resources_task_flow, CreateStackTask(), SyncRestoreStatusTask(), CompleteRestoreTask() ) flow_engine = workflow_engine.get_engine(restore_flow, store={'checkpoint': checkpoint, 'restore': restore}) return flow_engine