Просмотр исходного кода

Merge pull request #108 from gabriel-samfira/show-deleted

Show deleted migrations/replicas
Nashwan Azhari 6 лет назад
Родитель
Сommit
616f16cae8

+ 7 - 0
coriolis/api/v1/migrations.py

@@ -1,5 +1,6 @@
 # Copyright 2016 Cloudbase Solutions Srl
 # All Rights Reserved.
+import json
 
 from oslo_log import log as logging
 from webob import exc
@@ -31,14 +32,20 @@ class MigrationController(api_wsgi.Controller):
         return migration_view.single(req, migration)
 
     def index(self, req):
+        show_deleted = api_utils._get_show_deleted(
+            req.GET.get("show_deleted", None))
         context = req.environ["coriolis.context"]
+        context.show_deleted = show_deleted
         context.can(migration_policies.get_migrations_policy_label("show"))
         return migration_view.collection(
             req, self._migration_api.get_migrations(
                 context, include_tasks=False))
 
     def detail(self, req):
+        show_deleted = api_utils._get_show_deleted(
+            req.GET.get("show_deleted", None))
         context = req.environ["coriolis.context"]
+        context.show_deleted = show_deleted
         context.can(
             migration_policies.get_migrations_policy_label("show_execution"))
         return migration_view.collection(

+ 6 - 0
coriolis/api/v1/replicas.py

@@ -32,14 +32,20 @@ class ReplicaController(api_wsgi.Controller):
         return replica_view.single(req, replica)
 
     def index(self, req):
+        show_deleted = api_utils._get_show_deleted(
+            req.GET.get("show_deleted", None))
         context = req.environ["coriolis.context"]
+        context.show_deleted = show_deleted
         context.can(replica_policies.get_replicas_policy_label("list"))
         return replica_view.collection(
             req, self._replica_api.get_replicas(
                 context, include_tasks_executions=False))
 
     def detail(self, req):
+        show_deleted = api_utils._get_show_deleted(
+            req.GET.get("show_deleted", None))
         context = req.environ["coriolis.context"]
+        context.show_deleted = show_deleted
         context.can(
             replica_policies.get_replicas_policy_label("show_executions"))
         return replica_view.collection(

+ 15 - 0
coriolis/api/v1/utils.py

@@ -1,6 +1,7 @@
 # Copyright 2018 Cloudbase Solutions Srl
 # All Rights Reserved.
 
+import json
 
 from oslo_log import log as logging
 from webob import exc
@@ -12,6 +13,20 @@ from coriolis import schemas
 LOG = logging.getLogger(__name__)
 
 
+def _get_show_deleted(val):
+    if val is None:
+        return val
+    try:
+        show_deleted = json.loads(val)
+        if type(show_deleted) is bool:
+            return show_deleted
+    except Exception as err:
+        LOG.warn(
+            "failed to parse show_deleted: %s" % err)
+        pass
+    return None
+
+
 def validate_network_map(network_map):
     """ Validates the JSON schema for the network_map. """
     try:

+ 8 - 6
coriolis/api/v1/views/migration_view.py

@@ -5,7 +5,7 @@ import itertools
 
 from oslo_config import cfg as conf
 
-from coriolis.api.v1.views import replica_tasks_execution_view
+from coriolis.api.v1.views import replica_tasks_execution_view as view
 
 
 MIGRATIONS_API_OPTS = [
@@ -27,10 +27,14 @@ def _format_migration(req, migration, keys=None):
     migration_dict = dict(itertools.chain.from_iterable(
         transform(k, v) for k, v in migration.items()))
 
-    execution = replica_tasks_execution_view.format_replica_tasks_execution(
-        req, migration_dict["executions"][0])
+    if len(migration_dict.get("executions", [])):
+        execution = view.format_replica_tasks_execution(
+            req, migration_dict["executions"][0])
+        del migration_dict["executions"]
+    else:
+        execution = {}
 
-    migration_dict["status"] = execution["status"]
+    migration_dict["status"] = execution.get("status")
     tasks = execution.get("tasks")
     if tasks:
         migration_dict["tasks"] = tasks
@@ -38,8 +42,6 @@ def _format_migration(req, migration, keys=None):
     if not CONF.include_task_info_in_migrations_api and (
             "info" in migration_dict):
         migration_dict.pop("info")
-
-    del migration_dict["executions"]
     return migration_dict
 
 

+ 4 - 2
coriolis/conductor/rpc/client.py

@@ -183,9 +183,11 @@ class ConductorClient(object):
         return self._client.call(
             ctxt, 'delete_replica_disks', replica_id=replica_id)
 
-    def get_migrations(self, ctxt, include_tasks=False):
+    def get_migrations(self, ctxt, include_tasks=False,
+                       include_info=False):
         return self._client.call(ctxt, 'get_migrations',
-                                 include_tasks=include_tasks)
+                                 include_tasks=include_tasks,
+                                 include_info=include_info)
 
     def get_migration(self, ctxt, migration_id):
         return self._client.call(

+ 5 - 2
coriolis/conductor/rpc/server.py

@@ -536,8 +536,11 @@ class ConductorServerEndpoint(object):
             raise exception.NotFound("Replica not found")
         return replica
 
-    def get_migrations(self, ctxt, include_tasks):
-        return db_api.get_migrations(ctxt, include_tasks)
+    def get_migrations(self, ctxt, include_tasks,
+                       include_info=False):
+        return db_api.get_migrations(
+            ctxt, include_tasks,
+            include_info=include_info)
 
     @migration_synchronized
     def get_migration(self, ctxt, migration_id):

+ 16 - 5
coriolis/db/api.py

@@ -248,7 +248,7 @@ def delete_replica_schedule(context, replica_id,
     if is_user_context(context):
         if not q.join(models.Replica).filter(
                 models.Replica.project_id == context.tenant).first():
-                raise exception.NotAuthorized()
+            raise exception.NotAuthorized()
     if pre_delete_callable:
         pre_delete_callable(context, schedule)
     count = q.soft_delete()
@@ -276,15 +276,20 @@ def _get_replica_with_tasks_executions_options(q):
 
 
 @enginefacade.reader
-def get_replicas(context, include_tasks_executions=False):
+def get_replicas(context,
+                 include_tasks_executions=False,
+                 include_info=False):
     q = _soft_delete_aware_query(context, models.Replica)
     if include_tasks_executions:
         q = _get_replica_with_tasks_executions_options(q)
+    if include_info is False:
+        q = q.options(orm.defer('info'))
     q = q.filter()
     if is_user_context(context):
         q = q.filter(
             models.Replica.project_id == context.tenant)
-    return q.all()
+    db_result = q.all()
+    return [i.to_dict(include_info=include_info) for i in db_result]
 
 
 @enginefacade.reader
@@ -350,16 +355,22 @@ def get_replica_migrations(context, replica_id):
 
 
 @enginefacade.reader
-def get_migrations(context, include_tasks=False):
+def get_migrations(context, include_tasks=False,
+                   include_info=False):
     q = _soft_delete_aware_query(context, models.Migration)
     if include_tasks:
         q = _get_migration_task_query_options(q)
     else:
         q = q.options(orm.joinedload("executions"))
+    if include_info is False:
+        q = q.options(orm.defer('info'))
+
     args = {}
     if is_user_context(context):
         args["project_id"] = context.tenant
-    return q.filter_by(**args).all()
+    result = q.filter_by(**args).all()
+    to_dict = [i.to_dict(include_info=include_info) for i in result]
+    return to_dict
 
 
 def _get_tasks_with_details_options(query):

+ 118 - 0
coriolis/db/sqlalchemy/models.py

@@ -26,6 +26,19 @@ class TaskEvent(BASE, models.TimestampMixin, models.SoftDeleteMixin,
     level = sqlalchemy.Column(sqlalchemy.String(20), nullable=False)
     message = sqlalchemy.Column(sqlalchemy.String(1024), nullable=False)
 
+    def to_dict(self):
+        result = {
+            "id": self.id,
+            "task_id": self.task_id,
+            "level": self.level,
+            "message": self.message,
+            "created_at": self.created_at,
+            "updated_at": self.updated_at,
+            "deleted_at": self.deleted_at,
+            "deleted": self.deleted,
+        }
+        return result
+
 
 class TaskProgressUpdate(BASE, models.TimestampMixin, models.SoftDeleteMixin,
                          models.ModelBase):
@@ -41,6 +54,20 @@ class TaskProgressUpdate(BASE, models.TimestampMixin, models.SoftDeleteMixin,
     total_steps = sqlalchemy.Column(sqlalchemy.Integer, nullable=True)
     message = sqlalchemy.Column(sqlalchemy.String(1024), nullable=True)
 
+    def to_dict(self):
+        result = {
+            "id": self.id,
+            "task_id": self.task_id,
+            "current_step": self.current_step,
+            "total_steps": self.total_steps,
+            "message": self.message,
+            "created_at": self.created_at,
+            "updated_at": self.updated_at,
+            "deleted_at": self.deleted_at,
+            "deleted": self.deleted,
+        }
+        return result
+
 
 class Task(BASE, models.TimestampMixin, models.SoftDeleteMixin,
            models.ModelBase):
@@ -69,6 +96,35 @@ class Task(BASE, models.TimestampMixin, models.SoftDeleteMixin,
                                         cascade="all,delete",
                                         backref=orm.backref('task'))
 
+    def to_dict(self):
+        result = {
+            "id": self.id,
+            "execution_id": self.execution_id,
+            "instance": self.instance,
+            "host": self.host,
+            "process_id": self.process_id,
+            "status": self.status,
+            "task_type": self.task_type,
+            "exception_details": self.exception_details,
+            "depends_on": self.depends_on,
+            "index": self.index,
+            "on_error": self.on_error,
+            "events": [],
+            "progress_updates": [],
+            "created_at": self.created_at,
+            "updated_at": self.updated_at,
+            "deleted_at": self.deleted_at,
+            "deleted": self.deleted,
+        }
+
+        for evt in self.events:
+            result["events"].append(evt.to_dict())
+
+        for pgu in self.progress_updates:
+            result["progress_updates"].append(
+                pgu.to_dict())
+        return result
+
 
 class TasksExecution(BASE, models.TimestampMixin, models.ModelBase,
                      models.SoftDeleteMixin):
@@ -87,6 +143,23 @@ class TasksExecution(BASE, models.TimestampMixin, models.ModelBase,
     number = sqlalchemy.Column(sqlalchemy.Integer, nullable=False)
     type = sqlalchemy.Column(sqlalchemy.String(20))
 
+    def to_dict(self):
+        result = {
+            "id": self.id,
+            "action_id": self.action_id,
+            "tasks": [],
+            "status": self.status,
+            "number": self.number,
+            "type": self.type,
+            "created_at": self.created_at,
+            "updated_at": self.updated_at,
+            "deleted_at": self.deleted_at,
+            "deleted": self.deleted,
+        }
+        for tsk in self.tasks:
+            result["tasks"].append(tsk.to_dict())
+        return result
+
 
 class BaseTransferAction(BASE, models.TimestampMixin, models.ModelBase,
                          models.SoftDeleteMixin):
@@ -124,6 +197,34 @@ class BaseTransferAction(BASE, models.TimestampMixin, models.ModelBase,
         'polymorphic_on': type,
     }
 
+    def to_dict(self, include_info=True):
+        result = {
+            "base_id": self.base_id,
+            "user_id": self.user_id,
+            "project_id": self.project_id,
+            "destination_environment": self.destination_environment,
+            "type": self.type,
+            "executions": [],
+            "instances": self.instances,
+            "reservation_id": self.reservation_id,
+            "notes": self.notes,
+            "origin_endpoint_id": self.origin_endpoint_id,
+            "destination_endpoint_id": self.destination_endpoint_id,
+            "transfer_result": self.transfer_result,
+            "network_map": self.network_map,
+            "storage_mappings": self.storage_mappings,
+            "source_environment": self.source_environment,
+            "created_at": self.created_at,
+            "updated_at": self.updated_at,
+            "deleted_at": self.deleted_at,
+            "deleted": self.deleted,
+        }
+        for ex in self.executions:
+            result["executions"].append(ex.to_dict())
+        if include_info:
+            result["info"] = self.info
+        return result
+
 
 class Replica(BaseTransferAction):
     __tablename__ = 'replica'
@@ -137,6 +238,12 @@ class Replica(BaseTransferAction):
         'polymorphic_identity': 'replica',
     }
 
+    def to_dict(self, include_info=True):
+        base = super(Replica, self).to_dict(
+            include_info=include_info)
+        base.update({"id": self.id})
+        return base
+
 
 class Migration(BaseTransferAction):
     __tablename__ = 'migration'
@@ -159,6 +266,17 @@ class Migration(BaseTransferAction):
         'polymorphic_identity': 'migration',
     }
 
+    def to_dict(self, include_info=True):
+        base = super(Migration, self).to_dict(
+            include_info=include_info)
+        base.update({
+            "id": self.id,
+            "replica_id": self.replica_id,
+            "shutdown_instances": self.shutdown_instances,
+            "replication_count": self.replication_count,
+        })
+        return base
+
 
 class Endpoint(BASE, models.TimestampMixin, models.ModelBase,
                models.SoftDeleteMixin):