Selaa lähdekoodia

Merged in aznashwan/coriolis-core/validation-task (pull request #129)

Prepend input validation task to replica/migration executions.

Approved-by: Nashwan Azhari <nazhari@cloudbasesolutions.com>
Nashwan Azhari 7 vuotta sitten
vanhempi
sitoutus
1948959f36

+ 20 - 4
coriolis/conductor/rpc/server.py

@@ -237,9 +237,13 @@ class ConductorServerEndpoint(object):
         execution.action = replica
 
         for instance in execution.action.instances:
+            validate_replica_inputs_task = self._create_task(
+                instance, constants.TASK_TYPE_VALIDATE_REPLICA_INPUTS,
+                execution)
+
             get_instance_info_task = self._create_task(
                 instance, constants.TASK_TYPE_GET_INSTANCE_INFO,
-                execution)
+                execution, depends_on=[validate_replica_inputs_task.id])
 
             depends_on = [get_instance_info_task.id]
             if shutdown_instances:
@@ -496,13 +500,20 @@ class ConductorServerEndpoint(object):
         execution.number = 1
 
         for instance in instances:
-            create_snapshot_task_depends_on = []
+            validate_replica_desployment_inputs_task = self._create_task(
+                instance,
+                constants.TASK_TYPE_VALIDATE_REPLICA_DEPLOYMENT_INPUTS,
+                execution)
+
+            create_snapshot_task_depends_on = [
+                validate_replica_desployment_inputs_task.id]
 
             if (constants.PROVIDER_TYPE_INSTANCE_FLAVOR in
                     destination_provider_types):
                 get_optimal_flavor_task = self._create_task(
                     instance, constants.TASK_TYPE_GET_OPTIMAL_FLAVOR,
-                    execution)
+                    execution, depends_on=[
+                        validate_replica_desployment_inputs_task.id])
                 create_snapshot_task_depends_on.append(
                     get_optimal_flavor_task.id)
 
@@ -586,8 +597,13 @@ class ConductorServerEndpoint(object):
         migration.notes = notes
 
         for instance in instances:
+            task_validate = self._create_task(
+                instance, constants.TASK_TYPE_VALIDATE_MIGRATION_INPUTS,
+                execution)
+
             task_export = self._create_task(
-                instance, constants.TASK_TYPE_EXPORT_INSTANCE, execution)
+                instance, constants.TASK_TYPE_EXPORT_INSTANCE, execution,
+                depends_on=[task_validate.id])
 
             if (constants.PROVIDER_TYPE_INSTANCE_FLAVOR in
                     destination_provider_types):

+ 8 - 0
coriolis/constants.py

@@ -45,6 +45,10 @@ TASK_TYPE_CREATE_REPLICA_DISK_SNAPSHOTS = "CREATE_REPLICA_DISK_SNAPSHOTS"
 TASK_TYPE_DELETE_REPLICA_DISK_SNAPSHOTS = "DELETE_REPLICA_DISK_SNAPSHOTS"
 TASK_TYPE_RESTORE_REPLICA_DISK_SNAPSHOTS = "RESTORE_REPLICA_DISK_SNAPSHOTS"
 TASK_TYPE_GET_OPTIMAL_FLAVOR = "GET_OPTIMAL_FLAVOR"
+TASK_TYPE_VALIDATE_MIGRATION_INPUTS = "VALIDATE_MIGRATION_INPUTS"
+TASK_TYPE_VALIDATE_REPLICA_INPUTS = "VALIDATE_REPLICA_INPUTS"
+TASK_TYPE_VALIDATE_REPLICA_DEPLOYMENT_INPUTS = (
+    "VALIDATE_REPLICA_DEPLOYMENT_INPUTS")
 
 PROVIDER_TYPE_IMPORT = 1
 PROVIDER_TYPE_EXPORT = 2
@@ -57,6 +61,10 @@ PROVIDER_TYPE_ENDPOINT_NETWORKS = 128
 PROVIDER_TYPE_INSTANCE_FLAVOR = 256
 PROVIDER_TYPE_ENDPOINT_OPTIONS = 512
 PROVIDER_TYPE_SETUP_LIBS = 1024
+PROVIDER_TYPE_VALIDATE_MIGRATION_EXPORT = 2048
+PROVIDER_TYPE_VALIDATE_REPLICA_EXPORT = 4096
+PROVIDER_TYPE_VALIDATE_MIGRATION_IMPORT = 8192
+PROVIDER_TYPE_VALIDATE_REPLICA_IMPORT = 16384
 
 DISK_FORMAT_VMDK = 'vmdk'
 DISK_FORMAT_RAW = 'raw'

+ 54 - 0
coriolis/providers/base.py

@@ -157,6 +157,60 @@ class BaseImportInstanceProvider(BaseInstanceProvider):
         pass
 
 
+class BaseMigrationExportValidationProvider(
+        object, with_metaclass(abc.ABCMeta)):
+    """ Defines methods to be called for migration export input validation """
+
+    @abc.abstractmethod
+    def validate_migration_export_input(
+            self, ctxt, connection_info, instance_name, source_environment):
+        """ Should verify the provided 'connection_info' and
+        'source_environment' and return the expected Migration
+        export info for the given VM. """
+        return {}
+
+
+class BaseReplicaExportValidationProvider(
+        object, with_metaclass(abc.ABCMeta)):
+    """ Defines methods to be called for replica export input validation """
+
+    @abc.abstractmethod
+    def validate_replica_export_input(
+            self, ctxt, connection_info, instance_name, source_environment):
+        """ Should verify the provided 'connection_info' and
+        'source_environment' and return the expected Migration
+        export info for the given VM. """
+        return {}
+
+
+class BaseMigrationImportValidationProvider(
+        object, with_metaclass(abc.ABCMeta)):
+    """ Defines methods to be called for migration import input validation """
+
+    @abc.abstractmethod
+    def validate_migration_import_input(
+            self, ctxt, connection_info, target_environment, export_info):
+        """ Validates the provided Migration parameters """
+        pass
+
+
+class BaseReplicaImportValidationProvider(
+        object, with_metaclass(abc.ABCMeta)):
+    """ Defines methods to be called for replica import input validation """
+
+    @abc.abstractmethod
+    def validate_replica_import_input(
+            self, ctxt, connection_info, target_environment, export_info):
+        """ Validates the provided Replica parameters """
+        pass
+
+    @abc.abstractmethod
+    def validate_replica_deployment_input(
+            self, ctxt, connection_info, target_environment, export_info):
+        """ Validates the provided Replica deployment parameters """
+        pass
+
+
 class BaseImportProvider(BaseImportInstanceProvider):
 
     @abc.abstractmethod

+ 9 - 1
coriolis/providers/factory.py

@@ -31,7 +31,15 @@ PROVIDER_TYPE_MAP = {
         base.BaseEndpointNetworksProvider,
     constants.PROVIDER_TYPE_OS_MORPHING: base.BaseImportInstanceProvider,
     constants.PROVIDER_TYPE_INSTANCE_FLAVOR: base.BaseInstanceFlavorProvider,
-    constants.PROVIDER_TYPE_SETUP_LIBS: base.BaseProviderSetupExtraLibsMixin
+    constants.PROVIDER_TYPE_SETUP_LIBS: base.BaseProviderSetupExtraLibsMixin,
+    constants.PROVIDER_TYPE_VALIDATE_MIGRATION_EXPORT: (
+        base.BaseMigrationExportValidationProvider),
+    constants.PROVIDER_TYPE_VALIDATE_REPLICA_EXPORT: (
+        base.BaseReplicaExportValidationProvider),
+    constants.PROVIDER_TYPE_VALIDATE_MIGRATION_IMPORT: (
+        base.BaseMigrationImportValidationProvider),
+    constants.PROVIDER_TYPE_VALIDATE_REPLICA_IMPORT: (
+        base.BaseReplicaImportValidationProvider)
 }
 
 

+ 6 - 0
coriolis/tasks/factory.py

@@ -24,6 +24,8 @@ _TASKS_MAP = {
         migration_tasks.CleanupFailedImportInstanceTask,
     constants.TASK_TYPE_GET_OPTIMAL_FLAVOR:
         migration_tasks.GetOptimalFlavorTask,
+    constants.TASK_TYPE_VALIDATE_MIGRATION_INPUTS:
+        migration_tasks.ValidateMigrationParametersTask,
     constants.TASK_TYPE_DEPLOY_OS_MORPHING_RESOURCES:
         osmorphing_tasks.DeployOSMorphingResourcesTask,
     constants.TASK_TYPE_OS_MORPHING:
@@ -60,6 +62,10 @@ _TASKS_MAP = {
         replica_tasks.DeleteReplicaDiskSnapshotsTask,
     constants.TASK_TYPE_RESTORE_REPLICA_DISK_SNAPSHOTS:
         replica_tasks.RestoreReplicaDiskSnapshotsTask,
+    constants.TASK_TYPE_VALIDATE_REPLICA_INPUTS:
+        replica_tasks.ValidateReplicaExecutionParametersTask,
+    constants.TASK_TYPE_VALIDATE_REPLICA_DEPLOYMENT_INPUTS:
+        replica_tasks.ValidateReplicaDeploymentParametersTask
 }
 
 

+ 64 - 0
coriolis/tasks/migration_tasks.py

@@ -189,3 +189,67 @@ class GetOptimalFlavorTask(base.TaskRunner):
         task_info["retain_export_path"] = True
 
         return task_info
+
+
+class ValidateMigrationParametersTask(base.TaskRunner):
+    def run(self, ctxt, instance, origin, destination, task_info,
+            event_handler):
+        event_manager = events.EventManager(event_handler)
+        # validate source params:
+        origin_type = origin["type"]
+        origin_connection_info = base.get_connection_info(ctxt, origin)
+        destination_connection_info = base.get_connection_info(
+            ctxt, destination)
+        destination_type = destination["type"]
+        source_provider = providers_factory.get_provider(
+            origin_type, constants.PROVIDER_TYPE_VALIDATE_MIGRATION_EXPORT,
+            event_handler, raise_if_not_found=False)
+        export_info = None
+        if source_provider:
+            export_info = source_provider.validate_migration_export_input(
+                ctxt, base.get_connection_info(ctxt, origin), instance,
+                source_environment=origin.get("source_environment", {}))
+        else:
+            event_manager.progress_update(
+                "Migration Export Provider for platform '%s' does not support "
+                "Migration input validation" % origin_type)
+
+        if export_info is None:
+            source_endpoint_provider = providers_factory.get_provider(
+                origin_type, constants.PROVIDER_TYPE_ENDPOINT_INSTANCES,
+                event_handler, raise_if_not_found=False)
+            if not source_endpoint_provider:
+                event_manager.progress_update(
+                    "Migration Export Provider for platform '%s' does not "
+                    "support querying instance export info. Cannot perform "
+                    "Migration Import validation for destination platform "
+                    "'%s'" % (origin_type, destination_type))
+                return task_info
+            export_info = source_endpoint_provider.get_instance(
+                ctxt, origin_connection_info, instance)
+
+        # validate Export info:
+        schemas.validate_value(
+            export_info, schemas.CORIOLIS_VM_EXPORT_INFO_SCHEMA)
+        # NOTE: this export info will get overriden with updated values
+        # and disk paths after the ExportInstanceTask.
+        task_info["export_info"] = export_info
+
+        # validate destination params:
+        destination_provider = providers_factory.get_provider(
+            destination_type,
+            constants.PROVIDER_TYPE_VALIDATE_MIGRATION_IMPORT, event_handler,
+            raise_if_not_found=False)
+        if not destination_provider:
+            event_manager.progress_update(
+                "Migration Import Provider for platform '%s' does not support "
+                "Migration input validation" % destination_type)
+            return task_info
+
+        # NOTE: the target environment JSON schema should have been validated
+        # upon accepting the Migration API creation request.
+        target_environment = destination.get("target_environment", {})
+        destination_provider.validate_migration_import_input(
+            ctxt, destination_connection_info, target_environment, export_info)
+
+        return task_info

+ 98 - 0
coriolis/tasks/replica_tasks.py

@@ -4,6 +4,7 @@
 from oslo_log import log as logging
 
 from coriolis import constants
+from coriolis import events
 from coriolis import exception
 from coriolis.providers import factory as providers_factory
 from coriolis import schemas
@@ -327,3 +328,100 @@ class RestoreReplicaDiskSnapshotsTask(base.TaskRunner):
         task_info["volumes_info"] = volumes_info
 
         return task_info
+
+
+class ValidateReplicaExecutionParametersTask(base.TaskRunner):
+    def run(self, ctxt, instance, origin, destination, task_info,
+            event_handler):
+        event_manager = events.EventManager(event_handler)
+        # validate source params:
+        origin_type = origin["type"]
+        origin_connection_info = base.get_connection_info(ctxt, origin)
+        destination_connection_info = base.get_connection_info(
+            ctxt, destination)
+        destination_type = destination["type"]
+        source_provider = providers_factory.get_provider(
+            origin_type, constants.PROVIDER_TYPE_VALIDATE_REPLICA_EXPORT,
+            event_handler, raise_if_not_found=False)
+        export_info = None
+        if source_provider:
+            export_info = source_provider.validate_replica_export_input(
+                ctxt, base.get_connection_info(ctxt, origin), instance,
+                source_environment=origin.get("source_environment", {}))
+        else:
+            event_manager.progress_update(
+                "Replica Export Provider for platform '%s' does not support "
+                "Replica input validation" % origin_type)
+
+        if export_info is None:
+            source_endpoint_provider = providers_factory.get_provider(
+                origin_type, constants.PROVIDER_TYPE_ENDPOINT_INSTANCES,
+                event_handler, raise_if_not_found=False)
+            if not source_endpoint_provider:
+                event_manager.progress_update(
+                    "Replica Export Provider for platform '%s' does not "
+                    "support querying instance export info. Cannot perform "
+                    "Replica Import validation for destination platform "
+                    "'%s'" % (origin_type, destination_type))
+                return task_info
+            export_info = source_endpoint_provider.get_instance(
+                ctxt, origin_connection_info, instance)
+
+        # validate Export info:
+        schemas.validate_value(
+            export_info, schemas.CORIOLIS_VM_EXPORT_INFO_SCHEMA)
+        # NOTE: this export info will get overriden with updated values
+        # and disk paths after the ExportInstanceTask.
+        task_info["export_info"] = export_info
+
+        # validate destination params:
+        destination_provider = providers_factory.get_provider(
+            destination_type,
+            constants.PROVIDER_TYPE_VALIDATE_REPLICA_IMPORT, event_handler,
+            raise_if_not_found=False)
+        if not destination_provider:
+            event_manager.progress_update(
+                "Replica Import Provider for platform '%s' does not support "
+                "Replica input validation" % destination_type)
+            return task_info
+
+        # NOTE: the target environment JSON schema should have been validated
+        # upon accepting the Replica API creation request.
+        target_environment = destination.get("target_environment", {})
+        destination_provider.validate_replica_import_input(
+            ctxt, destination_connection_info, target_environment, export_info)
+
+        return task_info
+
+
+class ValidateReplicaDeploymentParametersTask(base.TaskRunner):
+    def run(self, ctxt, instance, origin, destination, task_info,
+            event_handler):
+        event_manager = events.EventManager(event_handler)
+        destination_connection_info = base.get_connection_info(
+            ctxt, destination)
+        destination_type = destination["type"]
+        export_info = task_info["export_info"]
+        # validate Export info:
+        schemas.validate_value(
+            export_info, schemas.CORIOLIS_VM_EXPORT_INFO_SCHEMA)
+
+        # validate destination params:
+        destination_provider = providers_factory.get_provider(
+            destination_type,
+            constants.PROVIDER_TYPE_VALIDATE_REPLICA_IMPORT, event_handler,
+            raise_if_not_found=False)
+        if not destination_provider:
+            event_manager.progress_update(
+                "Replica Deployment Provider for platform '%s' does not "
+                "support Replica Deployment input validation" % (
+                    destination_type))
+            return task_info
+
+        # NOTE: the target environment JSON schema should have been validated
+        # upon accepting the Replica API creation request.
+        target_environment = destination.get("target_environment", {})
+        destination_provider.validate_replica_deployment_input(
+            ctxt, destination_connection_info, target_environment, export_info)
+
+        return task_info