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()
         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.can(
-            minion_pool_policies.get_minion_pools_policy_label("initialize"))
+            minion_pool_policies.get_minion_pools_policy_label(
+                "set_up_shared_resources"))
         try:
             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:
             raise exc.HTTPNotFound(explanation=ex.msg)
         except exception.InvalidParameterValue as ex:
             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.can(
-            minion_pool_policies.get_minion_pools_policy_label("allocate"))
+            minion_pool_policies.get_minion_pools_policy_label(
+                "tear_down_shared_resources"))
         try:
             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:
             raise exc.HTTPNotFound(explanation=ex.msg)
         except exception.InvalidParameterValue as ex:
             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.can(
-            minion_pool_policies.get_minion_pools_policy_label("deallocate"))
+            minion_pool_policies.get_minion_pools_policy_label(
+                "allocate_machines"))
         try:
             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:
             raise exc.HTTPNotFound(explanation=ex.msg)
         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 webob import exc
 
+from coriolis import constants
 from coriolis import exception
 from coriolis.api.v1.views import minion_pool_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"]
             name = minion_pool["pool_name"]
             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"]
             self._endpoints_api.validate_endpoint_minion_pool_options(
                 ctxt, endpoint_id, environment_options)
@@ -53,8 +60,8 @@ class MinionPoolController(api_wsgi.Controller):
                 "minion_retention_strategy")
             notes = minion_pool.get("notes")
             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)
         except Exception as ex:
             LOG.exception(ex)
@@ -67,14 +74,14 @@ class MinionPoolController(api_wsgi.Controller):
     def create(self, req, body):
         context = req.environ["coriolis.context"]
         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,
          notes) = (
             self._validate_create_body(context, body))
         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):
         try:
@@ -82,7 +89,7 @@ class MinionPoolController(api_wsgi.Controller):
             vals = {k: minion_pool[k] for k in minion_pool.keys() &
                     {"name", "environment_options", "minimum_minions",
                      "maximum_minions", "minion_max_idle_time",
-                     "minion_retention_strategy", "notes"}}
+                     "minion_retention_strategy", "notes", "pool_os_type"}}
             if 'environment_options' in vals:
                 minion_pool = self._minion_pool_api.get_minion_pool(
                     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:
             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
 
 

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

@@ -374,28 +374,36 @@ class ConductorClient(object):
             ctxt, 'delete_service', service_id=service_id)
 
     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,
             minion_retention_strategy, notes=None):
         return self._client.call(
             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,
             minion_max_idle_time=minion_max_idle_time,
             minion_retention_strategy=minion_retention_strategy,
             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(
-            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(
-            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(
-            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):
         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)
             if not still_running:
                 LOG.info(
-                    "Updating 'pool_supporting_resources' for pool %s after "
+                    "Updating 'pool_shared_resources' for pool %s after "
                     "completion of task '%s' (type '%s').",
                     execution.action_id, task.id, task_type)
                 db_api.update_minion_pool_lifecycle(
                     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(
                     ctxt, execution.action_id,
                     constants.MINION_POOL_STATUS_DEALLOCATED)
@@ -2361,49 +2361,50 @@ class ConductorServerEndpoint(object):
             still_running = _check_other_tasks_running(execution, task)
             if not still_running:
                 LOG.info(
-                    "Clearing 'pool_supporting_resources' for pool %s following "
+                    "Clearing 'pool_shared_resources' for pool %s following "
                     "completion of task '%s' (type %s)",
                     execution.action_id, task.id, task_type)
                 db_api.update_minion_pool_lifecycle(
                     ctxt, execution.action_id, {
-                        "pool_supporting_resources": {}})
+                        "pool_shared_resources": {}})
                 db_api.set_minion_pool_lifecycle_status(
                     ctxt, execution.action_id,
                     constants.MINION_POOL_STATUS_UNINITIALIZED)
 
         elif task_type == constants.TASK_TYPE_CREATE_MINION:
             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).",
                 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)
             if not still_running:
                 db_api.set_minion_pool_lifecycle_status(
                     ctxt, execution.action_id,
-                    constants.MINION_POOL_STATUS_AVAILABLE)
+                    constants.MINION_POOL_STATUS_ALLOCATED)
 
         elif task_type == constants.TASK_TYPE_DELETE_MINION:
             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:
             LOG.debug(
@@ -2714,6 +2715,7 @@ class ConductorServerEndpoint(object):
             else:
                 self._cancel_tasks_execution(ctxt, execution)
 
+
             # NOTE: if this was a migration, make sure to delete
             # its associated reservation.
             if execution.type == constants.EXECUTION_TYPE_MIGRATION:
@@ -3017,7 +3019,7 @@ class ConductorServerEndpoint(object):
         db_api.delete_service(ctxt, service_id)
 
     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,
             minion_retention_strategy, notes=None):
         endpoint = db_api.get_endpoint(ctxt, endpoint_id)
@@ -3026,6 +3028,7 @@ class ConductorServerEndpoint(object):
         minion_pool.id = str(uuid.uuid4())
         minion_pool.pool_name = name
         minion_pool.notes = notes
+        minion_pool.pool_os_type = pool_os_type
         minion_pool.pool_status = constants.MINION_POOL_STATUS_UNINITIALIZED
         minion_pool.minimum_minions = minimum_minions
         minion_pool.maximum_minions = maximum_minions
@@ -3044,25 +3047,34 @@ class ConductorServerEndpoint(object):
         db_api.add_minion_pool_lifecycle(ctxt, minion_pool)
         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(
-            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:
             raise exception.NotFound(
                 "Minion pool with ID '%s' not found." % minion_pool_id)
         return minion_pool
 
     @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:
             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,
                     constants.MINION_POOL_STATUS_UNINITIALIZED))
 
@@ -3070,9 +3082,11 @@ class ConductorServerEndpoint(object):
         execution.id = str(uuid.uuid4())
         execution.action = minion_pool
         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] = {
+            "pool_os_type": minion_pool.pool_os_type,
             "pool_identifier": minion_pool.id,
             # TODO(aznashwan): remove redundancy once transfer
             # action DB models have been overhauled:
@@ -3098,20 +3112,68 @@ class ConductorServerEndpoint(object):
 
         # add new execution to DB:
         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)
         return self._get_minion_pool_lifecycle_execution(
             ctxt, minion_pool_id, execution.id).to_dict()
 
     @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)
-        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:
             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,
                     constants.MINION_POOL_STATUS_DEALLOCATED))
 
@@ -3119,35 +3181,44 @@ class ConductorServerEndpoint(object):
         execution.id = str(uuid.uuid4())
         execution.action = minion_pool
         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] = {
-                "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(
                 minion_machine_id,
@@ -3168,7 +3239,7 @@ class ConductorServerEndpoint(object):
         self._check_execution_tasks_sanity(execution, minion_pool.info)
 
         # 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(
                 ctxt, minion_pool.id, minion_machine_id,
                 minion_pool.info[minion_machine_id])
@@ -3182,14 +3253,18 @@ class ConductorServerEndpoint(object):
             ctxt, minion_pool_id, execution.id).to_dict()
 
     @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)
-        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 (
-                constants.MINION_POOL_STATUS_AVAILABLE):
+                constants.MINION_POOL_STATUS_ALLOCATED):
             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
         # executions/allocated machines
@@ -3198,27 +3273,33 @@ class ConductorServerEndpoint(object):
         execution.id = str(uuid.uuid4())
         execution.action = minion_pool
         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:
             minion_machine_id = 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(
                 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)
 
         # 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(
-                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:
         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)
         return self._get_minion_pool_lifecycle_execution(
@@ -3226,10 +3307,23 @@ class ConductorServerEndpoint(object):
 
     @minion_pool_synchronized
     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
     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(
             "Attempting to update minion_pool '%s' with payload: %s",
             minion_pool_id, updated_values)
@@ -3239,8 +3333,19 @@ class ConductorServerEndpoint(object):
 
     @minion_pool_synchronized
     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)
 
     @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_STOP_MINION = "STOP_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_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_DESTINATION = "destination"
@@ -204,6 +212,9 @@ OS_TYPE_UNKNOWN = "unknown"
 
 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"
 
 COMPRESSION_FORMAT_GZIP = "gzip"
@@ -219,8 +230,15 @@ EXECUTION_TYPE_REPLICA_DISKS_DELETE = "replica_disks_delete"
 EXECUTION_TYPE_REPLICA_DEPLOY = "replica_deploy"
 EXECUTION_TYPE_MIGRATION = "migration"
 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"
 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_DISKS_DELETE: REPLICA_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"
@@ -254,8 +278,8 @@ REPLICA_CRON_MAIN_MESSAGING_TOPIC = "coriolis_replica_cron_worker"
 
 MINION_POOL_STATUS_UNKNOWN = "UNKNOWN"
 MINION_POOL_STATUS_UNINITIALIZED = "UNINITIALIZED"
-MINION_POOL_STATUS_AVAILABLE = "AVAILABLE"
 MINION_POOL_STATUS_DEALLOCATED = "DEALLOCATED"
+MINION_POOL_STATUS_ALLOCATED = "ALLOCATED"
 MINION_POOL_STATUS_RECONFIGURING = "RECONFIGURING"
 
 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
 def delete_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(
-        id=minion_machine_id).soft_delete()
+        id=minion_machine_id).delete()
     if count == 0:
         raise exception.NotFound("0 MinionMachine entries were soft deleted")
 
@@ -1167,7 +1169,7 @@ def get_minion_pool_lifecycle(
 @enginefacade.reader
 def get_minion_pool_lifecycles(
         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)
     if include_tasks_executions:
         q = q.options(orm.joinedload(models.MinionPoolLifecycle.executions))
@@ -1177,7 +1179,8 @@ def get_minion_pool_lifecycles(
     if is_user_context(context):
         q = q.filter(
             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()
     if to_dict:
         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 = [
         "minimum_minions", "maximum_minions", "minion_max_idle_time",
         "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
     # transfer action class hirearchy is to be overhauled:
     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', 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 = []
 
     # add table for pool lifecycles:
@@ -28,11 +33,13 @@ def upgrade(migrate_engine):
                 primary_key=True),
             sqlalchemy.Column(
                 "pool_name", sqlalchemy.String(255), nullable=False),
+            sqlalchemy.Column(
+                "pool_os_type", sqlalchemy.String(255), nullable=False),
             sqlalchemy.Column(
                 "pool_status", sqlalchemy.String(255), nullable=False,
                 default=lambda: "UNKNOWN"),
             sqlalchemy.Column(
-                "pool_supporting_resources", sqlalchemy.Text, nullable=True),
+                "pool_shared_resources", sqlalchemy.Text, nullable=True),
             sqlalchemy.Column(
                 'minimum_minions', sqlalchemy.Integer, nullable=False),
             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)
 
+    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):
@@ -478,10 +493,12 @@ class MinionPoolLifecycle(BaseTransferAction):
     pool_name = sqlalchemy.Column(
         sqlalchemy.String(255),
         nullable=False)
+    pool_os_type = sqlalchemy.Column(
+        sqlalchemy.String(255), nullable=False)
     pool_status = sqlalchemy.Column(
         sqlalchemy.String(255), nullable=False,
         default=lambda: constants.MINION_POOL_STATUS_UNKNOWN)
-    pool_supporting_resources = sqlalchemy.Column(
+    pool_shared_resources = sqlalchemy.Column(
         types.Json, nullable=True)
     minimum_minions = sqlalchemy.Column(
         sqlalchemy.Integer, nullable=False)
@@ -499,18 +516,25 @@ class MinionPoolLifecycle(BaseTransferAction):
     __mapper_args__ = {
         '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(
-            include_info=include_info)
+            include_info=include_info, include_executions=include_executions)
         base.update({
             "id": self.id,
             "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,
             "minimum_minions": self.minimum_minions,
             "maximum_minions": self.maximum_minions,
             "minion_max_idle_time": self.minion_max_idle_time,
             "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
         # BaseTransferAction class into a more specialized hireachy:
         redundancies = {

+ 17 - 10
coriolis/minion_pools/api.py

@@ -10,13 +10,13 @@ class API(object):
         self._rpc_client = rpc_client.ConductorClient()
 
     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,
             minion_retention_strategy, notes=None):
         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):
         return self._rpc_client.update_minion_pool(
@@ -31,11 +31,18 @@ class API(object):
     def get_minion_pool(self, 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(
         get_minion_pools_policy_label('create'),
         MINION_POOLS_DEFAULT_RULE,
-        "Create a minion_pool",
+        "Create a minion pool",
         [
             {
                 "path": "/minion_pools",
@@ -42,7 +42,7 @@ MINION_POOLS_DEFAULT_RULES = [
     policy.DocumentedRuleDefault(
         get_minion_pools_policy_label('show'),
         MINION_POOLS_DEFAULT_RULE,
-        "Show details for minion_pool",
+        "Show details for minion pool",
         [
             {
                 "path": "/minion_pools/{minion_pool_id}",
@@ -53,7 +53,7 @@ MINION_POOLS_DEFAULT_RULES = [
     policy.DocumentedRuleDefault(
         get_minion_pools_policy_label('update'),
         MINION_POOLS_DEFAULT_RULE,
-        "Update details for minion_pool",
+        "Update details for minion pool",
         [
             {
                 "path": "/minion_pools/{minion_pool_id}",
@@ -64,7 +64,7 @@ MINION_POOLS_DEFAULT_RULES = [
     policy.DocumentedRuleDefault(
         get_minion_pools_policy_label('delete'),
         MINION_POOLS_DEFAULT_RULE,
-        "Delete minion_pool",
+        "Delete minion pool",
         [
             {
                 "path": "/minion_pools/{minion_pool_id}",
@@ -73,9 +73,20 @@ MINION_POOLS_DEFAULT_RULES = [
         ]
     ),
     policy.DocumentedRuleDefault(
-        get_minion_pools_policy_label('initialize'),
+        get_minion_pools_policy_label('set_up_shared_resources'),
         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",
@@ -83,11 +94,10 @@ MINION_POOLS_DEFAULT_RULES = [
             }
         ]
     ),
-
     policy.DocumentedRuleDefault(
-        get_minion_pools_policy_label('allocate'),
+        get_minion_pools_policy_label('allocate_machines'),
         MINION_POOLS_DEFAULT_RULE,
-        "Allocate Minion Pool",
+        "Allocate Minion Pool machines",
         [
             {
                 "path": "/minion_pools/{minion_pool_id}/actions",
@@ -96,9 +106,9 @@ MINION_POOLS_DEFAULT_RULES = [
         ]
     ),
     policy.DocumentedRuleDefault(
-        get_minion_pools_policy_label('deallocate'),
+        get_minion_pools_policy_label('deallocate_machines'),
         MINION_POOLS_DEFAULT_RULE,
-        "Deallocate Minion Pool",
+        "Deallocate Minion Pool machines",
         [
             {
                 "path": "/minion_pools/{minion_pool_id}/actions",

+ 14 - 18
coriolis/providers/base.py

@@ -552,8 +552,8 @@ class BaseMinionPoolProvider(
 
     @abc.abstractmethod
     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
         selected for a given transfer. Should raise if any options related to
         the minions in the pool might be deemed incompatible with the desited
@@ -562,13 +562,13 @@ class BaseMinionPoolProvider(
         pass
 
     @abc.abstractmethod
-    def validate_pool_options(
+    def validate_minion_pool_options(
             self, ctxt, connection_info, environment_options):
         """ Validates the provided pool options. """
         pass
 
     @abc.abstractmethod
-    def setup_pool_supporting_resources(
+    def set_up_pool_shared_resources(
             self, ctxt, connection_info, environment_options, pool_identifier):
         """ Sets up supporting resources which can be re-used amongst the
         machines which will be spawned within the pool (e.g. a shared network)
@@ -576,44 +576,40 @@ class BaseMinionPoolProvider(
         pass
 
     @abc.abstractmethod
-    def teardown_pool_supporting_resources(
+    def tear_down_pool_shared_resources(
             self, ctxt, connection_info, environment_options,
-            pool_supporting_resources):
+            pool_shared_resources):
         """ Tears down all pool supporting resources. """
         pass
 
     @abc.abstractmethod
     def create_minion(
             self, ctxt, connection_info, environment_options,
+            pool_identifier, pool_shared_resources,
             new_minion_identifier):
         pass
 
     @abc.abstractmethod
     def delete_minion(
-            self, ctxt, connection_info, environment_options,
-            minion_properties):
+            self, ctxt, connection_info, minion_properties):
         pass
 
     @abc.abstractmethod
     def shutdown_minion(
-            self, ctxt, connection_info, environment_options,
-            minion_properties):
+            self, ctxt, connection_info, minion_properties):
         pass
 
     @abc.abstractmethod
     def start_minion(
-            self, ctxt, connection_info, environment_options,
-            minion_properties):
+            self, ctxt, connection_info, minion_properties):
         pass
 
     @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
 
     @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

+ 14 - 2
coriolis/tasks/factory.py

@@ -90,9 +90,21 @@ _TASKS_MAP = {
     constants.TASK_TYPE_DELETE_MINION:
         minion_pool_tasks.DeleteMinionTask,
     constants.TASK_TYPE_SET_UP_SHARED_POOL_RESOURCES:
-        minion_pool_tasks.SetupPoolSupportingResourcesTask,
+        minion_pool_tasks.SetUpPoolSupportingResourcesTask,
     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 coriolis import constants
+from coriolis import exception
 from coriolis.providers import factory as providers_factory
 from coriolis.tasks import base
 
@@ -51,7 +52,7 @@ class ValidateMinionPoolOptionsTask(base.TaskRunner):
             event_handler)
 
         environment_options = task_info['pool_environment_options']
-        provider.validate_pool_options(
+        provider.validate_minion_pool_options(
             ctxt, connection_info, environment_options)
 
         return {}
@@ -69,7 +70,9 @@ class CreateMinionTask(base.TaskRunner):
 
     @classmethod
     def get_required_task_info_properties(cls):
-        return ["pool_environment_options"]
+        return [
+            "pool_environment_options", "pool_shared_resources",
+            "pool_identifier"]
 
     @classmethod
     def get_returned_task_info_properties(cls):
@@ -95,15 +98,28 @@ class CreateMinionTask(base.TaskRunner):
             destination["type"], constants.PROVIDER_TYPE_MINION_POOL,
             event_handler)
 
+        pool_identifier = task_info['pool_identifier']
         environment_options = task_info['pool_environment_options']
+        pool_shared_resources = task_info['pool_shared_resources']
         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 {
-            "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):
@@ -155,7 +171,7 @@ class DeleteMinionTask(base.TaskRunner):
             "minion_connection_info": None}
 
 
-class SetupPoolSupportingResourcesTask(base.TaskRunner):
+class SetUpPoolSupportingResourcesTask(base.TaskRunner):
 
     @classmethod
     def get_required_platform(cls):
@@ -171,7 +187,7 @@ class SetupPoolSupportingResourcesTask(base.TaskRunner):
 
     @classmethod
     def get_returned_task_info_properties(cls):
-        return ["pool_supporting_resources"]
+        return ["pool_shared_resources"]
 
     @classmethod
     def get_required_provider_types(cls):
@@ -195,13 +211,13 @@ class SetupPoolSupportingResourcesTask(base.TaskRunner):
 
         pool_identifier = task_info['pool_identifier']
         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)
 
-        return {"pool_supporting_resources": pool_supporting_resources}
+        return {"pool_shared_resources": pool_shared_resources}
 
 
-class TeardownPoolSupportingResourcesTask(base.TaskRunner):
+class TearDownPoolSupportingResourcesTask(base.TaskRunner):
 
     @classmethod
     def get_required_platform(cls):
@@ -213,11 +229,11 @@ class TeardownPoolSupportingResourcesTask(base.TaskRunner):
 
     @classmethod
     def get_required_task_info_properties(cls):
-        return ["pool_environment_options", "pool_supporting_resources"]
+        return ["pool_environment_options", "pool_shared_resources"]
 
     @classmethod
     def get_returned_task_info_properties(cls):
-        return ["pool_supporting_resources"]
+        return ["pool_shared_resources"]
 
     @classmethod
     def get_required_provider_types(cls):
@@ -240,9 +256,218 @@ class TeardownPoolSupportingResourcesTask(base.TaskRunner):
             event_handler)
 
         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,
-            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"