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

Add minion pool options listing and validation.

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

+ 45 - 0
coriolis/api/v1/endpoint_minion_pool_options.py

@@ -0,0 +1,45 @@
+# Copyright 2020 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from oslo_log import log as logging
+
+from coriolis import utils
+from coriolis.api.v1.views import endpoint_minion_pool_options_view
+from coriolis.api import wsgi as api_wsgi
+from coriolis.endpoint_minion_options import api
+from coriolis.policies import endpoints as endpoint_policies
+
+
+LOG = logging.getLogger(__name__)
+
+
+class EndpointMinionPoolOptionsController(api_wsgi.Controller):
+    def __init__(self):
+        self._minion_pool_options_api = api.API()
+        super(EndpointMinionPoolOptionsController, self).__init__()
+
+    def index(self, req, endpoint_id):
+        context = req.environ['coriolis.context']
+        context.can("%s:list_minion_pool_options" % (
+            endpoint_policies.ENDPOINTS_POLICY_PREFIX))
+
+        env = req.GET.get("env")
+        if env is not None:
+            env = utils.decode_base64_param(env, is_json=True)
+        else:
+            env = {}
+
+        options = req.GET.get("options")
+        if options is not None:
+            options = utils.decode_base64_param(options, is_json=True)
+        else:
+            options = {}
+
+        return endpoint_minion_pool_options_view.collection(
+            req,
+            self._minion_pool_options_api.get_endpoint_minion_pool_options(
+                context, endpoint_id, env=env, option_names=options))
+
+
+def create_resource():
+    return api_wsgi.Resource(EndpointMinionPoolOptionsController())

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

@@ -8,6 +8,7 @@ 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
 from coriolis.api import wsgi as api_wsgi
 from coriolis.api import wsgi as api_wsgi
+from coriolis.endpoints import api as endpoints_api
 from coriolis.policies import minion_pools as pools_policies
 from coriolis.policies import minion_pools as pools_policies
 from coriolis.minion_pools import api
 from coriolis.minion_pools import api
 
 
@@ -17,6 +18,7 @@ LOG = logging.getLogger(__name__)
 class MinionPoolController(api_wsgi.Controller):
 class MinionPoolController(api_wsgi.Controller):
     def __init__(self):
     def __init__(self):
         self._minion_pool_api = api.API()
         self._minion_pool_api = api.API()
+        self._endpoints_api = endpoints_api.API()
         super(MinionPoolController, self).__init__()
         super(MinionPoolController, self).__init__()
 
 
     def show(self, req, id):
     def show(self, req, id):
@@ -34,13 +36,15 @@ class MinionPoolController(api_wsgi.Controller):
         return minion_pool_view.collection(
         return minion_pool_view.collection(
             req, self._minion_pool_api.get_minion_pools(context))
             req, self._minion_pool_api.get_minion_pools(context))
 
 
-    def _validate_create_body(self, body):
+    def _validate_create_body(self, ctxt, body):
         try:
         try:
             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"]
-            # TODO(aznashwan): validate pool schema:
             environment_options = minion_pool["environment_options"]
             environment_options = minion_pool["environment_options"]
+            self._endpoints_api.validate_endpoint_minion_pool_options(
+                ctxt, endpoint_id, environment_options)
+
             minimum_minions = minion_pool.get("minimum_minions", 0)
             minimum_minions = minion_pool.get("minimum_minions", 0)
             maximum_minions = minion_pool.get("maximum_minions", 1)
             maximum_minions = minion_pool.get("maximum_minions", 1)
             minion_max_idle_time = minion_pool.get(
             minion_max_idle_time = minion_pool.get(
@@ -66,19 +70,26 @@ class MinionPoolController(api_wsgi.Controller):
         (name, endpoint_id, environment_options, minimum_minions,
         (name, endpoint_id, 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(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,
             context, name, endpoint_id, environment_options, minimum_minions,
             maximum_minions, minion_max_idle_time, minion_retention_strategy,
             maximum_minions, minion_max_idle_time, minion_retention_strategy,
             notes=notes))
             notes=notes))
 
 
-    def _validate_update_body(self, body):
+    def _validate_update_body(self, id, context, body):
         try:
         try:
             minion_pool = body["minion_pool"]
             minion_pool = body["minion_pool"]
-            return {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"}}
+            if 'environment_options' in vals:
+                minion_pool = self._minion_pool_api.get_minion_pool(
+                    context, id)
+                self._endpoints_api.validate_endpoint_minion_pool_options(
+                    context, minion_pool['endpoint_id'],
+                    vals['environment_options'])
+            return vals
         except Exception as ex:
         except Exception as ex:
             LOG.exception(ex)
             LOG.exception(ex)
             if hasattr(ex, "message"):
             if hasattr(ex, "message"):
@@ -90,8 +101,8 @@ class MinionPoolController(api_wsgi.Controller):
     def update(self, req, id, body):
     def update(self, req, id, body):
         context = req.environ["coriolis.context"]
         context = req.environ["coriolis.context"]
         context.can(pools_policies.get_minion_pools_policy_label("update"))
         context.can(pools_policies.get_minion_pools_policy_label("update"))
-        updated_values = self._validate_update_body(body)
-        return minion_pool_tasks_execution_view.single(
+        updated_values = self._validate_update_body(id, context, body)
+        return minion_pool_view.single(
             req, self._minion_pool_api.update(
             req, self._minion_pool_api.update(
                 req.environ['coriolis.context'], id, updated_values))
                 req.environ['coriolis.context'], id, updated_values))
 
 

+ 22 - 0
coriolis/api/v1/router.py

@@ -8,6 +8,7 @@ from coriolis.api.v1 import diagnostics
 from coriolis.api.v1 import endpoint_actions
 from coriolis.api.v1 import endpoint_actions
 from coriolis.api.v1 import endpoint_destination_options
 from coriolis.api.v1 import endpoint_destination_options
 from coriolis.api.v1 import endpoint_instances
 from coriolis.api.v1 import endpoint_instances
+from coriolis.api.v1 import endpoint_minion_pool_options
 from coriolis.api.v1 import endpoint_networks
 from coriolis.api.v1 import endpoint_networks
 from coriolis.api.v1 import endpoint_source_options
 from coriolis.api.v1 import endpoint_source_options
 from coriolis.api.v1 import endpoint_storage
 from coriolis.api.v1 import endpoint_storage
@@ -17,6 +18,7 @@ from coriolis.api.v1 import migrations
 from coriolis.api.v1 import minion_pools
 from coriolis.api.v1 import minion_pools
 from coriolis.api.v1 import minion_pool_actions
 from coriolis.api.v1 import minion_pool_actions
 from coriolis.api.v1 import minion_pool_tasks_executions
 from coriolis.api.v1 import minion_pool_tasks_executions
+from coriolis.api.v1 import minion_pool_tasks_execution_actions
 from coriolis.api.v1 import provider_schemas
 from coriolis.api.v1 import provider_schemas
 from coriolis.api.v1 import providers
 from coriolis.api.v1 import providers
 from coriolis.api.v1 import regions
 from coriolis.api.v1 import regions
@@ -85,6 +87,26 @@ class APIRouter(api.APIRouter):
                         collection={'detail': 'GET'},
                         collection={'detail': 'GET'},
                         member={'action': 'POST'})
                         member={'action': 'POST'})
 
 
+        minion_pool_tasks_execution_actions_resource = \
+            minion_pool_tasks_execution_actions.create_resource()
+        self.resources['minion_pool_tasks_execution_actions'] = \
+            minion_pool_tasks_execution_actions_resource
+        pool_execution_path = (
+            '/{project_id}/minion_pools/{minion_pool_id}/executions/{id}')
+        mapper.connect('minion_pool_tasks_execution_actions',
+                       pool_execution_path + '/actions',
+                       controller=self.resources[
+                           'minion_pool_tasks_execution_actions'],
+                       action='action',
+                       conditions={'method': 'POST'})
+
+        self.resources['endpoint_minion_pool_options'] = \
+            endpoint_minion_pool_options.create_resource()
+        mapper.resource('minion_pool_options',
+                        'endpoints/{endpoint_id}/minion-pool-options',
+                        controller=(
+                            self.resources['endpoint_minion_pool_options']))
+
         endpoint_actions_resource = endpoint_actions.create_resource()
         endpoint_actions_resource = endpoint_actions.create_resource()
         self.resources['endpoint_actions'] = endpoint_actions_resource
         self.resources['endpoint_actions'] = endpoint_actions_resource
         endpoint_path = '/{project_id}/endpoints/{id}'
         endpoint_path = '/{project_id}/endpoints/{id}'

+ 20 - 0
coriolis/api/v1/views/endpoint_minion_pool_options_view.py

@@ -0,0 +1,20 @@
+# Copyright 2020 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+import itertools
+
+
+def _format_dest_opt(req, destination_option, keys=None):
+    def transform(key, value):
+        if keys and key not in keys:
+            return
+        yield (key, value)
+
+    return dict(itertools.chain.from_iterable(
+        transform(k, v) for k, v in destination_option.items()))
+
+
+def collection(req, destination_options):
+    formatted_opts = [
+        _format_dest_opt(req, opt) for opt in destination_options]
+    return {'minion_pool_options': formatted_opts}

+ 12 - 0
coriolis/conductor/rpc/client.py

@@ -437,3 +437,15 @@ class ConductorClient(object):
             ctxt, 'cancel_minion_pool_lifecycle_execution',
             ctxt, 'cancel_minion_pool_lifecycle_execution',
             minion_pool_id=minion_pool_id, execution_id=execution_id,
             minion_pool_id=minion_pool_id, execution_id=execution_id,
             force=force)
             force=force)
+
+    def get_endpoint_minion_pool_options(
+            self, ctxt, endpoint_id, env, option_names):
+        return self._client.call(
+            ctxt, 'get_endpoint_minion_pool_options', endpoint_id=endpoint_id,
+            env=env, option_names=option_names)
+
+    def validate_endpoint_minion_pool_options(
+            self, ctxt, endpoint_id, pool_environment):
+        return self._client.call(
+            ctxt, 'validate_endpoint_minion_pool_options',
+            endpoint_id=endpoint_id, pool_environment=pool_environment)

+ 26 - 0
coriolis/conductor/rpc/server.py

@@ -459,6 +459,19 @@ class ConductorServerEndpoint(object):
         return worker_rpc.get_endpoint_destination_options(
         return worker_rpc.get_endpoint_destination_options(
             ctxt, endpoint.type, endpoint.connection_info, env, option_names)
             ctxt, endpoint.type, endpoint.connection_info, env, option_names)
 
 
+    def get_endpoint_minion_pool_options(
+            self, ctxt, endpoint_id, env, option_names):
+        endpoint = self.get_endpoint(ctxt, endpoint_id)
+
+        worker_rpc = self._get_worker_service_rpc_for_specs(
+            ctxt, enabled=True,
+            region_sets=[[reg.id for reg in endpoint.mapped_regions]],
+            provider_requirements={
+                endpoint.type: [
+                    constants.PROVIDER_TYPE_MINION_POOL]})
+        return worker_rpc.get_endpoint_minion_pool_options(
+            ctxt, endpoint.type, endpoint.connection_info, env, option_names)
+
     def get_endpoint_networks(self, ctxt, endpoint_id, env):
     def get_endpoint_networks(self, ctxt, endpoint_id, env):
         endpoint = self.get_endpoint(ctxt, endpoint_id)
         endpoint = self.get_endpoint(ctxt, endpoint_id)
 
 
@@ -520,6 +533,19 @@ class ConductorServerEndpoint(object):
         return worker_rpc.validate_endpoint_source_environment(
         return worker_rpc.validate_endpoint_source_environment(
             ctxt, endpoint.type, source_env)
             ctxt, endpoint.type, source_env)
 
 
+    def validate_endpoint_minion_pool_options(
+            self, ctxt, endpoint_id, pool_environment):
+        endpoint = self.get_endpoint(ctxt, endpoint_id)
+
+        worker_rpc = self._get_worker_service_rpc_for_specs(
+            ctxt, enabled=True,
+            region_sets=[[reg.id for reg in endpoint.mapped_regions]],
+            provider_requirements={
+                endpoint.type: [constants.PROVIDER_TYPE_MINION_POOL]})
+
+        return worker_rpc.validate_endpoint_minion_pool_options(
+            ctxt, endpoint.type, pool_environment)
+
     def get_available_providers(self, ctxt):
     def get_available_providers(self, ctxt):
         # TODO(aznashwan): merge list of all providers from all
         # TODO(aznashwan): merge list of all providers from all
         # worker services:
         # worker services:

+ 0 - 0
coriolis/endpoint_minion_options/__init__.py


+ 14 - 0
coriolis/endpoint_minion_options/api.py

@@ -0,0 +1,14 @@
+# Copyright 2016 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from coriolis.conductor.rpc import client as rpc_client
+
+
+class API(object):
+    def __init__(self):
+        self._rpc_client = rpc_client.ConductorClient()
+
+    def get_endpoint_minion_pool_options(
+            self, ctxt, endpoint_id, env=None, option_names=None):
+        return self._rpc_client.get_endpoint_minion_pool_options(
+            ctxt, endpoint_id, env, option_names)

+ 8 - 0
coriolis/endpoints/api.py

@@ -41,3 +41,11 @@ class API(object):
     def validate_source_environment(self, ctxt, endpoint_id, source_env):
     def validate_source_environment(self, ctxt, endpoint_id, source_env):
         return self._rpc_client.validate_endpoint_source_environment(
         return self._rpc_client.validate_endpoint_source_environment(
             ctxt, endpoint_id, source_env)
             ctxt, endpoint_id, source_env)
+
+    @utils.bad_request_on_error("Invalid minion pool environment: %s")
+    def validate_endpoint_minion_pool_options(
+            self, ctxt, endpoint_id, pool_environment):
+        return self._rpc_client.validate_endpoint_minion_pool_options(
+            ctxt, endpoint_id, pool_environment)
+
+

+ 12 - 0
coriolis/policies/endpoints.py

@@ -149,7 +149,19 @@ ENDPOINTS_POLICY_DEFAULT_RULES = [
                 "method": "GET"
                 "method": "GET"
             }
             }
         ]
         ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('list_minion_pool_options'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "List available minion pool options for endpoint",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}/minion-pool-option",
+                "method": "GET"
+            }
+        ]
     )
     )
+
 ]
 ]
 
 
 
 

+ 3 - 2
coriolis/providers/base.py

@@ -545,13 +545,14 @@ class BaseMinionPoolProvider(
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
-    def get_minion_pool_options(self, ctxt, environment_options):
+    def get_minion_pool_options(
+            self, ctxt, connection_info, env=None, option_names=None):
         """ Returns possible environment options for minion pools. """
         """ Returns possible environment options for minion pools. """
         pass
         pass
 
 
     @abc.abstractmethod
     @abc.abstractmethod
     def validate_minion_compatibility_for_transfer(
     def validate_minion_compatibility_for_transfer(
-            self, ctxt, environment_options,
+            self, ctxt, connection_info, environment_options,
             transfer_options, storage_mappings):
             transfer_options, storage_mappings):
         """ 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

+ 13 - 0
coriolis/worker/rpc/client.py

@@ -130,3 +130,16 @@ class WorkerClient(object):
 
 
     def get_service_status(self, ctxt):
     def get_service_status(self, ctxt):
         return self._client.call(ctxt, 'get_service_status')
         return self._client.call(ctxt, 'get_service_status')
+
+    def get_endpoint_minion_pool_options(
+            self, ctxt, platform_name, connection_info, env, option_names):
+        return self._client.call(
+            ctxt, 'get_endpoint_minion_pool_options',
+            platform_name=platform_name, connection_info=connection_info,
+            env=env, option_names=option_names)
+
+    def validate_endpoint_minion_pool_options(
+            self, ctxt, platform_name, pool_environment):
+        return self._client.call(
+            ctxt, 'validate_endpoint_minion_pool_options',
+            platform_name=platform_name, pool_environment=pool_environment)

+ 40 - 0
coriolis/worker/rpc/server.py

@@ -372,6 +372,30 @@ class WorkerServerEndpoint(object):
 
 
         return options
         return options
 
 
+    def get_endpoint_minion_pool_options(
+            self, ctxt, platform_name, connection_info, env, option_names):
+        provider = providers_factory.get_provider(
+            platform_name,
+            constants.PROVIDER_TYPE_MINION_POOL,
+            None, raise_if_not_found=False)
+        if not provider:
+            raise exception.InvalidInput(
+                "Provider plugin for platform '%s' does not support listing "
+                "minion pool creation or management." % platform_name)
+
+        secret_connection_info = utils.get_secret_connection_info(
+            ctxt, connection_info)
+
+        options = provider.get_minion_pool_options(
+            ctxt, secret_connection_info, env=env,
+            option_names=option_names)
+
+        # NOTE: the structure of option values is the same for minion pools:
+        schemas.validate_value(
+            options, schemas.CORIOLIS_DESTINATION_ENVIRONMENT_OPTIONS_SCHEMA)
+
+        return options
+
     def get_endpoint_source_options(
     def get_endpoint_source_options(
             self, ctxt, platform_name, connection_info, env, option_names):
             self, ctxt, platform_name, connection_info, env, option_names):
         provider = providers_factory.get_provider(
         provider = providers_factory.get_provider(
@@ -459,6 +483,22 @@ class WorkerServerEndpoint(object):
 
 
         return (is_valid, message)
         return (is_valid, message)
 
 
+    def validate_endpoint_minion_pool_options(
+            self, ctxt, platform_name, pool_environment):
+        provider = providers_factory.get_provider(
+            platform_name, constants.PROVIDER_TYPE_MINION_POOL, None)
+        pool_options_schema = provider.get_minion_pool_environment_schema()
+
+        is_valid = True
+        message = None
+        try:
+            schemas.validate_value(pool_environment, pool_options_schema)
+        except exception.SchemaValidationException as ex:
+            is_valid = False
+            message = str(ex)
+
+        return (is_valid, message)
+
     def validate_endpoint_connection(self, ctxt, platform_name,
     def validate_endpoint_connection(self, ctxt, platform_name,
                                      connection_info):
                                      connection_info):
         provider = providers_factory.get_provider(
         provider = providers_factory.get_provider(