# Copyright 2014 # The Cloudscaling Group, Inc. # # 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 uuid from oslo_utils import timeutils from gceapi.api import base_api from gceapi.api import scopes from gceapi import exception from gceapi.i18n import _ class API(base_api.API): """GCE operation API.""" KIND = "operation" PERSISTENT_ATTRIBUTES = ["id", "insert_time", "start_time", "end_time", "name", "type", "user", "status", "progress", "scope_type", "scope_name", "target_type", "target_name", "method_key", "item_id", "error_code", "error_message", "errors"] def __init__(self, *args, **kwargs): super(API, self).__init__(*args, **kwargs) method = base_api.API._get_complex_operation_progress self._method_keys = {method: "complex_operation"} self._get_progress_methods = {"complex_operation": method} def _get_type(self): return self.KIND def _get_persistent_attributes(self): return self.PERSISTENT_ATTRIBUTES def register_get_progress_method(self, method_key, method): if method_key in self._get_progress_methods: raise exception.Invalid() # TODO(ft): check 'method' formal arguments self._method_keys[method] = method_key self._get_progress_methods[method_key] = method def get_scopes(self, context, item): return [scopes.construct(item["scope_type"], item["scope_name"])] def get_item(self, context, name, scope=None): operation = self._get_db_item_by_name(context, name) if (operation is None or operation["scope_type"] != scope.get_type() or operation["scope_name"] != scope.get_name()): raise exception.NotFound operation = self._update_operation_progress(context, operation) return operation def get_items(self, context, scope=None): operations = self._get_db_items(context) if scope is not None: operations = [operation for operation in operations if (operation["scope_type"] == scope.get_type() and operation["scope_name"] == scope.get_name())] for operation in operations: operation = self._update_operation_progress(context, operation) return operations def delete_item(self, context, name, scope=None): # NOTE(ft): Google deletes operation with no check it's scope item = self._get_db_item_by_name(context, name) if item is None: raise exception.NotFound self._delete_db_item(context, item) # TODO(apavlov): rework updating end_time field # now it updates only by user request that may occurs # after a long period of time def _update_operation_progress(self, context, operation): if operation["status"] == "DONE" or not operation.get("item_id"): return operation method_key = operation["method_key"] get_progress = self._get_progress_methods[method_key] operation_progress = get_progress(context, operation["item_id"]) if operation_progress is None: return operation operation.update(operation_progress) if operation["progress"] == 100: operation["status"] = "DONE" operation["end_time"] = timeutils.isotime(None, True) self._update_db_item(context, operation) return operation def construct_operation(self, context, op_type, target_type, target_name, scope): operation_id = str(uuid.uuid4()) operation = { "id": operation_id, "name": "operation-" + operation_id, "insert_time": timeutils.isotime(context.timestamp, True), "user": context.user_name, "type": op_type, "target_type": target_type, "target_name": target_name, "scope_type": scope.get_type(), "scope_name": scope.get_name(), } return operation def save_operation(self, context, operation, start_time, get_progress_method, item_id, operation_result): if isinstance(operation_result, Exception): operation.update(self._error_from_exception(operation_result)) operation["start_time"] = start_time method_key = self._method_keys.get(get_progress_method) if method_key is None or "error_code" in operation: operation["progress"] = 100 operation["status"] = "DONE" operation["end_time"] = timeutils.isotime(None, True) else: operation["progress"] = 0 operation["status"] = "RUNNING" operation["method_key"] = method_key if item_id is not None: operation["item_id"] = item_id return self._add_db_item(context, operation) def update_operation(self, context, operation_id, operation_result): operation = self._get_db_item_by_id(context, operation_id) if operation is None: # NOTE(ft): it may lead to hungup not finished operation in DB return if isinstance(operation_result, Exception): operation.update(self._error_from_exception(operation_result)) elif operation_result: operation.update(operation_result) if operation["progress"] == 100 or "error_code" in operation: operation["status"] = "DONE" operation["end_time"] = timeutils.isotime(None, True) self._update_db_item(context, operation) def _error_from_exception(self, ex): return { "errors": [{"code": ex.__class__.__name__, "message": str(ex)}], "error_code": 500, "error_message": _('Internal server error')}