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

+ 33 - 12
coriolis/api/v1/minion_pool_actions.py

@@ -15,40 +15,61 @@ class MinionPoolActionsController(api_wsgi.Controller):
         self.minion_pool_api = api.API()
         self.minion_pool_api = api.API()
         super(MinionPoolActionsController, self).__init__()
         super(MinionPoolActionsController, self).__init__()
 
 
-    @api_wsgi.action('initialize')
-    def _initialize_pool(self, req, id, body):
+    @api_wsgi.action('set-up-shared-resources')
+    def _set_up_shared_resources(self, req, id, body):
         context = req.environ['coriolis.context']
         context = req.environ['coriolis.context']
         context.can(
         context.can(
-            minion_pool_policies.get_minion_pools_policy_label("initialize"))
+            minion_pool_policies.get_minion_pools_policy_label(
+                "set_up_shared_resources"))
         try:
         try:
             return minion_pool_tasks_execution_view.single(
             return minion_pool_tasks_execution_view.single(
-                req, self.minion_pool_api.initialize(context, id))
+                req, self.minion_pool_api.set_up_shared_pool_resources(
+                    context, id))
         except exception.NotFound as ex:
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)
             raise exc.HTTPNotFound(explanation=ex.msg)
         except exception.InvalidParameterValue as ex:
         except exception.InvalidParameterValue as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)
             raise exc.HTTPNotFound(explanation=ex.msg)
 
 
-    @api_wsgi.action('allocate')
-    def _allocate_pool(self, req, id, body):
+    @api_wsgi.action('tear-down-shared-resources')
+    def _tear_down_shared_resources(self, req, id, body):
         context = req.environ['coriolis.context']
         context = req.environ['coriolis.context']
         context.can(
         context.can(
-            minion_pool_policies.get_minion_pools_policy_label("allocate"))
+            minion_pool_policies.get_minion_pools_policy_label(
+                "tear_down_shared_resources"))
         try:
         try:
             return minion_pool_tasks_execution_view.single(
             return minion_pool_tasks_execution_view.single(
-                req, self.minion_pool_api.allocate(context, id))
+                req, self.minion_pool_api.tear_down_shared_pool_resources(
+                    context, id))
         except exception.NotFound as ex:
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)
             raise exc.HTTPNotFound(explanation=ex.msg)
         except exception.InvalidParameterValue as ex:
         except exception.InvalidParameterValue as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)
             raise exc.HTTPNotFound(explanation=ex.msg)
 
 
-    @api_wsgi.action('deallocate')
-    def _deallocate_pool(self, req, id, body):
+    @api_wsgi.action('allocate-machines')
+    def _allocate_pool_machines(self, req, id, body):
         context = req.environ['coriolis.context']
         context = req.environ['coriolis.context']
         context.can(
         context.can(
-            minion_pool_policies.get_minion_pools_policy_label("deallocate"))
+            minion_pool_policies.get_minion_pools_policy_label(
+                "allocate_machines"))
         try:
         try:
             return minion_pool_tasks_execution_view.single(
             return minion_pool_tasks_execution_view.single(
-                req, self.minion_pool_api.deallocate(context, id))
+                req, self.minion_pool_api.allocate_machines(
+                    context, id))
+        except exception.NotFound as ex:
+            raise exc.HTTPNotFound(explanation=ex.msg)
+        except exception.InvalidParameterValue as ex:
+            raise exc.HTTPNotFound(explanation=ex.msg)
+
+    @api_wsgi.action('deallocate-machines')
+    def _deallocate_pool_machines(self, req, id, body):
+        context = req.environ['coriolis.context']
+        context.can(
+            minion_pool_policies.get_minion_pools_policy_label(
+                "deallocate_machines"))
+        try:
+            return minion_pool_tasks_execution_view.single(
+                req, self.minion_pool_api.deallocate_machines(
+                    context, id))
         except exception.NotFound as ex:
         except exception.NotFound as ex:
             raise exc.HTTPNotFound(explanation=ex.msg)
             raise exc.HTTPNotFound(explanation=ex.msg)
         except exception.InvalidParameterValue as ex:
         except exception.InvalidParameterValue as ex:

+ 14 - 7
coriolis/api/v1/minion_pools.py

@@ -4,6 +4,7 @@
 from oslo_log import log as logging
 from oslo_log import log as logging
 from webob import exc
 from webob import exc
 
 
+from coriolis import constants
 from coriolis import exception
 from coriolis import exception
 from coriolis.api.v1.views import minion_pool_view
 from coriolis.api.v1.views import minion_pool_view
 from coriolis.api.v1.views import minion_pool_tasks_execution_view
 from coriolis.api.v1.views import minion_pool_tasks_execution_view
@@ -41,6 +42,12 @@ class MinionPoolController(api_wsgi.Controller):
             minion_pool = body["minion_pool"]
             minion_pool = body["minion_pool"]
             name = minion_pool["pool_name"]
             name = minion_pool["pool_name"]
             endpoint_id = minion_pool["endpoint_id"]
             endpoint_id = minion_pool["endpoint_id"]
+            pool_os_type = minion_pool["pool_os_type"]
+            if pool_os_type not in constants.VALID_OS_TYPES:
+                raise Exception(
+                    "The provided pool OS type '%s' is invalid. Must be one "
+                    "of the following: %s" % (
+                        pool_os_type, constants.VALID_OS_TYPES))
             environment_options = minion_pool["environment_options"]
             environment_options = minion_pool["environment_options"]
             self._endpoints_api.validate_endpoint_minion_pool_options(
             self._endpoints_api.validate_endpoint_minion_pool_options(
                 ctxt, endpoint_id, environment_options)
                 ctxt, endpoint_id, environment_options)
@@ -53,8 +60,8 @@ class MinionPoolController(api_wsgi.Controller):
                 "minion_retention_strategy")
                 "minion_retention_strategy")
             notes = minion_pool.get("notes")
             notes = minion_pool.get("notes")
             return (
             return (
-                name, endpoint_id, environment_options, minimum_minions,
-                maximum_minions, minion_max_idle_time,
+                name, endpoint_id, pool_os_type, environment_options,
+                minimum_minions, maximum_minions, minion_max_idle_time,
                 minion_retention_strategy, notes)
                 minion_retention_strategy, notes)
         except Exception as ex:
         except Exception as ex:
             LOG.exception(ex)
             LOG.exception(ex)
@@ -67,14 +74,14 @@ class MinionPoolController(api_wsgi.Controller):
     def create(self, req, body):
     def create(self, req, body):
         context = req.environ["coriolis.context"]
         context = req.environ["coriolis.context"]
         context.can(pools_policies.get_minion_pools_policy_label("create"))
         context.can(pools_policies.get_minion_pools_policy_label("create"))
-        (name, endpoint_id, environment_options, minimum_minions,
+        (name, endpoint_id, pool_os_type, environment_options, minimum_minions,
          maximum_minions, minion_max_idle_time, minion_retention_strategy,
          maximum_minions, minion_max_idle_time, minion_retention_strategy,
          notes) = (
          notes) = (
             self._validate_create_body(context, body))
             self._validate_create_body(context, body))
         return minion_pool_view.single(req, self._minion_pool_api.create(
         return minion_pool_view.single(req, self._minion_pool_api.create(
-            context, name, endpoint_id, environment_options, minimum_minions,
-            maximum_minions, minion_max_idle_time, minion_retention_strategy,
-            notes=notes))
+            context, name, endpoint_id, pool_os_type, environment_options,
+            minimum_minions, maximum_minions, minion_max_idle_time,
+            minion_retention_strategy, notes=notes))
 
 
     def _validate_update_body(self, id, context, body):
     def _validate_update_body(self, id, context, body):
         try:
         try:
@@ -82,7 +89,7 @@ class MinionPoolController(api_wsgi.Controller):
             vals = {k: minion_pool[k] for k in minion_pool.keys() &
             vals = {k: minion_pool[k] for k in minion_pool.keys() &
                     {"name", "environment_options", "minimum_minions",
                     {"name", "environment_options", "minimum_minions",
                      "maximum_minions", "minion_max_idle_time",
                      "maximum_minions", "minion_max_idle_time",
-                     "minion_retention_strategy", "notes"}}
+                     "minion_retention_strategy", "notes", "pool_os_type"}}
             if 'environment_options' in vals:
             if 'environment_options' in vals:
                 minion_pool = self._minion_pool_api.get_minion_pool(
                 minion_pool = self._minion_pool_api.get_minion_pool(
                     context, id)
                     context, id)

+ 0 - 4
coriolis/api/v1/views/minion_pool_view.py

@@ -22,10 +22,6 @@ def _format_minion_pool(req, minion_pool, keys=None):
         if key in minion_pool_dict:
         if key in minion_pool_dict:
             minion_pool_dict["environment_options"] = minion_pool_dict.pop(key)
             minion_pool_dict["environment_options"] = minion_pool_dict.pop(key)
 
 
-    pool_machines = minion_pool_dict.get('minion_machines', [])
-    minion_pool_dict['minion_machines'] = [
-        machine['id'] for machine in pool_machines]
-
     return minion_pool_dict
     return minion_pool_dict
 
 
 
 

+ 16 - 8
coriolis/conductor/rpc/client.py

@@ -374,28 +374,36 @@ class ConductorClient(object):
             ctxt, 'delete_service', service_id=service_id)
             ctxt, 'delete_service', service_id=service_id)
 
 
     def create_minion_pool(
     def create_minion_pool(
-            self, ctxt, name, endpoint_id, environment_options,
+            self, ctxt, name, endpoint_id, pool_os_type, environment_options,
             minimum_minions, maximum_minions, minion_max_idle_time,
             minimum_minions, maximum_minions, minion_max_idle_time,
             minion_retention_strategy, notes=None):
             minion_retention_strategy, notes=None):
         return self._client.call(
         return self._client.call(
             ctxt, 'create_minion_pool', name=name, endpoint_id=endpoint_id,
             ctxt, 'create_minion_pool', name=name, endpoint_id=endpoint_id,
-            environment_options=environment_options,
+            pool_os_type=pool_os_type, environment_options=environment_options,
             minimum_minions=minimum_minions, maximum_minions=maximum_minions,
             minimum_minions=minimum_minions, maximum_minions=maximum_minions,
             minion_max_idle_time=minion_max_idle_time,
             minion_max_idle_time=minion_max_idle_time,
             minion_retention_strategy=minion_retention_strategy,
             minion_retention_strategy=minion_retention_strategy,
             notes=notes)
             notes=notes)
 
 
-    def initialize_minion_pool(self, ctxt, minion_pool_id):
+    def set_up_shared_minion_pool_resources(self, ctxt, minion_pool_id):
         return self._client.call(
         return self._client.call(
-            ctxt, "initialize_minion_pool", minion_pool_id=minion_pool_id)
+            ctxt, "set_up_shared_minion_pool_resources",
+            minion_pool_id=minion_pool_id)
 
 
-    def allocate_minion_pool(self, ctxt, minion_pool_id):
+    def tear_down_shared_minion_pool_resources(self, ctxt, minion_pool_id):
         return self._client.call(
         return self._client.call(
-            ctxt, "allocate_minion_pool", minion_pool_id=minion_pool_id)
+            ctxt, "tear_down_shared_minion_pool_resources",
+            minion_pool_id=minion_pool_id)
 
 
-    def deallocate_minion_pool(self, ctxt, minion_pool_id):
+    def allocate_minion_pool_machines(self, ctxt, minion_pool_id):
         return self._client.call(
         return self._client.call(
-            ctxt, "deallocate_minion_pool", minion_pool_id=minion_pool_id)
+            ctxt, "allocate_minion_pool_machines",
+            minion_pool_id=minion_pool_id)
+
+    def deallocate_minion_pool_machines(self, ctxt, minion_pool_id):
+        return self._client.call(
+            ctxt, "deallocate_minion_pool_machines",
+            minion_pool_id=minion_pool_id)
 
 
     def get_minion_pools(self, ctxt):
     def get_minion_pools(self, ctxt):
         return self._client.call(ctxt, 'get_minion_pools')
         return self._client.call(ctxt, 'get_minion_pools')

+ 192 - 87
coriolis/conductor/rpc/server.py

@@ -2346,13 +2346,13 @@ class ConductorServerEndpoint(object):
             still_running = _check_other_tasks_running(execution, task)
             still_running = _check_other_tasks_running(execution, task)
             if not still_running:
             if not still_running:
                 LOG.info(
                 LOG.info(
-                    "Updating 'pool_supporting_resources' for pool %s after "
+                    "Updating 'pool_shared_resources' for pool %s after "
                     "completion of task '%s' (type '%s').",
                     "completion of task '%s' (type '%s').",
                     execution.action_id, task.id, task_type)
                     execution.action_id, task.id, task_type)
                 db_api.update_minion_pool_lifecycle(
                 db_api.update_minion_pool_lifecycle(
                     ctxt, execution.action_id, {
                     ctxt, execution.action_id, {
-                        "pool_supporting_resources": task_info.get(
-                            "pool_supporting_resources", {})})
+                        "pool_shared_resources": task_info.get(
+                            "pool_shared_resources", {})})
                 db_api.set_minion_pool_lifecycle_status(
                 db_api.set_minion_pool_lifecycle_status(
                     ctxt, execution.action_id,
                     ctxt, execution.action_id,
                     constants.MINION_POOL_STATUS_DEALLOCATED)
                     constants.MINION_POOL_STATUS_DEALLOCATED)
@@ -2361,49 +2361,50 @@ class ConductorServerEndpoint(object):
             still_running = _check_other_tasks_running(execution, task)
             still_running = _check_other_tasks_running(execution, task)
             if not still_running:
             if not still_running:
                 LOG.info(
                 LOG.info(
-                    "Clearing 'pool_supporting_resources' for pool %s following "
+                    "Clearing 'pool_shared_resources' for pool %s following "
                     "completion of task '%s' (type %s)",
                     "completion of task '%s' (type %s)",
                     execution.action_id, task.id, task_type)
                     execution.action_id, task.id, task_type)
                 db_api.update_minion_pool_lifecycle(
                 db_api.update_minion_pool_lifecycle(
                     ctxt, execution.action_id, {
                     ctxt, execution.action_id, {
-                        "pool_supporting_resources": {}})
+                        "pool_shared_resources": {}})
                 db_api.set_minion_pool_lifecycle_status(
                 db_api.set_minion_pool_lifecycle_status(
                     ctxt, execution.action_id,
                     ctxt, execution.action_id,
                     constants.MINION_POOL_STATUS_UNINITIALIZED)
                     constants.MINION_POOL_STATUS_UNINITIALIZED)
 
 
         elif task_type == constants.TASK_TYPE_CREATE_MINION:
         elif task_type == constants.TASK_TYPE_CREATE_MINION:
             LOG.info(
             LOG.info(
-                "Updating properties for Minion Machine '%s' of pool %s "
+                "Adding DB entry for Minion Machine '%s' of pool %s "
                 "following completion of task '%s' (type %s).",
                 "following completion of task '%s' (type %s).",
                 task.instance, execution.action_id, task.id, task_type)
                 task.instance, execution.action_id, task.id, task_type)
-            db_api.update_minion_machine(
-                # NOTE: we used the Minion Machine ID as the tasks' instance.
-                ctxt, task.instance, {
-                    "status": constants.MINION_MACHINE_STATUS_AVAILABLE,
-                    "provider_properties": task_info[
-                        "minion_provider_properties"],
-                    "connection_info": task_info[
-                        "minion_connection_info"]})
+            minion_machine = models.MinionMachine()
+            minion_machine.id = task.instance
+            minion_machine.pool_id = execution.action_id
+            minion_machine.status = (
+                constants.MINION_MACHINE_STATUS_AVAILABLE)
+            minion_machine.connection_info = task_info[
+                "minion_connection_info"]
+            minion_machine.provider_properties = task_info[
+                "minion_provider_properties"]
+            db_api.add_minion_machine(ctxt, minion_machine)
 
 
             still_running = _check_other_tasks_running(execution, task)
             still_running = _check_other_tasks_running(execution, task)
             if not still_running:
             if not still_running:
                 db_api.set_minion_pool_lifecycle_status(
                 db_api.set_minion_pool_lifecycle_status(
                     ctxt, execution.action_id,
                     ctxt, execution.action_id,
-                    constants.MINION_POOL_STATUS_AVAILABLE)
+                    constants.MINION_POOL_STATUS_ALLOCATED)
 
 
         elif task_type == constants.TASK_TYPE_DELETE_MINION:
         elif task_type == constants.TASK_TYPE_DELETE_MINION:
             LOG.info(
             LOG.info(
-                "Clearing properties for Minion Machine '%s' of pool %s "
-                "following completion of task '%s' (type %s).",
-                task.instance, execution.action_id, task.id, task_type)
-            db_api.update_minion_machine(
-                # NOTE: we used the Minion Machine ID as the tasks' instance.
-                ctxt, task.instance, {
-                    "status": constants.MINION_MACHINE_STATUS_UNINITIALIZED,
-                    "provider_properties": task_info[
-                        "minion_provider_properties"],
-                    "connection_info": task_info[
-                        "minion_connection_info"]})
+                "%s task for Minon Machine '%s' has completed successfully. "
+                "Deleting minion machine from DB.",
+                constants.TASK_TYPE_DELETE_MINION, task.instance)
+            db_api.delete_minion_machine(ctxt, task.instance)
+
+            still_running = _check_other_tasks_running(execution, task)
+            if not still_running:
+                db_api.set_minion_pool_lifecycle_status(
+                    ctxt, execution.action_id,
+                    constants.MINION_POOL_STATUS_DEALLOCATED)
 
 
         else:
         else:
             LOG.debug(
             LOG.debug(
@@ -2714,6 +2715,7 @@ class ConductorServerEndpoint(object):
             else:
             else:
                 self._cancel_tasks_execution(ctxt, execution)
                 self._cancel_tasks_execution(ctxt, execution)
 
 
+
             # NOTE: if this was a migration, make sure to delete
             # NOTE: if this was a migration, make sure to delete
             # its associated reservation.
             # its associated reservation.
             if execution.type == constants.EXECUTION_TYPE_MIGRATION:
             if execution.type == constants.EXECUTION_TYPE_MIGRATION:
@@ -3017,7 +3019,7 @@ class ConductorServerEndpoint(object):
         db_api.delete_service(ctxt, service_id)
         db_api.delete_service(ctxt, service_id)
 
 
     def create_minion_pool(
     def create_minion_pool(
-            self, ctxt, name, endpoint_id, environment_options,
+            self, ctxt, name, endpoint_id, pool_os_type, environment_options,
             minimum_minions, maximum_minions, minion_max_idle_time,
             minimum_minions, maximum_minions, minion_max_idle_time,
             minion_retention_strategy, notes=None):
             minion_retention_strategy, notes=None):
         endpoint = db_api.get_endpoint(ctxt, endpoint_id)
         endpoint = db_api.get_endpoint(ctxt, endpoint_id)
@@ -3026,6 +3028,7 @@ class ConductorServerEndpoint(object):
         minion_pool.id = str(uuid.uuid4())
         minion_pool.id = str(uuid.uuid4())
         minion_pool.pool_name = name
         minion_pool.pool_name = name
         minion_pool.notes = notes
         minion_pool.notes = notes
+        minion_pool.pool_os_type = pool_os_type
         minion_pool.pool_status = constants.MINION_POOL_STATUS_UNINITIALIZED
         minion_pool.pool_status = constants.MINION_POOL_STATUS_UNINITIALIZED
         minion_pool.minimum_minions = minimum_minions
         minion_pool.minimum_minions = minimum_minions
         minion_pool.maximum_minions = maximum_minions
         minion_pool.maximum_minions = maximum_minions
@@ -3044,25 +3047,34 @@ class ConductorServerEndpoint(object):
         db_api.add_minion_pool_lifecycle(ctxt, minion_pool)
         db_api.add_minion_pool_lifecycle(ctxt, minion_pool)
         return self.get_minion_pool(ctxt, minion_pool.id)
         return self.get_minion_pool(ctxt, minion_pool.id)
 
 
-    def get_minion_pools(self, ctxt):
+    def get_minion_pools(self, ctxt, include_tasks_executions=False):
         return db_api.get_minion_pool_lifecycles(
         return db_api.get_minion_pool_lifecycles(
-            ctxt, include_tasks_executions=False)
-
-    def _get_minion_pool(self, ctxt, minion_pool_id):
-        minion_pool = db_api.get_minion_pool_lifecycle(ctxt, minion_pool_id)
+            ctxt, include_tasks_executions=include_tasks_executions,
+            include_machines=True)
+
+    def _get_minion_pool(
+            self, ctxt, minion_pool_id, include_tasks_executions=True,
+            include_machines=True):
+        minion_pool = db_api.get_minion_pool_lifecycle(
+            ctxt, minion_pool_id, include_machines=include_machines,
+            include_tasks_executions=include_tasks_executions)
         if not minion_pool:
         if not minion_pool:
             raise exception.NotFound(
             raise exception.NotFound(
                 "Minion pool with ID '%s' not found." % minion_pool_id)
                 "Minion pool with ID '%s' not found." % minion_pool_id)
         return minion_pool
         return minion_pool
 
 
     @minion_pool_synchronized
     @minion_pool_synchronized
-    def initialize_minion_pool(self, ctxt, minion_pool_id):
-        LOG.info("Attempting to initialize Minion Pool '%s'.", minion_pool_id)
-        minion_pool = db_api.get_minion_pool_lifecycle(ctxt, minion_pool_id)
+    def set_up_shared_minion_pool_resources(self, ctxt, minion_pool_id):
+        LOG.info(
+            "Attempting to set up shared resources for Minion Pool '%s'.",
+            minion_pool_id)
+        minion_pool = db_api.get_minion_pool_lifecycle(
+            ctxt, minion_pool_id, include_tasks_executions=False,
+            include_machines=False)
         if minion_pool.pool_status != constants.MINION_POOL_STATUS_UNINITIALIZED:
         if minion_pool.pool_status != constants.MINION_POOL_STATUS_UNINITIALIZED:
             raise exception.InvalidMinionPoolState(
             raise exception.InvalidMinionPoolState(
-                "Minion Pool '%s' cannot be initialized as it is in '%s' state "
-                "instead of the expected %s."% (
+                "Minion Pool '%s' cannot have shared resources set up as it "
+                "is in '%s' state instead of the expected %s."% (
                     minion_pool_id, minion_pool.pool_status,
                     minion_pool_id, minion_pool.pool_status,
                     constants.MINION_POOL_STATUS_UNINITIALIZED))
                     constants.MINION_POOL_STATUS_UNINITIALIZED))
 
 
@@ -3070,9 +3082,11 @@ class ConductorServerEndpoint(object):
         execution.id = str(uuid.uuid4())
         execution.id = str(uuid.uuid4())
         execution.action = minion_pool
         execution.action = minion_pool
         execution.status = constants.EXECUTION_STATUS_UNEXECUTED
         execution.status = constants.EXECUTION_STATUS_UNEXECUTED
-        execution.type = constants.EXECUTION_TYPE_MINION_POOL_INITIALIZATION
+        execution.type = (
+            constants.EXECUTION_TYPE_MINION_POOL_SET_UP_SHARED_RESOURCES)
 
 
         minion_pool.info[minion_pool_id] = {
         minion_pool.info[minion_pool_id] = {
+            "pool_os_type": minion_pool.pool_os_type,
             "pool_identifier": minion_pool.id,
             "pool_identifier": minion_pool.id,
             # TODO(aznashwan): remove redundancy once transfer
             # TODO(aznashwan): remove redundancy once transfer
             # action DB models have been overhauled:
             # action DB models have been overhauled:
@@ -3098,20 +3112,68 @@ class ConductorServerEndpoint(object):
 
 
         # add new execution to DB:
         # add new execution to DB:
         db_api.add_minion_pool_lifecycle_execution(ctxt, execution)
         db_api.add_minion_pool_lifecycle_execution(ctxt, execution)
-        LOG.info("Minion pool initialization execution created: %s", execution.id)
+        LOG.info(
+            "Minion pool shared resource creation execution created: %s",
+            execution.id)
+
+        self._begin_tasks(ctxt, execution, task_info=minion_pool.info)
+        return self._get_minion_pool_lifecycle_execution(
+            ctxt, minion_pool_id, execution.id).to_dict()
+
+    @minion_pool_synchronized
+    def tear_down_shared_minion_pool_resources(self, ctxt, minion_pool_id):
+        LOG.info(
+            "Attempting to tear down shared resources for Minion Pool '%s'.",
+            minion_pool_id)
+        minion_pool = db_api.get_minion_pool_lifecycle(
+            ctxt, minion_pool_id, include_tasks_executions=False,
+            include_machines=False)
+        if minion_pool.pool_status != constants.MINION_POOL_STATUS_DEALLOCATED:
+            raise exception.InvalidMinionPoolState(
+                "Minion Pool '%s' cannot have shared resources torn down as it"
+                " is in '%s' state instead of the expected %s."% (
+                    minion_pool_id, minion_pool.pool_status,
+                    constants.MINION_POOL_STATUS_DEALLOCATED))
+
+        execution = models.TasksExecution()
+        execution.id = str(uuid.uuid4())
+        execution.action = minion_pool
+        execution.status = constants.EXECUTION_STATUS_UNEXECUTED
+        execution.type = (
+            constants.EXECUTION_TYPE_MINION_POOL_TEAR_DOWN_SHARED_RESOURCES)
+
+        self._create_task(
+            minion_pool.id,
+            constants.TASK_TYPE_TEAR_DOWN_SHARED_POOL_RESOURCES,
+            execution)
+
+        self._check_execution_tasks_sanity(execution, minion_pool.info)
+
+        # update the action info for the pool's instance:
+        db_api.update_transfer_action_info_for_instance(
+            ctxt, minion_pool.id, minion_pool.id,
+            minion_pool.info[minion_pool.id])
+
+        # add new execution to DB:
+        db_api.add_minion_pool_lifecycle_execution(ctxt, execution)
+        LOG.info(
+            "Minion pool shared resource teardown execution created: %s",
+            execution.id)
 
 
         self._begin_tasks(ctxt, execution, task_info=minion_pool.info)
         self._begin_tasks(ctxt, execution, task_info=minion_pool.info)
         return self._get_minion_pool_lifecycle_execution(
         return self._get_minion_pool_lifecycle_execution(
             ctxt, minion_pool_id, execution.id).to_dict()
             ctxt, minion_pool_id, execution.id).to_dict()
 
 
     @minion_pool_synchronized
     @minion_pool_synchronized
-    def allocate_minion_pool(self, ctxt, minion_pool_id):
+    def allocate_minion_pool_machines(self, ctxt, minion_pool_id):
         LOG.info("Attempting to allocate Minion Pool '%s'.", minion_pool_id)
         LOG.info("Attempting to allocate Minion Pool '%s'.", minion_pool_id)
-        minion_pool = self._get_minion_pool(ctxt, minion_pool_id)
+        minion_pool = self._get_minion_pool(
+            ctxt, minion_pool_id, include_tasks_executions=False,
+            include_machines=True)
         if minion_pool.pool_status != constants.MINION_POOL_STATUS_DEALLOCATED:
         if minion_pool.pool_status != constants.MINION_POOL_STATUS_DEALLOCATED:
             raise exception.InvalidMinionPoolState(
             raise exception.InvalidMinionPoolState(
-                "Minion Pool '%s' cannot be started as it is in '%s' state "
-                "instead of the expected %s."% (
+                "Minion machines for pool '%s' cannot be allocated as the pool"
+                " is in '%s' state instead of the expected %s."% (
                     minion_pool_id, minion_pool.pool_status,
                     minion_pool_id, minion_pool.pool_status,
                     constants.MINION_POOL_STATUS_DEALLOCATED))
                     constants.MINION_POOL_STATUS_DEALLOCATED))
 
 
@@ -3119,35 +3181,44 @@ class ConductorServerEndpoint(object):
         execution.id = str(uuid.uuid4())
         execution.id = str(uuid.uuid4())
         execution.action = minion_pool
         execution.action = minion_pool
         execution.status = constants.EXECUTION_STATUS_UNEXECUTED
         execution.status = constants.EXECUTION_STATUS_UNEXECUTED
-        execution.type = constants.EXECUTION_TYPE_MINION_POOL_MAINTENANCE
-
-        minion_machine_ids = []
-        try:
-            for i in range(minion_pool.minimum_minions):
-                minion_machine = models.MinionMachine()
-                minion_machine.id = str(uuid.uuid4())
-                minion_machine.pool_id = minion_pool.id
-                minion_machine.status = (
-                    constants.MINION_MACHINE_STATUS_UNINITIALIZED)
-                minion_machine.connection_info = {}
-                minion_machine.provider_properties = {}
-                db_api.add_minion_machine(ctxt, minion_machine)
-
-                minion_machine_ids.append(minion_machine.id)
-        except Exception as ex:
-            LOG.warn(
-                "Failed to create minion machine DB entry for pool with "
-                "ID '%s'. Cleaning up already created machines: %s" % (
-                    minion_pool_id, minion_machine_ids))
-            for minion_machine_id in minion_machine_ids:
-                utils.ignore_exceptions(
-                    db_api.delete_minion_machine(ctxt, minion_machine_id))
-            raise
-
-        # TODO(aznashwan): add shared pool resources setup tasks:
-        for minion_machine_id in minion_machine_ids:
+        execution.type = constants.EXECUTION_TYPE_MINION_POOL_ALLOCATE_MINIONS
+
+        new_minion_machine_ids = [
+            str(uuid.uuid4()) for _ in range(minion_pool.minimum_minions)]
+
+        # minion_machine_ids = []
+        # try:
+        #     for i in range(minion_pool.minimum_minions):
+        #         minion_machine = models.MinionMachine()
+        #         minion_machine.id = str(uuid.uuid4())
+        #         minion_machine.pool_id = minion_pool.id
+        #         minion_machine.status = (
+        #             constants.MINION_MACHINE_STATUS_UNINITIALIZED)
+        #         minion_machine.connection_info = {}
+        #         minion_machine.provider_properties = {}
+        #         db_api.add_minion_machine(ctxt, minion_machine)
+
+        #         minion_machine_ids.append(minion_machine.id)
+        # except Exception as ex:
+        #     LOG.warn(
+        #         "Failed to create minion machine DB entry for pool with "
+        #         "ID '%s'. Cleaning up already created machines: %s" % (
+        #             minion_pool_id, minion_machine_ids))
+        #     for minion_machine_id in minion_machine_ids:
+        #         utils.ignore_exceptions(
+        #             db_api.delete_minion_machine(ctxt, minion_machine_id))
+        #     raise
+
+        for minion_machine_id in new_minion_machine_ids:
             minion_pool.info[minion_machine_id] = {
             minion_pool.info[minion_machine_id] = {
-                "pool_environment_options": minion_pool.source_environment}
+                "pool_identifier": minion_pool_id,
+                "pool_os_type": minion_pool.pool_os_type,
+                "pool_shared_resources": minion_pool.pool_shared_resources,
+                "pool_environment_options": minion_pool.source_environment,
+                # NOTE: we default this to an empty dict here to avoid possible
+                # task info conflicts on the cleanup task below for minions
+                # which were slower to deploy:
+                "minion_provider_properties": {}}
 
 
             validate_minions_option_task = self._create_task(
             validate_minions_option_task = self._create_task(
                 minion_machine_id,
                 minion_machine_id,
@@ -3168,7 +3239,7 @@ class ConductorServerEndpoint(object):
         self._check_execution_tasks_sanity(execution, minion_pool.info)
         self._check_execution_tasks_sanity(execution, minion_pool.info)
 
 
         # update the action info for all of the pool's minions:
         # update the action info for all of the pool's minions:
-        for minion_machine_id in minion_machine_ids:
+        for minion_machine_id in new_minion_machine_ids:
             db_api.update_transfer_action_info_for_instance(
             db_api.update_transfer_action_info_for_instance(
                 ctxt, minion_pool.id, minion_machine_id,
                 ctxt, minion_pool.id, minion_machine_id,
                 minion_pool.info[minion_machine_id])
                 minion_pool.info[minion_machine_id])
@@ -3182,14 +3253,18 @@ class ConductorServerEndpoint(object):
             ctxt, minion_pool_id, execution.id).to_dict()
             ctxt, minion_pool_id, execution.id).to_dict()
 
 
     @minion_pool_synchronized
     @minion_pool_synchronized
-    def deallocate_minion_pool(self, ctxt, minion_pool_id):
+    def deallocate_minion_pool_machines(self, ctxt, minion_pool_id):
         LOG.info("Attempting to deallocate Minion Pool '%s'.", minion_pool_id)
         LOG.info("Attempting to deallocate Minion Pool '%s'.", minion_pool_id)
-        minion_pool = db_api.get_minion_pool_lifecycle(ctxt, minion_pool_id)
+        minion_pool = db_api.get_minion_pool_lifecycle(
+            ctxt, minion_pool_id, include_tasks_executions=False,
+            include_machines=True)
         if minion_pool.pool_status not in (
         if minion_pool.pool_status not in (
-                constants.MINION_POOL_STATUS_AVAILABLE):
+                constants.MINION_POOL_STATUS_ALLOCATED):
             raise exception.InvalidMinionPoolState(
             raise exception.InvalidMinionPoolState(
-                "Minion Pool '%s' cannot be started as it is in '%s' state" % (
-                    minion_pool_id, minion_pool.pool_status))
+                "Minion Pool '%s' cannot be deallocated as it is in '%s' "
+                "state instead of the expected '%s'." % (
+                    minion_pool_id, minion_pool.pool_status,
+                    constants.MINION_POOL_STATUS_ALLOCATED))
 
 
         # TODO(aznashwan): check minion pool running
         # TODO(aznashwan): check minion pool running
         # executions/allocated machines
         # executions/allocated machines
@@ -3198,27 +3273,33 @@ class ConductorServerEndpoint(object):
         execution.id = str(uuid.uuid4())
         execution.id = str(uuid.uuid4())
         execution.action = minion_pool
         execution.action = minion_pool
         execution.status = constants.EXECUTION_STATUS_UNEXECUTED
         execution.status = constants.EXECUTION_STATUS_UNEXECUTED
-        execution.type = constants.EXECUTION_TYPE_MINION_POOL_MAINTENANCE
+        execution.type = (
+            constants.EXECUTION_TYPE_MINION_POOL_DEALLOCATE_MINIONS)
 
 
         for minion_machine in minion_pool.minion_machines:
         for minion_machine in minion_pool.minion_machines:
             minion_machine_id = minion_machine.id
             minion_machine_id = minion_machine.id
             minion_pool.info[minion_machine_id] = {
             minion_pool.info[minion_machine_id] = {
-                "pool_environment_options": minion_pool.source_environment}
+                "pool_environment_options": minion_pool.source_environment,
+                "minion_provider_properties": (
+                    minion_machine.provider_properties)}
             self._create_task(
             self._create_task(
                 minion_machine_id, constants.TASK_TYPE_DELETE_MINION,
                 minion_machine_id, constants.TASK_TYPE_DELETE_MINION,
-                execution)
+                # NOTE: we set 'on_error=True' to allow for the completion of
+                # already running deletion tasks to prevent partial deletes:
+                execution, on_error=True)
 
 
         self._check_execution_tasks_sanity(execution, minion_pool.info)
         self._check_execution_tasks_sanity(execution, minion_pool.info)
 
 
         # update the action info for all of the pool's minions:
         # update the action info for all of the pool's minions:
-        for minion_machine_id in minion_pool.instances:
+        for minion_machine in minion_pool.minion_machines:
             db_api.update_transfer_action_info_for_instance(
             db_api.update_transfer_action_info_for_instance(
-                ctxt, minion_pool.id, minion_machine_id,
-                minion_pool.info[minion_machine_id])
+                ctxt, minion_pool.id, minion_machine.id,
+                minion_pool.info[minion_machine.id])
 
 
         # add new execution to DB:
         # add new execution to DB:
         db_api.add_minion_pool_lifecycle_execution(ctxt, execution)
         db_api.add_minion_pool_lifecycle_execution(ctxt, execution)
-        LOG.info("Minion pool allocation execution created: %s", execution.id)
+        LOG.info(
+            "Minion pool deallocation execution created: %s", execution.id)
 
 
         self._begin_tasks(ctxt, execution, task_info=minion_pool.info)
         self._begin_tasks(ctxt, execution, task_info=minion_pool.info)
         return self._get_minion_pool_lifecycle_execution(
         return self._get_minion_pool_lifecycle_execution(
@@ -3226,10 +3307,23 @@ class ConductorServerEndpoint(object):
 
 
     @minion_pool_synchronized
     @minion_pool_synchronized
     def get_minion_pool(self, ctxt, minion_pool_id):
     def get_minion_pool(self, ctxt, minion_pool_id):
-        return self._get_minion_pool(ctxt, minion_pool_id)
+        return self._get_minion_pool(
+            ctxt, minion_pool_id, include_tasks_executions=True,
+            include_machines=True)
 
 
     @minion_pool_synchronized
     @minion_pool_synchronized
     def update_minion_pool(self, ctxt, minion_pool_id, updated_values):
     def update_minion_pool(self, ctxt, minion_pool_id, updated_values):
+        minion_pool = self._get_minion_pool(
+            ctxt, minion_pool_id, include_tasks_executions=False,
+            include_machines=False)
+        if minion_pool.pool_status != constants.MINION_POOL_STATUS_UNINITIALIZED:
+            raise exception.InvalidMinionPoolState(
+                "Minion Pool '%s' cannot be updated as it is in '%s' status "
+                "instead of the expected '%s'. Please ensure the pool machines"
+                "have been deallocated and the pool's supporting resources "
+                "have been torn down before updating the pool." % (
+                    minion_pool_id, minion_pool.pool_status,
+                    constants.MINION_POOL_STATUS_UNINITIALIZED))
         LOG.info(
         LOG.info(
             "Attempting to update minion_pool '%s' with payload: %s",
             "Attempting to update minion_pool '%s' with payload: %s",
             minion_pool_id, updated_values)
             minion_pool_id, updated_values)
@@ -3239,8 +3333,19 @@ class ConductorServerEndpoint(object):
 
 
     @minion_pool_synchronized
     @minion_pool_synchronized
     def delete_minion_pool(self, ctxt, minion_pool_id):
     def delete_minion_pool(self, ctxt, minion_pool_id):
-        # TODO(aznashwan): add checks for endpoints/services
-        # associated to the minion_pool before deletion:
+        minion_pool = self._get_minion_pool(
+            ctxt, minion_pool_id, include_tasks_executions=False,
+            include_machines=False)
+        if minion_pool.pool_status != constants.MINION_POOL_STATUS_UNINITIALIZED:
+            raise exception.InvalidMinionPoolState(
+                "Minion Pool '%s' cannot be deleted as it is in '%s' status "
+                "instead of the expected '%s'. Please ensure the pool machines"
+                "have been deallocated and the pool's supporting resources "
+                "have been torn down before deleting the pool." % (
+                    minion_pool_id, minion_pool.pool_status,
+                    constants.MINION_POOL_STATUS_UNINITIALIZED))
+
+        LOG.info("Deleting minion pool with ID '%s'" % minion_pool_id)
         db_api.delete_minion_pool_lifecycle(ctxt, minion_pool_id)
         db_api.delete_minion_pool_lifecycle(ctxt, minion_pool_id)
 
 
     @minion_pool_synchronized
     @minion_pool_synchronized

+ 31 - 7
coriolis/constants.py

@@ -136,12 +136,20 @@ TASK_TYPE_CREATE_MINION = "CREATE_MINION"
 TASK_TYPE_DELETE_MINION = "DELETE_MINION"
 TASK_TYPE_DELETE_MINION = "DELETE_MINION"
 TASK_TYPE_STOP_MINION = "STOP_MINION"
 TASK_TYPE_STOP_MINION = "STOP_MINION"
 TASK_TYPE_START_MINION = "START_MINION"
 TASK_TYPE_START_MINION = "START_MINION"
-TASK_TYPE_ATTACH_DISK_TO_MINION = "ATTACH_DISK_TO_MINION"
-TASK_TYPE_DETACH_DISK_FROM_MINION = "DETACH_DISK_FROM_MINION"
 TASK_TYPE_SET_UP_SHARED_POOL_RESOURCES = "SET_UP_POOL_RESOURCES"
 TASK_TYPE_SET_UP_SHARED_POOL_RESOURCES = "SET_UP_POOL_RESOURCES"
 TASK_TYPE_TEAR_DOWN_SHARED_POOL_RESOURCES = (
 TASK_TYPE_TEAR_DOWN_SHARED_POOL_RESOURCES = (
     "TEAR_DOWN_SHARED_POOL_RESOURCES")
     "TEAR_DOWN_SHARED_POOL_RESOURCES")
-
+TASK_TYPE_ATTACH_VOLUMES_TO_SOURCE_MINION = "ATTACH_VOLUMES_TO_SOURCE_MINION"
+TASK_TYPE_DETACH_VOLUMES_FROM_SOURCE_MINION = (
+    "DETACH_VOLUMES_FROM_SOURCE_MINION")
+TASK_TYPE_ATTACH_VOLUMES_TO_DESTINATION_MINION = (
+    "ATTACH_VOLUMES_TO_DESTINATION_MINION")
+TASK_TYPE_DETACH_VOLUMES_FROM_DESTINATION_MINION = (
+    "DETACH_VOLUMES_FROM_DESTINATION_MINION")
+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_PLATFORM_SOURCE = "source"
 TASK_PLATFORM_SOURCE = "source"
 TASK_PLATFORM_DESTINATION = "destination"
 TASK_PLATFORM_DESTINATION = "destination"
@@ -204,6 +212,9 @@ OS_TYPE_UNKNOWN = "unknown"
 
 
 DEFAULT_OS_TYPE = OS_TYPE_LINUX
 DEFAULT_OS_TYPE = OS_TYPE_LINUX
 
 
+VALID_OS_TYPES = [
+    OS_TYPE_BSD, OS_TYPE_LINUX, OS_TYPE_OS_X, OS_TYPE_SOLARIS, OS_TYPE_WINDOWS]
+
 TMP_DIRS_KEY = "__tmp_dirs"
 TMP_DIRS_KEY = "__tmp_dirs"
 
 
 COMPRESSION_FORMAT_GZIP = "gzip"
 COMPRESSION_FORMAT_GZIP = "gzip"
@@ -219,8 +230,15 @@ EXECUTION_TYPE_REPLICA_DISKS_DELETE = "replica_disks_delete"
 EXECUTION_TYPE_REPLICA_DEPLOY = "replica_deploy"
 EXECUTION_TYPE_REPLICA_DEPLOY = "replica_deploy"
 EXECUTION_TYPE_MIGRATION = "migration"
 EXECUTION_TYPE_MIGRATION = "migration"
 EXECUTION_TYPE_REPLICA_UPDATE = "replica_update"
 EXECUTION_TYPE_REPLICA_UPDATE = "replica_update"
-EXECUTION_TYPE_MINION_POOL_MAINTENANCE = "pool_maintenance"
-EXECUTION_TYPE_MINION_POOL_INITIALIZATION = "pool_initialization"
+EXECUTION_TYPE_MINION_POOL_MAINTENANCE = "minion_pool_maintenance"
+EXECUTION_TYPE_MINION_POOL_UPDATE = "minion_pool_update"
+EXECUTION_TYPE_MINION_POOL_SET_UP_SHARED_RESOURCES = (
+    "minion_pool_set_up_shared_resources")
+EXECUTION_TYPE_MINION_POOL_TEAR_DOWN_SHARED_RESOURCES = (
+    "minion_pool_tear_down_shared_resources")
+EXECUTION_TYPE_MINION_POOL_ALLOCATE_MINIONS = "minion_pool_allocate_minions"
+EXECUTION_TYPE_MINION_POOL_DEALLOCATE_MINIONS = (
+    "minion_pool_deallocate_minions")
 
 
 TASK_LOCK_NAME_FORMAT = "task-%s"
 TASK_LOCK_NAME_FORMAT = "task-%s"
 EXECUTION_LOCK_NAME_FORMAT = "execution-%s"
 EXECUTION_LOCK_NAME_FORMAT = "execution-%s"
@@ -239,7 +257,13 @@ EXECUTION_TYPE_TO_ACTION_LOCK_NAME_FORMAT_MAP = {
     EXECUTION_TYPE_REPLICA_UPDATE: REPLICA_LOCK_NAME_FORMAT,
     EXECUTION_TYPE_REPLICA_UPDATE: REPLICA_LOCK_NAME_FORMAT,
     EXECUTION_TYPE_REPLICA_DISKS_DELETE: REPLICA_LOCK_NAME_FORMAT,
     EXECUTION_TYPE_REPLICA_DISKS_DELETE: REPLICA_LOCK_NAME_FORMAT,
     EXECUTION_TYPE_MINION_POOL_MAINTENANCE: MINION_POOL_LOCK_NAME_FORMAT,
     EXECUTION_TYPE_MINION_POOL_MAINTENANCE: MINION_POOL_LOCK_NAME_FORMAT,
-    EXECUTION_TYPE_MINION_POOL_INITIALIZATION: MINION_POOL_LOCK_NAME_FORMAT
+    EXECUTION_TYPE_MINION_POOL_UPDATE: MINION_POOL_LOCK_NAME_FORMAT,
+    EXECUTION_TYPE_MINION_POOL_SET_UP_SHARED_RESOURCES: (
+        MINION_POOL_LOCK_NAME_FORMAT),
+    EXECUTION_TYPE_MINION_POOL_TEAR_DOWN_SHARED_RESOURCES: (
+        MINION_POOL_LOCK_NAME_FORMAT),
+    EXECUTION_TYPE_MINION_POOL_ALLOCATE_MINIONS: MINION_POOL_LOCK_NAME_FORMAT,
+    EXECUTION_TYPE_MINION_POOL_DEALLOCATE_MINIONS: MINION_POOL_LOCK_NAME_FORMAT
 }
 }
 
 
 SERVICE_STATUS_UP = "UP"
 SERVICE_STATUS_UP = "UP"
@@ -254,8 +278,8 @@ REPLICA_CRON_MAIN_MESSAGING_TOPIC = "coriolis_replica_cron_worker"
 
 
 MINION_POOL_STATUS_UNKNOWN = "UNKNOWN"
 MINION_POOL_STATUS_UNKNOWN = "UNKNOWN"
 MINION_POOL_STATUS_UNINITIALIZED = "UNINITIALIZED"
 MINION_POOL_STATUS_UNINITIALIZED = "UNINITIALIZED"
-MINION_POOL_STATUS_AVAILABLE = "AVAILABLE"
 MINION_POOL_STATUS_DEALLOCATED = "DEALLOCATED"
 MINION_POOL_STATUS_DEALLOCATED = "DEALLOCATED"
+MINION_POOL_STATUS_ALLOCATED = "ALLOCATED"
 MINION_POOL_STATUS_RECONFIGURING = "RECONFIGURING"
 MINION_POOL_STATUS_RECONFIGURING = "RECONFIGURING"
 
 
 MINION_MACHINE_IDENTIFIER_FORMAT = "coriolis-pool-%(pool_id)s-minion-%(minion_id)s"
 MINION_MACHINE_IDENTIFIER_FORMAT = "coriolis-pool-%(pool_id)s-minion-%(minion_id)s"

+ 7 - 4
coriolis/db/api.py

@@ -1129,8 +1129,10 @@ def update_minion_machine(context, minion_machine_id, updated_values):
 @enginefacade.writer
 @enginefacade.writer
 def delete_minion_machine(context, minion_machine_id):
 def delete_minion_machine(context, minion_machine_id):
     minion_machine = get_minion_machine(context, minion_machine_id)
     minion_machine = get_minion_machine(context, minion_machine_id)
+    # TODO(aznashwan): update models to be soft-delete-aware to
+    # avoid needing to hard-delete here:
     count = _soft_delete_aware_query(context, models.MinionMachine).filter_by(
     count = _soft_delete_aware_query(context, models.MinionMachine).filter_by(
-        id=minion_machine_id).soft_delete()
+        id=minion_machine_id).delete()
     if count == 0:
     if count == 0:
         raise exception.NotFound("0 MinionMachine entries were soft deleted")
         raise exception.NotFound("0 MinionMachine entries were soft deleted")
 
 
@@ -1167,7 +1169,7 @@ def get_minion_pool_lifecycle(
 @enginefacade.reader
 @enginefacade.reader
 def get_minion_pool_lifecycles(
 def get_minion_pool_lifecycles(
         context, include_tasks_executions=False, include_info=False,
         context, include_tasks_executions=False, include_info=False,
-        to_dict=True):
+        include_machines=False, to_dict=True):
     q = _soft_delete_aware_query(context, models.MinionPoolLifecycle)
     q = _soft_delete_aware_query(context, models.MinionPoolLifecycle)
     if include_tasks_executions:
     if include_tasks_executions:
         q = q.options(orm.joinedload(models.MinionPoolLifecycle.executions))
         q = q.options(orm.joinedload(models.MinionPoolLifecycle.executions))
@@ -1177,7 +1179,8 @@ def get_minion_pool_lifecycles(
     if is_user_context(context):
     if is_user_context(context):
         q = q.filter(
         q = q.filter(
             models.Replica.project_id == context.tenant)
             models.Replica.project_id == context.tenant)
-    q = q.options(orm.joinedload('minion_machines'))
+    if include_machines:
+        q = q.options(orm.joinedload('minion_machines'))
     db_result = q.all()
     db_result = q.all()
     if to_dict:
     if to_dict:
         return [i.to_dict(include_info=include_info) for i in db_result]
         return [i.to_dict(include_info=include_info) for i in db_result]
@@ -1223,7 +1226,7 @@ def update_minion_pool_lifecycle(context, minion_pool_id, updated_values):
     updateable_fields = [
     updateable_fields = [
         "minimum_minions", "maximum_minions", "minion_max_idle_time",
         "minimum_minions", "maximum_minions", "minion_max_idle_time",
         "minion_retention_strategy", "environment_options",
         "minion_retention_strategy", "environment_options",
-        "pool_supporting_resources", "notes", "pool_name"]
+        "pool_shared_resources", "notes", "pool_name", "pool_os_type"]
     # TODO(aznashwan): this should no longer be required when the
     # TODO(aznashwan): this should no longer be required when the
     # transfer action class hirearchy is to be overhauled:
     # transfer action class hirearchy is to be overhauled:
     redundancies = {
     redundancies = {

+ 8 - 1
coriolis/db/sqlalchemy/migrate_repo/versions/016_adds_minion_vm_pools.py

@@ -15,6 +15,11 @@ def upgrade(migrate_engine):
     base_transfer_action = sqlalchemy.Table(
     base_transfer_action = sqlalchemy.Table(
         'base_transfer_action', meta, autoload=True)
         'base_transfer_action', meta, autoload=True)
 
 
+    # extend tasks execution 'type' column:
+    tasks_execution = sqlalchemy.Table(
+        'tasks_execution', meta, autoload=True)
+    tasks_execution.c.type.alter(type=sqlalchemy.String(255))
+
     tables = []
     tables = []
 
 
     # add table for pool lifecycles:
     # add table for pool lifecycles:
@@ -28,11 +33,13 @@ def upgrade(migrate_engine):
                 primary_key=True),
                 primary_key=True),
             sqlalchemy.Column(
             sqlalchemy.Column(
                 "pool_name", sqlalchemy.String(255), nullable=False),
                 "pool_name", sqlalchemy.String(255), nullable=False),
+            sqlalchemy.Column(
+                "pool_os_type", sqlalchemy.String(255), nullable=False),
             sqlalchemy.Column(
             sqlalchemy.Column(
                 "pool_status", sqlalchemy.String(255), nullable=False,
                 "pool_status", sqlalchemy.String(255), nullable=False,
                 default=lambda: "UNKNOWN"),
                 default=lambda: "UNKNOWN"),
             sqlalchemy.Column(
             sqlalchemy.Column(
-                "pool_supporting_resources", sqlalchemy.Text, nullable=True),
+                "pool_shared_resources", sqlalchemy.Text, nullable=True),
             sqlalchemy.Column(
             sqlalchemy.Column(
                 'minimum_minions', sqlalchemy.Integer, nullable=False),
                 'minimum_minions', sqlalchemy.Integer, nullable=False),
             sqlalchemy.Column(
             sqlalchemy.Column(

+ 28 - 4
coriolis/db/sqlalchemy/models.py

@@ -461,6 +461,21 @@ class MinionMachine(BASE, models.TimestampMixin, models.ModelBase,
 
 
     provider_properties = sqlalchemy.Column(types.Json)
     provider_properties = sqlalchemy.Column(types.Json)
 
 
+    def to_dict(self):
+        result = {
+            "id": self.id,
+            "user_id": self.user_id,
+            "project_id": self.project_id,
+            "created_at": self.created_at,
+            "updated_at": self.updated_at,
+            "deleted_at": self.deleted_at,
+            "deleted": self.deleted,
+            "pool_id": self.pool_id,
+            "status": self.status,
+            "connection_info": self.connection_info,
+            "provider_properties": self.provider_properties
+        }
+        return result
 
 
 
 
 class MinionPoolLifecycle(BaseTransferAction):
 class MinionPoolLifecycle(BaseTransferAction):
@@ -478,10 +493,12 @@ class MinionPoolLifecycle(BaseTransferAction):
     pool_name = sqlalchemy.Column(
     pool_name = sqlalchemy.Column(
         sqlalchemy.String(255),
         sqlalchemy.String(255),
         nullable=False)
         nullable=False)
+    pool_os_type = sqlalchemy.Column(
+        sqlalchemy.String(255), nullable=False)
     pool_status = sqlalchemy.Column(
     pool_status = sqlalchemy.Column(
         sqlalchemy.String(255), nullable=False,
         sqlalchemy.String(255), nullable=False,
         default=lambda: constants.MINION_POOL_STATUS_UNKNOWN)
         default=lambda: constants.MINION_POOL_STATUS_UNKNOWN)
-    pool_supporting_resources = sqlalchemy.Column(
+    pool_shared_resources = sqlalchemy.Column(
         types.Json, nullable=True)
         types.Json, nullable=True)
     minimum_minions = sqlalchemy.Column(
     minimum_minions = sqlalchemy.Column(
         sqlalchemy.Integer, nullable=False)
         sqlalchemy.Integer, nullable=False)
@@ -499,18 +516,25 @@ class MinionPoolLifecycle(BaseTransferAction):
     __mapper_args__ = {
     __mapper_args__ = {
         'polymorphic_identity': 'minion_pool_lifecycle'}
         'polymorphic_identity': 'minion_pool_lifecycle'}
 
 
-    def to_dict(self, include_info=True):
+    def to_dict(
+            self, include_info=True, include_machines=True,
+            include_executions=True):
         base = super(MinionPoolLifecycle, self).to_dict(
         base = super(MinionPoolLifecycle, self).to_dict(
-            include_info=include_info)
+            include_info=include_info, include_executions=include_executions)
         base.update({
         base.update({
             "id": self.id,
             "id": self.id,
             "pool_name": self.pool_name,
             "pool_name": self.pool_name,
-            "pool_supporting_resources": self.pool_supporting_resources,
+            "pool_os_type": self.pool_os_type,
+            "pool_shared_resources": self.pool_shared_resources,
             "pool_status": self.pool_status,
             "pool_status": self.pool_status,
             "minimum_minions": self.minimum_minions,
             "minimum_minions": self.minimum_minions,
             "maximum_minions": self.maximum_minions,
             "maximum_minions": self.maximum_minions,
             "minion_max_idle_time": self.minion_max_idle_time,
             "minion_max_idle_time": self.minion_max_idle_time,
             "minion_retention_strategy": self.minion_retention_strategy})
             "minion_retention_strategy": self.minion_retention_strategy})
+        base["minion_machines"] = []
+        if include_machines:
+            base["minion_machines"] = [
+                machine.to_dict() for machine in self.minion_machines]
         # TODO(aznashwan): these nits should be avoided by splitting the
         # TODO(aznashwan): these nits should be avoided by splitting the
         # BaseTransferAction class into a more specialized hireachy:
         # BaseTransferAction class into a more specialized hireachy:
         redundancies = {
         redundancies = {

+ 17 - 10
coriolis/minion_pools/api.py

@@ -10,13 +10,13 @@ class API(object):
         self._rpc_client = rpc_client.ConductorClient()
         self._rpc_client = rpc_client.ConductorClient()
 
 
     def create(
     def create(
-            self, ctxt, name, endpoint_id, environment_options,
+            self, ctxt, name, endpoint_id, pool_os_type, environment_options,
             minimum_minions, maximum_minions, minion_max_idle_time,
             minimum_minions, maximum_minions, minion_max_idle_time,
             minion_retention_strategy, notes=None):
             minion_retention_strategy, notes=None):
         return self._rpc_client.create_minion_pool(
         return self._rpc_client.create_minion_pool(
-            ctxt, name, endpoint_id, environment_options, minimum_minions,
-            maximum_minions, minion_max_idle_time, minion_retention_strategy,
-            notes=notes)
+            ctxt, name, endpoint_id, pool_os_type, environment_options,
+            minimum_minions, maximum_minions, minion_max_idle_time,
+            minion_retention_strategy, notes=notes)
 
 
     def update(self, ctxt, minion_pool_id, updated_values):
     def update(self, ctxt, minion_pool_id, updated_values):
         return self._rpc_client.update_minion_pool(
         return self._rpc_client.update_minion_pool(
@@ -31,11 +31,18 @@ class API(object):
     def get_minion_pool(self, ctxt, minion_pool_id):
     def get_minion_pool(self, ctxt, minion_pool_id):
         return self._rpc_client.get_minion_pool(ctxt, minion_pool_id)
         return self._rpc_client.get_minion_pool(ctxt, minion_pool_id)
 
 
-    def initialize(self, ctxt, minion_pool_id):
-        return self._rpc_client.initialize_minion_pool(ctxt, minion_pool_id)
+    def set_up_shared_pool_resources(self, ctxt, minion_pool_id):
+        return self._rpc_client.set_up_shared_minion_pool_resources(
+            ctxt, minion_pool_id)
+
+    def tear_down_shared_pool_resources(self, ctxt, minion_pool_id):
+        return self._rpc_client.tear_down_shared_minion_pool_resources(
+            ctxt, minion_pool_id)
 
 
-    def allocate(self, ctxt, minion_pool_id):
-        return self._rpc_client.allocate_minion_pool(ctxt, minion_pool_id)
+    def allocate_machines(self, ctxt, minion_pool_id):
+        return self._rpc_client.allocate_minion_pool_machines(
+            ctxt, minion_pool_id)
 
 
-    def deallocate(self, ctxt, minion_pool_id):
-        return self._rpc_client.deallocate_minion_pool(ctxt, minion_pool_id)
+    def deallocate_machines(self, ctxt, minion_pool_id):
+        return self._rpc_client.deallocate_minion_pool_machines(
+            ctxt, minion_pool_id)

+ 21 - 11
coriolis/policies/minion_pools.py

@@ -20,7 +20,7 @@ MINION_POOLS_DEFAULT_RULES = [
     policy.DocumentedRuleDefault(
     policy.DocumentedRuleDefault(
         get_minion_pools_policy_label('create'),
         get_minion_pools_policy_label('create'),
         MINION_POOLS_DEFAULT_RULE,
         MINION_POOLS_DEFAULT_RULE,
-        "Create a minion_pool",
+        "Create a minion pool",
         [
         [
             {
             {
                 "path": "/minion_pools",
                 "path": "/minion_pools",
@@ -42,7 +42,7 @@ MINION_POOLS_DEFAULT_RULES = [
     policy.DocumentedRuleDefault(
     policy.DocumentedRuleDefault(
         get_minion_pools_policy_label('show'),
         get_minion_pools_policy_label('show'),
         MINION_POOLS_DEFAULT_RULE,
         MINION_POOLS_DEFAULT_RULE,
-        "Show details for minion_pool",
+        "Show details for minion pool",
         [
         [
             {
             {
                 "path": "/minion_pools/{minion_pool_id}",
                 "path": "/minion_pools/{minion_pool_id}",
@@ -53,7 +53,7 @@ MINION_POOLS_DEFAULT_RULES = [
     policy.DocumentedRuleDefault(
     policy.DocumentedRuleDefault(
         get_minion_pools_policy_label('update'),
         get_minion_pools_policy_label('update'),
         MINION_POOLS_DEFAULT_RULE,
         MINION_POOLS_DEFAULT_RULE,
-        "Update details for minion_pool",
+        "Update details for minion pool",
         [
         [
             {
             {
                 "path": "/minion_pools/{minion_pool_id}",
                 "path": "/minion_pools/{minion_pool_id}",
@@ -64,7 +64,7 @@ MINION_POOLS_DEFAULT_RULES = [
     policy.DocumentedRuleDefault(
     policy.DocumentedRuleDefault(
         get_minion_pools_policy_label('delete'),
         get_minion_pools_policy_label('delete'),
         MINION_POOLS_DEFAULT_RULE,
         MINION_POOLS_DEFAULT_RULE,
-        "Delete minion_pool",
+        "Delete minion pool",
         [
         [
             {
             {
                 "path": "/minion_pools/{minion_pool_id}",
                 "path": "/minion_pools/{minion_pool_id}",
@@ -73,9 +73,20 @@ MINION_POOLS_DEFAULT_RULES = [
         ]
         ]
     ),
     ),
     policy.DocumentedRuleDefault(
     policy.DocumentedRuleDefault(
-        get_minion_pools_policy_label('initialize'),
+        get_minion_pools_policy_label('set_up_shared_resources'),
         MINION_POOLS_DEFAULT_RULE,
         MINION_POOLS_DEFAULT_RULE,
-        "Initialize Minion Pool",
+        "Set up shared minion pool resources",
+        [
+            {
+                "path": "/minion_pools/{minion_pool_id}/actions",
+                "method": "POST"
+            }
+        ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_minion_pools_policy_label('tear_down_shared_resources'),
+        MINION_POOLS_DEFAULT_RULE,
+        "Tear down shared minion pool resources",
         [
         [
             {
             {
                 "path": "/minion_pools/{minion_pool_id}/actions",
                 "path": "/minion_pools/{minion_pool_id}/actions",
@@ -83,11 +94,10 @@ MINION_POOLS_DEFAULT_RULES = [
             }
             }
         ]
         ]
     ),
     ),
-
     policy.DocumentedRuleDefault(
     policy.DocumentedRuleDefault(
-        get_minion_pools_policy_label('allocate'),
+        get_minion_pools_policy_label('allocate_machines'),
         MINION_POOLS_DEFAULT_RULE,
         MINION_POOLS_DEFAULT_RULE,
-        "Allocate Minion Pool",
+        "Allocate Minion Pool machines",
         [
         [
             {
             {
                 "path": "/minion_pools/{minion_pool_id}/actions",
                 "path": "/minion_pools/{minion_pool_id}/actions",
@@ -96,9 +106,9 @@ MINION_POOLS_DEFAULT_RULES = [
         ]
         ]
     ),
     ),
     policy.DocumentedRuleDefault(
     policy.DocumentedRuleDefault(
-        get_minion_pools_policy_label('deallocate'),
+        get_minion_pools_policy_label('deallocate_machines'),
         MINION_POOLS_DEFAULT_RULE,
         MINION_POOLS_DEFAULT_RULE,
-        "Deallocate Minion Pool",
+        "Deallocate Minion Pool machines",
         [
         [
             {
             {
                 "path": "/minion_pools/{minion_pool_id}/actions",
                 "path": "/minion_pools/{minion_pool_id}/actions",

+ 14 - 18
coriolis/providers/base.py

@@ -552,8 +552,8 @@ class BaseMinionPoolProvider(
 
 
     @abc.abstractmethod
     @abc.abstractmethod
     def validate_minion_compatibility_for_transfer(
     def validate_minion_compatibility_for_transfer(
-            self, ctxt, connection_info, environment_options,
-            transfer_options, storage_mappings):
+            self, ctxt, connection_info, export_info, environment_options,
+            minion_properties):
         """ Validates compatibility between the pool's options and the options
         """ Validates compatibility between the pool's options and the options
         selected for a given transfer. Should raise if any options related to
         selected for a given transfer. Should raise if any options related to
         the minions in the pool might be deemed incompatible with the desited
         the minions in the pool might be deemed incompatible with the desited
@@ -562,13 +562,13 @@ class BaseMinionPoolProvider(
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
-    def validate_pool_options(
+    def validate_minion_pool_options(
             self, ctxt, connection_info, environment_options):
             self, ctxt, connection_info, environment_options):
         """ Validates the provided pool options. """
         """ Validates the provided pool options. """
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
-    def setup_pool_supporting_resources(
+    def set_up_pool_shared_resources(
             self, ctxt, connection_info, environment_options, pool_identifier):
             self, ctxt, connection_info, environment_options, pool_identifier):
         """ Sets up supporting resources which can be re-used amongst the
         """ Sets up supporting resources which can be re-used amongst the
         machines which will be spawned within the pool (e.g. a shared network)
         machines which will be spawned within the pool (e.g. a shared network)
@@ -576,44 +576,40 @@ class BaseMinionPoolProvider(
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
-    def teardown_pool_supporting_resources(
+    def tear_down_pool_shared_resources(
             self, ctxt, connection_info, environment_options,
             self, ctxt, connection_info, environment_options,
-            pool_supporting_resources):
+            pool_shared_resources):
         """ Tears down all pool supporting resources. """
         """ Tears down all pool supporting resources. """
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
     def create_minion(
     def create_minion(
             self, ctxt, connection_info, environment_options,
             self, ctxt, connection_info, environment_options,
+            pool_identifier, pool_shared_resources,
             new_minion_identifier):
             new_minion_identifier):
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
     def delete_minion(
     def delete_minion(
-            self, ctxt, connection_info, environment_options,
-            minion_properties):
+            self, ctxt, connection_info, minion_properties):
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
     def shutdown_minion(
     def shutdown_minion(
-            self, ctxt, connection_info, environment_options,
-            minion_properties):
+            self, ctxt, connection_info, minion_properties):
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
     def start_minion(
     def start_minion(
-            self, ctxt, connection_info, environment_options,
-            minion_properties):
+            self, ctxt, connection_info, minion_properties):
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
-    def attach_volume_to_minion(
-            self, ctxt, connection_info, environment_options,
-            minion_properties, volume_info):
+    def attach_volumes_to_minion(
+            self, ctxt, connection_info, minion_properties, volumes_info):
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
-    def detach_volume_from_minion(
-            self, ctxt, connection_info, environment_options,
-            minion_properties, volume_info):
+    def detach_volumes_from_minion(
+            self, ctxt, connection_info, minion_properties, volumes_info):
         pass
         pass

+ 14 - 2
coriolis/tasks/factory.py

@@ -90,9 +90,21 @@ _TASKS_MAP = {
     constants.TASK_TYPE_DELETE_MINION:
     constants.TASK_TYPE_DELETE_MINION:
         minion_pool_tasks.DeleteMinionTask,
         minion_pool_tasks.DeleteMinionTask,
     constants.TASK_TYPE_SET_UP_SHARED_POOL_RESOURCES:
     constants.TASK_TYPE_SET_UP_SHARED_POOL_RESOURCES:
-        minion_pool_tasks.SetupPoolSupportingResourcesTask,
+        minion_pool_tasks.SetUpPoolSupportingResourcesTask,
     constants.TASK_TYPE_TEAR_DOWN_SHARED_POOL_RESOURCES:
     constants.TASK_TYPE_TEAR_DOWN_SHARED_POOL_RESOURCES:
-        minion_pool_tasks.TeardownPoolSupportingResourcesTask
+        minion_pool_tasks.TearDownPoolSupportingResourcesTask,
+    constants.TASK_TYPE_ATTACH_VOLUMES_TO_SOURCE_MINION:
+        minion_pool_tasks.AttachVolumesToSourceMinionTask,
+    constants.TASK_TYPE_DETACH_VOLUMES_FROM_SOURCE_MINION:
+        minion_pool_tasks.DetachVolumesFromSourceMinionTask,
+    constants.TASK_TYPE_ATTACH_VOLUMES_TO_DESTINATION_MINION:
+        minion_pool_tasks.AttachVolumesToDestinationMinionTask,
+    constants.TASK_TYPE_DETACH_VOLUMES_FROM_DESTINATION_MINION:
+        minion_pool_tasks.DetachVolumesFromDestinationMinionTask,
+    constants.TASK_TYPE_VALIDATE_SOURCE_MINION_POOL_COMPATIBILITY:
+        minion_pool_tasks.ValidateSourceMinionCompatibilityTask,
+    constants.TASK_TYPE_VALIDATE_DESTINATION_MINION_POOL_COMPATIBILITY:
+        minion_pool_tasks.ValidateDestinationMinionCompatibilityTask
 }
 }
 
 
 
 

+ 243 - 18
coriolis/tasks/minion_pool_tasks.py

@@ -4,6 +4,7 @@
 from oslo_log import log as logging
 from oslo_log import log as logging
 
 
 from coriolis import constants
 from coriolis import constants
+from coriolis import exception
 from coriolis.providers import factory as providers_factory
 from coriolis.providers import factory as providers_factory
 from coriolis.tasks import base
 from coriolis.tasks import base
 
 
@@ -51,7 +52,7 @@ class ValidateMinionPoolOptionsTask(base.TaskRunner):
             event_handler)
             event_handler)
 
 
         environment_options = task_info['pool_environment_options']
         environment_options = task_info['pool_environment_options']
-        provider.validate_pool_options(
+        provider.validate_minion_pool_options(
             ctxt, connection_info, environment_options)
             ctxt, connection_info, environment_options)
 
 
         return {}
         return {}
@@ -69,7 +70,9 @@ class CreateMinionTask(base.TaskRunner):
 
 
     @classmethod
     @classmethod
     def get_required_task_info_properties(cls):
     def get_required_task_info_properties(cls):
-        return ["pool_environment_options"]
+        return [
+            "pool_environment_options", "pool_shared_resources",
+            "pool_identifier"]
 
 
     @classmethod
     @classmethod
     def get_returned_task_info_properties(cls):
     def get_returned_task_info_properties(cls):
@@ -95,15 +98,28 @@ class CreateMinionTask(base.TaskRunner):
             destination["type"], constants.PROVIDER_TYPE_MINION_POOL,
             destination["type"], constants.PROVIDER_TYPE_MINION_POOL,
             event_handler)
             event_handler)
 
 
+        pool_identifier = task_info['pool_identifier']
         environment_options = task_info['pool_environment_options']
         environment_options = task_info['pool_environment_options']
+        pool_shared_resources = task_info['pool_shared_resources']
         minion_properties = provider.create_minion(
         minion_properties = provider.create_minion(
-            ctxt, connection_info, environment_options, minion_pool_machine_id)
+            ctxt, connection_info, environment_options, pool_identifier,
+            pool_shared_resources, minion_pool_machine_id)
+
+        missing = [
+            key for key in [
+                "minion_connection_info", "minion_provider_properties"]
+            if key not in minion_properties]
+        if missing:
+            LOG.warn(
+                "Provider of type '%s' failed to return the following minion "
+                "property keys: %s. Allowing run to completion for later "
+                "cleanup.")
 
 
         return {
         return {
-            "minion_connection_info": minion_properties[
-                "minion_connection_info"],
-            "minion_provider_properties": minion_properties[
-                "minion_provider_properties"]}
+            "minion_connection_info": minion_properties.get(
+                "minion_connection_info"),
+            "minion_provider_properties": minion_properties.get(
+                "minion_provider_properties")}
 
 
 
 
 class DeleteMinionTask(base.TaskRunner):
 class DeleteMinionTask(base.TaskRunner):
@@ -155,7 +171,7 @@ class DeleteMinionTask(base.TaskRunner):
             "minion_connection_info": None}
             "minion_connection_info": None}
 
 
 
 
-class SetupPoolSupportingResourcesTask(base.TaskRunner):
+class SetUpPoolSupportingResourcesTask(base.TaskRunner):
 
 
     @classmethod
     @classmethod
     def get_required_platform(cls):
     def get_required_platform(cls):
@@ -171,7 +187,7 @@ class SetupPoolSupportingResourcesTask(base.TaskRunner):
 
 
     @classmethod
     @classmethod
     def get_returned_task_info_properties(cls):
     def get_returned_task_info_properties(cls):
-        return ["pool_supporting_resources"]
+        return ["pool_shared_resources"]
 
 
     @classmethod
     @classmethod
     def get_required_provider_types(cls):
     def get_required_provider_types(cls):
@@ -195,13 +211,13 @@ class SetupPoolSupportingResourcesTask(base.TaskRunner):
 
 
         pool_identifier = task_info['pool_identifier']
         pool_identifier = task_info['pool_identifier']
         environment_options = task_info['pool_environment_options']
         environment_options = task_info['pool_environment_options']
-        pool_supporting_resources = provider.setup_pool_supporting_resources(
+        pool_shared_resources = provider.set_up_pool_shared_resources(
             ctxt, connection_info, environment_options, pool_identifier)
             ctxt, connection_info, environment_options, pool_identifier)
 
 
-        return {"pool_supporting_resources": pool_supporting_resources}
+        return {"pool_shared_resources": pool_shared_resources}
 
 
 
 
-class TeardownPoolSupportingResourcesTask(base.TaskRunner):
+class TearDownPoolSupportingResourcesTask(base.TaskRunner):
 
 
     @classmethod
     @classmethod
     def get_required_platform(cls):
     def get_required_platform(cls):
@@ -213,11 +229,11 @@ class TeardownPoolSupportingResourcesTask(base.TaskRunner):
 
 
     @classmethod
     @classmethod
     def get_required_task_info_properties(cls):
     def get_required_task_info_properties(cls):
-        return ["pool_environment_options", "pool_supporting_resources"]
+        return ["pool_environment_options", "pool_shared_resources"]
 
 
     @classmethod
     @classmethod
     def get_returned_task_info_properties(cls):
     def get_returned_task_info_properties(cls):
-        return ["pool_supporting_resources"]
+        return ["pool_shared_resources"]
 
 
     @classmethod
     @classmethod
     def get_required_provider_types(cls):
     def get_required_provider_types(cls):
@@ -240,9 +256,218 @@ class TeardownPoolSupportingResourcesTask(base.TaskRunner):
             event_handler)
             event_handler)
 
 
         environment_options = task_info['pool_environment_options']
         environment_options = task_info['pool_environment_options']
-        pool_supporting_resources = task_info['pool_supporting_resources']
-        provider.teardown_pool_supporting_resources(
+        pool_shared_resources = task_info['pool_shared_resources']
+        provider.tear_down_pool_shared_resources(
             ctxt, connection_info, environment_options,
             ctxt, connection_info, environment_options,
-            pool_supporting_resources)
+            pool_shared_resources)
+
+        return {"pool_shared_resources": None}
+
+
+class _BaseVolumesMinionMachineAttachmentTask(base.TaskRunner):
+
+    @classmethod
+    def get_required_platform(cls):
+        raise NotImplementedError(
+            "No minion disk attachment platform specified")
+
+    @classmethod
+    def get_required_task_info_properties(cls):
+        return ["volumes_info", cls._get_minion_properties_task_info_field()]
+
+    @classmethod
+    def get_returned_task_info_properties(cls):
+        return ["volumes_info", cls._get_minion_properties_task_info_field()]
+
+    @classmethod
+    def get_required_provider_types(cls):
+        return {
+            cls.get_required_platform(): [constants.PROVIDER_TYPE_MINION_POOL]}
+
+    @classmethod
+    def _get_minion_properties_task_info_field(cls):
+        raise NotImplementedError(
+            "No minion disk attachment task info field specified.")
+
+    @classmethod
+    def _get_provider_disk_operation(cls, provider):
+        raise NotImplementedError(
+            "No minion disk attachment provider operation specified.")
+
+    def _run(self, ctxt, instance, origin, destination,
+             task_info, event_handler):
+
+        platform_to_target = None
+        required_platform = self.get_required_platform()
+        if required_platform == constants.TASK_PLATFORM_SOURCE:
+            platform_to_target = origin
+        elif required_platform == constants.TASK_PLATFORM_DESTINATION:
+            platform_to_target = destination
+        else:
+            raise NotImplementedError(
+                "Unknown minion pool disk operation platform '%s'" % (
+                    required_platform))
+
+        connection_info = base.get_connection_info(ctxt, platform_to_target)
+        provider = providers_factory.get_provider(
+            platform_to_target["type"], constants.PROVIDER_TYPE_MINION_POOL,
+            event_handler)
+
+        volumes_info = task_info["volumes_info"]
+        minion_properties = task_info[
+            self._get_minion_properties_task_info_field()]
+        res = self._get_provider_disk_operation(provider)(
+            ctxt, connection_info, minion_properties, volumes_info)
+
+        missing_result_props = [
+            prop for prop in ["volumes_info", "minion_properties"]
+            if prop not in res]
+        if missing_result_props:
+            raise exception.CoriolisException(
+                "The following properties were missing from minion disk "
+                "operation '%s' from platform '%s'." % (
+                    self._get_provider_disk_operation.__name__,
+                    platform_to_target))
+
+        return {
+            "volumes_info": res['volumes_info'],
+            self._get_minion_properties_task_info_field(): res[
+                "minion_properties"]}
+
+
+class AttachVolumesToSourceMinionTask(_BaseVolumesMinionMachineAttachmentTask):
+
+    @classmethod
+    def get_required_platform(cls):
+        return constants.TASK_PLATFORM_SOURCE
+
+    @classmethod
+    def _get_minion_properties_task_info_field(cls):
+        return "source_minion_provider_properties"
+
+    @classmethod
+    def _get_provider_disk_operation(cls, provider):
+        return provider.attach_volumes_to_minion
+
+
+class DetachVolumesFromSourceMinionTask(AttachVolumesToSourceMinionTask):
+
+    @classmethod
+    def _get_provider_disk_operation(cls, provider):
+        return provider.detach_volumes_from_minion
+
+
+class AttachVolumesToDestinationMinionTask(_BaseVolumesMinionMachineAttachmentTask):
+
+    @classmethod
+    def get_required_platform(cls):
+        return constants.TASK_PLATFORM_DESTINATION
 
 
-        return {"pool_supporting_resources": None}
+    @classmethod
+    def _get_minion_properties_task_info_field(cls):
+        return "destination_minion_provider_properties"
+
+    @classmethod
+    def _get_provider_disk_operation(cls, provider):
+        return provider.attach_volumes_to_minion
+
+
+class DetachVolumesFromDestinationMinionTask(AttachVolumesToDestinationMinionTask):
+
+    @classmethod
+    def _get_provider_disk_operation(cls, provider):
+        return provider.detach_volumes_from_minion
+
+
+class _BaseValidateMinionCompatibilityTask(base.TaskRunner):
+
+    @classmethod
+    def get_required_platform(cls):
+        raise NotImplementedError(
+            "No minion validation platform specified")
+
+    @classmethod
+    def get_required_task_info_properties(cls):
+        return [
+            "export_info",
+            cls._get_transfer_properties_task_info_field(),
+            cls._get_minion_properties_task_info_field()]
+
+    @classmethod
+    def get_returned_task_info_properties(cls):
+        return []
+
+    @classmethod
+    def get_required_provider_types(cls):
+        return {
+            cls.get_required_platform(): [constants.PROVIDER_TYPE_MINION_POOL]}
+
+    @classmethod
+    def _get_transfer_properties_task_info_field(cls):
+        platform = cls.get_required_platform()
+        if platform == constants.PROVIDER_PLATFORM_SOURCE:
+            return "source_environment"
+        elif platform == constants.PROVIDER_PLATFORM_DESTINATION:
+            return "target_environment"
+        raise exception.CoriolisException(
+            "Unknown minion pool validation operation platform '%s'" % (
+                platform))
+
+    @classmethod
+    def _get_minion_properties_task_info_field(cls):
+        raise NotImplementedError(
+            "No minion validation task info field specified.")
+
+    def _run(self, ctxt, instance, origin, destination,
+             task_info, event_handler):
+
+        platform_to_target = None
+        required_platform = self.get_required_platform()
+        if required_platform == constants.TASK_PLATFORM_SOURCE:
+            platform_to_target = origin
+        elif required_platform == constants.TASK_PLATFORM_DESTINATION:
+            platform_to_target = destination
+        else:
+            raise NotImplementedError(
+                "Unknown minion pool validation operation platform '%s'" % (
+                    required_platform))
+
+        connection_info = base.get_connection_info(ctxt, platform_to_target)
+        provider = providers_factory.get_provider(
+            platform_to_target["type"], constants.PROVIDER_TYPE_MINION_POOL,
+            event_handler)
+
+        export_info = task_info["export_info"]
+        minion_properties = task_info[
+            self._get_minion_properties_task_info_field()]
+        transfer_properties = [
+            self._get_transfer_properties_task_info_field()]
+        provider.validate_minion_compatibility_for_transfer(
+            ctxt, connection_info, export_info, transfer_properties,
+            minion_properties)
+
+        return {}
+
+
+class ValidateSourceMinionCompatibilityTask(
+        _BaseValidateMinionCompatibilityTask):
+
+    @classmethod
+    def get_required_platform(cls):
+        return constants.PROVIDER_PLATFORM_SOURCE
+
+    @classmethod
+    def _get_minion_properties_task_info_field(cls):
+        return "source_minion_provider_properties"
+
+
+class ValidateDestinationMinionCompatibilityTask(
+        _BaseValidateMinionCompatibilityTask):
+
+    @classmethod
+    def get_required_platform(cls):
+        return constants.PROVIDER_PLATFORM_DESTINATION
+
+    @classmethod
+    def _get_minion_properties_task_info_field(cls):
+        return "destination_minion_provider_properties"