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

Add minion pool options for trasnfer actions.

Nashwan Azhari 5 лет назад
Родитель
Сommit
d0c11f6004

+ 19 - 4
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
@@ -56,6 +57,11 @@ class MigrationController(api_wsgi.Controller):
         try:
             origin_endpoint_id = migration["origin_endpoint_id"]
             destination_endpoint_id = migration["destination_endpoint_id"]
+            origin_minion_pool_id = migration.get('origin_minion_pool_id')
+            destination_minion_pool_id = migration.get(
+                'destination_minion_pool_id')
+            instance_osmorphing_minion_pool_mappings = migration.get(
+                'instance_osmorphing_minion_pool_mappings')
             destination_environment = migration.get(
                 "destination_environment", {})
             instances = migration["instances"]
@@ -92,8 +98,10 @@ class MigrationController(api_wsgi.Controller):
             destination_environment['storage_mappings'] = storage_mappings
 
             return (origin_endpoint_id, destination_endpoint_id,
-                    source_environment, destination_environment, instances,
-                    notes, skip_os_morphing, replication_count,
+                    origin_minion_pool_id, destination_minion_pool_id,
+                    instance_osmorphing_minion_pool_mappings, source_environment,
+                    destination_environment, instances, notes,
+                    skip_os_morphing, replication_count,
                     shutdown_instances, network_map, storage_mappings)
         except Exception as ex:
             LOG.exception(ex)
@@ -111,15 +119,20 @@ class MigrationController(api_wsgi.Controller):
             clone_disks = migration_body.get("clone_disks", True)
             force = migration_body.get("force", False)
             skip_os_morphing = migration_body.get("skip_os_morphing", False)
+            instance_osmorphing_minion_pool_mappings = migration_body.get(
+                'instance_osmorphing_minion_pool_mappings')
 
             # NOTE: destination environment for replica should have been
             # validated upon its creation.
             migration = self._migration_api.deploy_replica_instances(
-                context, replica_id, clone_disks, force, skip_os_morphing,
-                user_scripts=user_scripts)
+                context, replica_id, instance_osmorphing_minion_pool_mappings, clone_disks,
+                force, skip_os_morphing, user_scripts=user_scripts)
         else:
             (origin_endpoint_id,
              destination_endpoint_id,
+             origin_minion_pool_id,
+             destination_minion_pool_id,
+             instance_osmorphing_minion_pool_mappings,
              source_environment,
              destination_environment,
              instances,
@@ -132,6 +145,8 @@ class MigrationController(api_wsgi.Controller):
                  context, migration_body)
             migration = self._migration_api.migrate_instances(
                 context, origin_endpoint_id, destination_endpoint_id,
+                origin_minion_pool_id, destination_minion_pool_id,
+                instance_osmorphing_minion_pool_mappings,
                 source_environment, destination_environment, instances,
                 network_map, storage_mappings, replication_count,
                 shutdown_instances, notes=notes,

+ 3 - 1
coriolis/api/v1/minion_pools.py

@@ -94,7 +94,9 @@ class MinionPoolController(api_wsgi.Controller):
                 minion_pool = self._minion_pool_api.get_minion_pool(
                     context, id)
                 self._endpoints_api.validate_endpoint_minion_pool_options(
-                    context, minion_pool['endpoint_id'],
+                    # TODO(aznashwan): remove endpoint ID fields reduncancy
+                    # once DB models are overhauled:
+                    context, minion_pool['origin_endpoint_id'],
                     vals['environment_options'])
             return vals
         except Exception as ex:

+ 11 - 2
coriolis/api/v1/replicas.py

@@ -71,6 +71,11 @@ class ReplicaController(api_wsgi.Controller):
             api_utils.validate_network_map(network_map)
             destination_environment['network_map'] = network_map
 
+            origin_minion_pool_id = replica.get(
+                'origin_minion_pool_id')
+            destination_minion_pool_id = replica.get(
+                'destination_minion_pool_id')
+
             # NOTE(aznashwan): we validate the destination environment for the
             # import provider before appending the 'storage_mappings' parameter
             # for plugins with strict property name checks which do not yet
@@ -88,7 +93,8 @@ class ReplicaController(api_wsgi.Controller):
 
             return (origin_endpoint_id, destination_endpoint_id,
                     source_environment, destination_environment, instances,
-                    network_map, storage_mappings, notes)
+                    network_map, storage_mappings, notes,
+                    origin_minion_pool_id, destination_minion_pool_id)
         except Exception as ex:
             LOG.exception(ex)
             msg = getattr(ex, "message", str(ex))
@@ -100,10 +106,13 @@ class ReplicaController(api_wsgi.Controller):
 
         (origin_endpoint_id, destination_endpoint_id,
          source_environment, destination_environment, instances, network_map,
-         storage_mappings, notes) = self._validate_create_body(context, body)
+         storage_mappings, notes, origin_minion_pool_id,
+         destination_minion_pool_id) = self._validate_create_body(
+            context, body)
 
         return replica_view.single(req, self._replica_api.create(
             context, origin_endpoint_id, destination_endpoint_id,
+            origin_minion_pool_id, destination_minion_pool_id,
             source_environment, destination_environment, instances,
             network_map, storage_mappings, notes))
 

+ 21 - 6
coriolis/conductor/rpc/client.py

@@ -155,6 +155,8 @@ class ConductorClient(object):
 
     def create_instances_replica(self, ctxt, origin_endpoint_id,
                                  destination_endpoint_id,
+                                 origin_minion_pool_id,
+                                 destination_minion_pool_id,
                                  source_environment, destination_environment,
                                  instances, network_map, storage_mappings,
                                  notes=None):
@@ -162,6 +164,8 @@ class ConductorClient(object):
             ctxt, 'create_instances_replica',
             origin_endpoint_id=origin_endpoint_id,
             destination_endpoint_id=destination_endpoint_id,
+            origin_minion_pool_id=origin_minion_pool_id,
+            destination_minion_pool_id=destination_minion_pool_id,
             destination_environment=destination_environment,
             instances=instances,
             notes=notes,
@@ -197,15 +201,22 @@ class ConductorClient(object):
             ctxt, 'get_migration', migration_id=migration_id)
 
     def migrate_instances(self, ctxt, origin_endpoint_id,
-                          destination_endpoint_id, source_environment,
-                          destination_environment, instances, network_map,
-                          storage_mappings, replication_count,
-                          shutdown_instances=False, notes=None,
-                          skip_os_morphing=False, user_scripts=None):
+                          destination_endpoint_id, origin_minion_pool_id,
+                          destination_minion_pool_id,
+                          instance_osmorphing_minion_pool_mappings,
+                          source_environment, destination_environment,
+                          instances, network_map, storage_mappings,
+                          replication_count, shutdown_instances=False,
+                          notes=None, skip_os_morphing=False,
+                          user_scripts=None):
         return self._client.call(
             ctxt, 'migrate_instances',
             origin_endpoint_id=origin_endpoint_id,
             destination_endpoint_id=destination_endpoint_id,
+            origin_minion_pool_id=origin_minion_pool_id,
+            destination_minion_pool_id=destination_minion_pool_id,
+            instance_osmorphing_minion_pool_mappings=(
+                instance_osmorphing_minion_pool_mappings),
             destination_environment=destination_environment,
             instances=instances,
             notes=notes,
@@ -217,11 +228,15 @@ class ConductorClient(object):
             source_environment=source_environment,
             user_scripts=user_scripts)
 
-    def deploy_replica_instances(self, ctxt, replica_id, clone_disks=False,
+    def deploy_replica_instances(self, ctxt, replica_id,
+                                 instance_osmorphing_minion_pool_mappings,
+                                 clone_disks=False,
                                  force=False, skip_os_morphing=False,
                                  user_scripts=None):
         return self._client.call(
             ctxt, 'deploy_replica_instances', replica_id=replica_id,
+            instance_osmorphing_minion_pool_mappings=(
+                instance_osmorphing_minion_pool_mappings),
             clone_disks=clone_disks, force=force,
             skip_os_morphing=skip_os_morphing,
             user_scripts=user_scripts)

+ 23 - 14
coriolis/conductor/rpc/server.py

@@ -596,6 +596,10 @@ class ConductorServerEndpoint(object):
                             t.status != constants.TASK_STATUS_ON_ERROR_ONLY]:
                         task.status = constants.TASK_STATUS_SCHEDULED
                         break
+            # on_error tasks with no deps are automatically scheduled:
+            else:
+                task.status = constants.TASK_STATUS_SCHEDULED
+
         return task
 
     def _get_task_origin(self, ctxt, action):
@@ -1125,7 +1129,10 @@ class ConductorServerEndpoint(object):
             raise exception.SameDestination()
 
     def create_instances_replica(self, ctxt, origin_endpoint_id,
-                                 destination_endpoint_id, source_environment,
+                                 destination_endpoint_id,
+                                 origin_minion_pool_id,
+                                 destination_minion_pool_id,
+                                 source_environment,
                                  destination_environment, instances,
                                  network_map, storage_mappings, notes=None):
         origin_endpoint = self.get_endpoint(ctxt, origin_endpoint_id)
@@ -1218,8 +1225,11 @@ class ConductorServerEndpoint(object):
         return provider_types["types"]
 
     @replica_synchronized
-    def deploy_replica_instances(self, ctxt, replica_id, clone_disks, force,
-                                 skip_os_morphing=False, user_scripts=None):
+    def deploy_replica_instances(self, ctxt, replica_id,
+                                 instance_osmorphing_minion_pool_mappings,
+                                 clone_disks, force,
+                                 skip_os_morphing=False,
+                                 user_scripts=None):
         replica = self._get_replica(ctxt, replica_id)
         self._check_reservation_for_transfer(replica)
         self._check_replica_running_executions(ctxt, replica)
@@ -1384,11 +1394,13 @@ class ConductorServerEndpoint(object):
         return ret
 
     def migrate_instances(self, ctxt, origin_endpoint_id,
-                          destination_endpoint_id, source_environment,
-                          destination_environment, instances, network_map,
-                          storage_mappings, replication_count,
-                          shutdown_instances=False, notes=None,
-                          skip_os_morphing=False, user_scripts=None):
+                          destination_endpoint_id, origin_minion_pool_id,
+                          destination_minion_pool_id,
+                          instance_osmorphing_minion_pool_mappings,
+                          source_environment, destination_environment,
+                          instances, network_map, storage_mappings,
+                          replication_count, shutdown_instances=False,
+                          notes=None, skip_os_morphing=False, user_scripts=None):
         origin_endpoint = self.get_endpoint(ctxt, origin_endpoint_id)
         destination_endpoint = self.get_endpoint(ctxt, destination_endpoint_id)
         self._check_endpoints(ctxt, origin_endpoint, destination_endpoint)
@@ -2385,6 +2397,8 @@ class ConductorServerEndpoint(object):
                 "minion_connection_info"]
             minion_machine.provider_properties = task_info[
                 "minion_provider_properties"]
+            minion_machine.backup_writer_connection_info = task_info[
+                "minion_backup_writer_connection_info"]
             db_api.add_minion_machine(ctxt, minion_machine)
 
             still_running = _check_other_tasks_running(execution, task)
@@ -3220,15 +3234,10 @@ class ConductorServerEndpoint(object):
                 # which were slower to deploy:
                 "minion_provider_properties": {}}
 
-            validate_minions_option_task = self._create_task(
-                minion_machine_id,
-                constants.TASK_TYPE_VALIDATE_MINION_POOL_OPTIONS,
-                execution)
-
             create_minion_task = self._create_task(
                 minion_machine_id,
                 constants.TASK_TYPE_CREATE_MINION,
-                execution, depends_on=[validate_minions_option_task.id])
+                execution)
 
             self._create_task(
                 minion_machine_id,

+ 2 - 0
coriolis/constants.py

@@ -150,6 +150,8 @@ TASK_TYPE_VALIDATE_SOURCE_MINION_POOL_COMPATIBILITY = (
     "VALIDATE_SOURCE_MINION_POOL_COMPATIBILITY")
 TASK_TYPE_VALIDATE_DESTINATION_MINION_POOL_COMPATIBILITY = (
     "VALIDATE_DESTINATION_MINION_POOL_COMPATIBILITY")
+TASK_TYPE_VALIDATE_OSMORPHING_MINION_POOL_COMPATIBILITY = (
+    "VALIDATE_OSMORPHING_MINION_POOL_COMPATIBILITY")
 
 TASK_PLATFORM_SOURCE = "source"
 TASK_PLATFORM_DESTINATION = "destination"

+ 6 - 3
coriolis/db/api.py

@@ -746,7 +746,8 @@ def update_replica(context, replica_id, updated_values):
 
     updateable_fields = [
         "source_environment", "destination_environment", "notes",
-        "network_map", "storage_mappings"]
+        "network_map", "storage_mappings",
+        "origin_minion_pool_id", "destination_minion_pool_id"]
     for field in updateable_fields:
         if mapped_info_fields.get(field, field) in updated_values:
             LOG.debug(
@@ -1121,7 +1122,9 @@ def update_minion_machine(context, minion_machine_id, updated_values):
         raise exception.NotFound(
             "MinionMachine with ID '%s' does not exist." % minion_machine_id)
 
-    updateable_fields = ["connection_info", "provider_properties", "status"]
+    updateable_fields = [
+        "connection_info", "provider_properties", "status",
+        "backup_writer_connection_info"]
     _update_sqlalchemy_object_fields(
         minion_machine, updateable_fields, updated_values)
 
@@ -1235,7 +1238,7 @@ def update_minion_pool_lifecycle(context, minion_pool_id, updated_values):
     for field in updateable_fields:
         if field in updated_values:
             if field in redundancies:
-                for old_key in redundancies["field"]:
+                for old_key in redundancies[field]:
                     LOG.debug(
                         "Updating the '%s' field of Minion Pool '%s' to: '%s'",
                         old_key, minion_pool_id, updated_values[field])

+ 21 - 2
coriolis/db/sqlalchemy/migrate_repo/versions/016_adds_minion_vm_pools.py

@@ -15,6 +15,19 @@ def upgrade(migrate_engine):
     base_transfer_action = sqlalchemy.Table(
         'base_transfer_action', meta, autoload=True)
 
+    # add the pool option properties for the transfer:
+    origin_minion_pool_id = sqlalchemy.Column(
+        "origin_minion_pool_id", sqlalchemy.String(36), nullable=True)
+    destination_minion_pool_id = sqlalchemy.Column(
+        "destination_minion_pool_id", sqlalchemy.String(36), nullable=True)
+    instance_osmorphing_minion_pool_mappings = sqlalchemy.Column(
+        "instance_osmorphing_minion_pool_mappings", sqlalchemy.Text,
+        nullable=False, default='{}')
+    for col in [
+            origin_minion_pool_id, destination_minion_pool_id,
+            instance_osmorphing_minion_pool_mappings]:
+        base_transfer_action.create_column(col)
+
     # extend tasks execution 'type' column:
     tasks_execution = sqlalchemy.Table(
         'tasks_execution', meta, autoload=True)
@@ -67,12 +80,18 @@ def upgrade(migrate_engine):
             sqlalchemy.Column('deleted', sqlalchemy.String(36)),
             sqlalchemy.Column(
                 'pool_id', sqlalchemy.String(36),
-                sqlalchemy.ForeignKey('minion_pool_lifecycle.id'), nullable=False),
+                sqlalchemy.ForeignKey('minion_pool_lifecycle.id'),
+                nullable=False),
             sqlalchemy.Column(
                 'status', sqlalchemy.String(255), nullable=False,
                 default=lambda: "UNKNOWN"),
             sqlalchemy.Column('connection_info', sqlalchemy.Text),
-            sqlalchemy.Column('provider_properties', sqlalchemy.Text)))
+            sqlalchemy.Column(
+                'backup_writer_connection_info', sqlalchemy.Text,
+                nullable=True),
+            sqlalchemy.Column(
+                'provider_properties', sqlalchemy.Text,
+                nullable=True)))
 
     for index, table in enumerate(tables):
         try:

+ 19 - 2
coriolis/db/sqlalchemy/models.py

@@ -196,6 +196,12 @@ class BaseTransferAction(BASE, models.TimestampMixin, models.ModelBase,
     network_map = sqlalchemy.Column(types.Json, nullable=True)
     storage_mappings = sqlalchemy.Column(types.Json, nullable=True)
     source_environment = sqlalchemy.Column(types.Json, nullable=True)
+    origin_minion_pool_id = sqlalchemy.Column(
+        sqlalchemy.String(36), nullable=True)
+    destination_minion_pool_id = sqlalchemy.Column(
+        sqlalchemy.String(36), nullable=True)
+    instance_osmorphing_minion_pool_mappings = sqlalchemy.Column(
+        types.Json, nullable=False, default=lambda: {})
 
     __mapper_args__ = {
         'polymorphic_identity': 'base_transfer_action',
@@ -224,6 +230,10 @@ class BaseTransferAction(BASE, models.TimestampMixin, models.ModelBase,
             "updated_at": self.updated_at,
             "deleted_at": self.deleted_at,
             "deleted": self.deleted,
+            "origin_minion_pool_id": self.origin_minion_pool_id,
+            "destination_minion_pool_id": self.destination_minion_pool_id,
+            "instance_osmorphing_minion_pool_mappings":
+                self.instance_osmorphing_minion_pool_mappings
         }
         if include_executions:
             for ex in self.executions:
@@ -457,9 +467,14 @@ class MinionMachine(BASE, models.TimestampMixin, models.ModelBase,
         sqlalchemy.String(255), nullable=False,
         default=lambda: constants.MINION_MACHINE_STATUS_UNKNOWN)
 
-    connection_info = sqlalchemy.Column(types.Json)
+    connection_info = sqlalchemy.Column(
+        types.Json, nullable=True)
+
+    backup_writer_connection_info = sqlalchemy.Column(
+        types.Json, nullable=True)
 
-    provider_properties = sqlalchemy.Column(types.Json)
+    provider_properties = sqlalchemy.Column(
+        types.Json, nullable=True)
 
     def to_dict(self):
         result = {
@@ -473,6 +488,8 @@ class MinionMachine(BASE, models.TimestampMixin, models.ModelBase,
             "pool_id": self.pool_id,
             "status": self.status,
             "connection_info": self.connection_info,
+            "backup_writer_connection_info": (
+                self.backup_writer_connection_info),
             "provider_properties": self.provider_properties
         }
         return result

+ 17 - 11
coriolis/migrations/api.py

@@ -9,25 +9,31 @@ class API(object):
         self._rpc_client = rpc_client.ConductorClient()
 
     def migrate_instances(self, ctxt, origin_endpoint_id,
-                          destination_endpoint_id, source_environment,
-                          destination_environment, instances, network_map,
-                          storage_mappings, replication_count,
+                          destination_endpoint_id, origin_minion_pool_id,
+                          destination_minion_pool_id,
+                          instance_osmorphing_minion_pool_mappings,
+                          source_environment, destination_environment,
+                          instances, network_map, storage_mappings,
+                          replication_count,
                           shutdown_instances, notes=None,
                           skip_os_morphing=False, user_scripts=None):
         return self._rpc_client.migrate_instances(
             ctxt, origin_endpoint_id, destination_endpoint_id,
-            source_environment, destination_environment, instances,
-            network_map, storage_mappings,
-            replication_count, shutdown_instances=shutdown_instances,
+            origin_minion_pool_id, destination_minion_pool_id,
+            instance_osmorphing_minion_pool_mappings, source_environment,
+            destination_environment, instances, network_map,
+            storage_mappings, replication_count,
+            shutdown_instances=shutdown_instances,
             notes=notes, skip_os_morphing=skip_os_morphing,
             user_scripts=user_scripts)
 
-    def deploy_replica_instances(self, ctxt, replica_id, clone_disks=False,
-                                 force=False, skip_os_morphing=False,
-                                 user_scripts=None):
+    def deploy_replica_instances(self, ctxt, replica_id,
+                                 instance_osmorphing_minion_pool_mappings,
+                                 clone_disks=False, force=False,
+                                 skip_os_morphing=False, user_scripts=None):
         return self._rpc_client.deploy_replica_instances(
-            ctxt, replica_id, clone_disks, force, skip_os_morphing,
-            user_scripts=user_scripts)
+            ctxt, replica_id, instance_osmorphing_minion_pool_mappings,
+            clone_disks, force, skip_os_morphing, user_scripts=user_scripts)
 
     def delete(self, ctxt, migration_id):
         self._rpc_client.delete_migration(ctxt, migration_id)

+ 2 - 0
coriolis/replicas/api.py

@@ -9,10 +9,12 @@ class API(object):
         self._rpc_client = rpc_client.ConductorClient()
 
     def create(self, ctxt, origin_endpoint_id, destination_endpoint_id,
+               origin_minion_pool_id, destination_minion_pool_id,
                source_environment, destination_environment, instances,
                network_map, storage_mappings, notes=None):
         return self._rpc_client.create_instances_replica(
             ctxt, origin_endpoint_id, destination_endpoint_id,
+            origin_minion_pool_id, destination_minion_pool_id,
             source_environment, destination_environment, instances,
             network_map, storage_mappings, notes)
 

+ 3 - 1
coriolis/tasks/factory.py

@@ -104,7 +104,9 @@ _TASKS_MAP = {
     constants.TASK_TYPE_VALIDATE_SOURCE_MINION_POOL_COMPATIBILITY:
         minion_pool_tasks.ValidateSourceMinionCompatibilityTask,
     constants.TASK_TYPE_VALIDATE_DESTINATION_MINION_POOL_COMPATIBILITY:
-        minion_pool_tasks.ValidateDestinationMinionCompatibilityTask
+        minion_pool_tasks.ValidateDestinationMinionCompatibilityTask,
+    constants.TASK_TYPE_VALIDATE_OSMORPHING_MINION_POOL_COMPATIBILITY:
+        minion_pool_tasks.ValidateOSMorphingMinionCompatibilityTask
 }
 
 

+ 37 - 12
coriolis/tasks/minion_pool_tasks.py

@@ -76,7 +76,9 @@ class CreateMinionTask(base.TaskRunner):
 
     @classmethod
     def get_returned_task_info_properties(cls):
-        return ["minion_provider_properties", "minion_connection_info"]
+        return [
+            "minion_provider_properties", "minion_connection_info",
+            "minion_backup_writer_connection_info"]
 
     @classmethod
     def get_required_provider_types(cls):
@@ -107,7 +109,8 @@ class CreateMinionTask(base.TaskRunner):
 
         missing = [
             key for key in [
-                "minion_connection_info", "minion_provider_properties"]
+                "connection_info", "minion_provider_properties",
+                "backup_writer_connection_info"]
             if key not in minion_properties]
         if missing:
             LOG.warn(
@@ -115,9 +118,23 @@ class CreateMinionTask(base.TaskRunner):
                 "property keys: %s. Allowing run to completion for later "
                 "cleanup.")
 
+        minion_connection_info = {}
+        if 'connection_info' in minion_properties:
+            minion_connection_info = base.marshal_migr_conn_info(
+                minion_properties['connection_info'])
+        minion_backup_writer_conn = {}
+        if 'backup_writer_connection_info' in minion_properties:
+            minion_backup_writer_conn = minion_properties[
+                'backup_writer_connection_info']
+            if 'connection_details' in minion_backup_writer_conn:
+                minion_backup_writer_conn['connection_details'] = (
+                    base.marshal_migr_conn_info(
+                        minion_backup_writer_conn['connection_details']))
+
         return {
-            "minion_connection_info": minion_properties.get(
-                "minion_connection_info"),
+            "minion_connection_info": minion_connection_info,
+            "minion_backup_writer_connection_info": (
+                minion_backup_writer_conn),
             "minion_provider_properties": minion_properties.get(
                 "minion_provider_properties")}
 
@@ -134,11 +151,11 @@ class DeleteMinionTask(base.TaskRunner):
 
     @classmethod
     def get_required_task_info_properties(cls):
-        return ["pool_environment_options", "minion_provider_properties"]
+        return ["minion_provider_properties"]
 
     @classmethod
     def get_returned_task_info_properties(cls):
-        return ["minion_provider_properties", "minion_connection_info"]
+        return []
 
     @classmethod
     def get_required_provider_types(cls):
@@ -160,15 +177,11 @@ class DeleteMinionTask(base.TaskRunner):
             destination["type"], constants.PROVIDER_TYPE_MINION_POOL,
             event_handler)
 
-        environment_options = task_info['pool_environment_options']
         minion_provider_properties = task_info['minion_provider_properties']
         provider.delete_minion(
-            ctxt, connection_info, environment_options,
-            minion_provider_properties)
+            ctxt, connection_info, minion_provider_properties)
 
-        return {
-            "minion_provider_properties": None,
-            "minion_connection_info": None}
+        return {}
 
 
 class SetUpPoolSupportingResourcesTask(base.TaskRunner):
@@ -471,3 +484,15 @@ class ValidateDestinationMinionCompatibilityTask(
     @classmethod
     def _get_minion_properties_task_info_field(cls):
         return "destination_minion_provider_properties"
+
+
+class ValidateOSMorphingMinionCompatibilityTask(
+        _BaseValidateMinionCompatibilityTask):
+
+    @classmethod
+    def get_required_platform(cls):
+        return constants.PROVIDER_PLATFORM_DESTINATION
+
+    @classmethod
+    def _get_minion_properties_task_info_field(cls):
+        return "osmorphing_minion_provider_properties"