Quellcode durchsuchen

Merge pull request #37 from aznashwan/source-env-opts

Add source environment options listing API call.
Nashwan Azhari vor 7 Jahren
Ursprung
Commit
6c20bb6eb3

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

@@ -0,0 +1,45 @@
+# Copyright 2019 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+from oslo_log import log as logging
+
+from coriolis import utils
+from coriolis.api.v1.views import endpoint_source_options_view
+from coriolis.api import wsgi as api_wsgi
+from coriolis.endpoint_source_options import api
+from coriolis.policies import endpoints as endpoint_policies
+
+
+LOG = logging.getLogger(__name__)
+
+
+class EndpointSourceOptionsController(api_wsgi.Controller):
+    def __init__(self):
+        self._source_options_api = api.API()
+        super(EndpointSourceOptionsController, self).__init__()
+
+    def index(self, req, endpoint_id):
+        context = req.environ['coriolis.context']
+        context.can("%s:list_source_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_source_options_view.collection(
+            req,
+            self._source_options_api.get_endpoint_source_options(
+                context, endpoint_id, env=env, option_names=options))
+
+
+def create_resource():
+    return api_wsgi.Resource(EndpointSourceOptionsController())

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

@@ -8,6 +8,7 @@ from coriolis.api.v1 import endpoint_actions
 from coriolis.api.v1 import endpoint_destination_options
 from coriolis.api.v1 import endpoint_instances
 from coriolis.api.v1 import endpoint_networks
+from coriolis.api.v1 import endpoint_source_options
 from coriolis.api.v1 import endpoint_storage
 from coriolis.api.v1 import endpoints
 from coriolis.api.v1 import migration_actions
@@ -78,6 +79,13 @@ class APIRouter(api.APIRouter):
                         controller=(
                             self.resources['endpoint_destination_options']))
 
+        self.resources['endpoint_source_options'] = \
+            endpoint_source_options.create_resource()
+        mapper.resource('source_options',
+                        'endpoints/{endpoint_id}/source-options',
+                        controller=(
+                            self.resources['endpoint_source_options']))
+
         self.resources['provider_schemas'] = \
             provider_schemas.create_resource()
         mapper.resource('provider_schemas',

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

@@ -0,0 +1,20 @@
+# Copyright 2019 Cloudbase Solutions Srl
+# All Rights Reserved.
+
+import itertools
+
+
+def _format_source_opt(req, source_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 source_option.items()))
+
+
+def collection(req, source_options):
+    formatted_opts = [
+        _format_source_opt(req, opt) for opt in source_options]
+    return {'source_options': formatted_opts}

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

@@ -64,6 +64,13 @@ class ConductorClient(object):
             endpoint_id=endpoint_id,
             instance_name=instance_name)
 
+    def get_endpoint_source_options(
+            self, ctxt, endpoint_id, env, option_names):
+        return self._client.call(
+            ctxt, 'get_endpoint_source_options',
+            endpoint_id=endpoint_id,
+            env=env, option_names=option_names)
+
     def get_endpoint_destination_options(
             self, ctxt, endpoint_id, env, option_names):
         return self._client.call(

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

@@ -192,6 +192,13 @@ class ConductorServerEndpoint(object):
         return self._rpc_worker_client.get_endpoint_instance(
             ctxt, endpoint.type, endpoint.connection_info, instance_name)
 
+    def get_endpoint_source_options(
+            self, ctxt, endpoint_id, env, option_names):
+        endpoint = self.get_endpoint(ctxt, endpoint_id)
+
+        return self._rpc_worker_client.get_endpoint_source_options(
+            ctxt, endpoint.type, endpoint.connection_info, env, option_names)
+
     def get_endpoint_destination_options(
             self, ctxt, endpoint_id, env, option_names):
         endpoint = self.get_endpoint(ctxt, endpoint_id)

+ 2 - 1
coriolis/constants.py

@@ -60,7 +60,7 @@ PROVIDER_TYPE_ENDPOINT_INSTANCES = 32
 PROVIDER_TYPE_OS_MORPHING = 64
 PROVIDER_TYPE_ENDPOINT_NETWORKS = 128
 PROVIDER_TYPE_INSTANCE_FLAVOR = 256
-PROVIDER_TYPE_ENDPOINT_OPTIONS = 512
+PROVIDER_TYPE_DESTINATION_ENDPOINT_OPTIONS = 512
 PROVIDER_TYPE_SETUP_LIBS = 1024
 PROVIDER_TYPE_VALIDATE_MIGRATION_EXPORT = 2048
 PROVIDER_TYPE_VALIDATE_REPLICA_EXPORT = 4096
@@ -68,6 +68,7 @@ PROVIDER_TYPE_VALIDATE_MIGRATION_IMPORT = 8192
 PROVIDER_TYPE_VALIDATE_REPLICA_IMPORT = 16384
 PROVIDER_TYPE_ENDPOINT_STORAGE = 32768
 PROVIDER_TYPE_REPLICA_UPDATE = 65536
+PROVIDER_TYPE_SOURCE_ENDPOINT_OPTIONS = 131072
 
 DISK_FORMAT_VMDK = 'vmdk'
 DISK_FORMAT_RAW = 'raw'

+ 0 - 0
coriolis/endpoint_source_options/__init__.py


+ 14 - 0
coriolis/endpoint_source_options/api.py

@@ -0,0 +1,14 @@
+# Copyright 2019 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_source_options(
+            self, ctxt, endpoint_id, env=None, option_names=None):
+        return self._rpc_client.get_endpoint_source_options(
+            ctxt, endpoint_id, env, option_names)

+ 1 - 1
coriolis/licensing/client.py

@@ -33,7 +33,7 @@ class LicensingClient(object):
         Returns None if 'LICENSING_SERVER_BASE_URL' is not defined.
         """
         base_url = os.environ.get("LICENSING_SERVER_BASE_URL")
-        if not base_url:
+        if base_url in [None, "None", "null"]:
             LOG.warn(
                 "No 'LICENSING_SERVER_BASE_URL' env var present. Cannot "
                 "instantiate licensing client.")

+ 11 - 1
coriolis/policies/endpoints.py

@@ -128,7 +128,6 @@ ENDPOINTS_POLICY_DEFAULT_RULES = [
             }
         ]
     ),
-
     policy.DocumentedRuleDefault(
         get_endpoints_policy_label('list_destination_options'),
         ENDPOINTS_POLICY_DEFAULT_RULE,
@@ -139,6 +138,17 @@ ENDPOINTS_POLICY_DEFAULT_RULES = [
                 "method": "GET"
             }
         ]
+    ),
+    policy.DocumentedRuleDefault(
+        get_endpoints_policy_label('list_source_options'),
+        ENDPOINTS_POLICY_DEFAULT_RULE,
+        "List available source options for endpoint",
+        [
+            {
+                "path": "/endpoint/{endpoint_id}/source-options",
+                "method": "GET"
+            }
+        ]
     )
 ]
 

+ 60 - 0
coriolis/providers/base.py

@@ -126,6 +126,66 @@ class BaseEndpointDestinationOptionsProvider(
         pass
 
 
+class BaseEndpointSourceOptionsProvider(
+        object, with_metaclass(abc.ABCMeta)):
+    @abc.abstractmethod
+    def get_source_environment_options(
+            self, ctxt, connection_info, env=None, option_names=None):
+        """ Returns all possible values for the source environment options, as
+        well as any settings the options might have in the configuration files.
+
+        param env: dict: optional target environment options
+        param option_names: list(str): optional list of parameter names to show
+        values for
+
+        Example returned values for the following options:
+        schema = {
+            "properties": {
+                "migr_network": {
+                    "type": "string"
+                },
+                "security_groups": {
+                    "type": "array",
+                    "items": { "type": "string" }
+                },
+                "migr_image": {
+                    "type": "object",
+                    "properties": {
+                        "id": { "type": "string" },
+                        "name": { "type": "integer" }
+                    }
+                }
+            }
+        }
+        The provider should return:
+        options = [
+            {
+                "name": "migr_network",
+                "values": ["net1", "net2", "net3"],
+                "config_default": "net2"},
+            {
+                "name": "security_groups",
+                "values": ["secgroup1", "secgroup2", "secgroup3"],
+                "config_default": ["secgroup2", "secgroup3"]},
+            {
+                "name": "migr_image",
+                "values": [
+                    {"name": "testimage1", "id": 101},
+                    {"name": "testimg2", "id": 4}],
+                "config_default": {"name": "testimg2", "id": 4}}}
+        ]
+        Observations:
+            - base types such as 'integer' or 'string' are preserved
+            - 'array' types will return an array with all the options which are
+              settable through that paramter (any, all or none may be set)
+            - for fields where both a name or ID may be returned, returning the
+              name will be preferred. The provider must ensure that, if there
+              are objects with the same name, the IDs of those objects are
+              offered as an option instead of two identical names.
+        """
+        pass
+
+
 class BaseInstanceProvider(BaseProvider):
 
     def get_os_morphing_tools(self, conn, osmorphing_info):

+ 4 - 2
coriolis/providers/factory.py

@@ -23,7 +23,7 @@ PROVIDER_TYPE_MAP = {
     constants.PROVIDER_TYPE_IMPORT: base.BaseImportProvider,
     constants.PROVIDER_TYPE_REPLICA_IMPORT: base.BaseReplicaImportProvider,
     constants.PROVIDER_TYPE_ENDPOINT: base.BaseEndpointProvider,
-    constants.PROVIDER_TYPE_ENDPOINT_OPTIONS:
+    constants.PROVIDER_TYPE_DESTINATION_ENDPOINT_OPTIONS:
         base.BaseEndpointDestinationOptionsProvider,
     constants.PROVIDER_TYPE_ENDPOINT_INSTANCES:
         base.BaseEndpointInstancesProvider,
@@ -43,7 +43,9 @@ PROVIDER_TYPE_MAP = {
     constants.PROVIDER_TYPE_VALIDATE_REPLICA_IMPORT: (
         base.BaseReplicaImportValidationProvider),
     constants.PROVIDER_TYPE_REPLICA_UPDATE: (
-        base.BaseReplicaUpdateableProvider)
+        base.BaseReplicaUpdateableProvider),
+    constants.PROVIDER_TYPE_SOURCE_ENDPOINT_OPTIONS: (
+        base.BaseEndpointSourceOptionsProvider)
 }
 
 

+ 5 - 1
coriolis/schemas.py

@@ -27,6 +27,7 @@ _CORIOLIS_OS_MORPHING_RES_SCHEMA_NAME = "os_morphing_resources_schema.json"
 _CORIOLIS_VM_NETWORK_SCHEMA_NAME = "vm_network_schema.json"
 _SCHEDULE_API_BODY_SCHEMA_NAME = "replica_schedule_schema.json"
 _CORIOLIS_DESTINATION_OPTIONS_SCHEMA_NAME = "destination_options_schema.json"
+_CORIOLIS_SOURCE_OPTIONS_SCHEMA_NAME = "source_options_schema.json"
 _CORIOLIS_NETWORK_MAP_SCHEMA_NAME = "network_map_schema.json"
 _CORIOLIS_STORAGE_MAPPINGS_SCHEMA_NAME = "storage_mappings_schema.json"
 _CORIOLIS_VM_STORAGE_SCHEMA_NAME = "vm_storage_schema.json"
@@ -87,9 +88,12 @@ CORIOLIS_VM_NETWORK_SCHEMA = get_schema(
 SCHEDULE_API_BODY_SCHEMA = get_schema(
     __name__, _SCHEDULE_API_BODY_SCHEMA_NAME)
 
-CORIOLIS_DESTINATION_ENVIRONMENT = get_schema(
+CORIOLIS_DESTINATION_ENVIRONMENT_OPTIONS_SCHEMA = get_schema(
     __name__, _CORIOLIS_DESTINATION_OPTIONS_SCHEMA_NAME)
 
+CORIOLIS_SOURCE_ENVIRONMENT_OPTIONS_SCHEMA = get_schema(
+    __name__, _CORIOLIS_SOURCE_OPTIONS_SCHEMA_NAME)
+
 CORIOLIS_NETWORK_MAP_SCHEMA = get_schema(
     __name__, _CORIOLIS_NETWORK_MAP_SCHEMA_NAME)
 

+ 22 - 0
coriolis/schemas/source_options_schema.json

@@ -0,0 +1,22 @@
+{
+  "$schema": "http://cloudbase.it/coriolis/schemas/endpoint_source_options#",
+  "type": "array",
+  "items": {
+    "type:": "object",
+    "properties": {
+      "name": {
+        "type": "string",
+        "help": "The name of the source environment parameter."
+      },
+      "values": {
+        "type": "array",
+        "help": "A list of possible values of the given source environment parameter."
+      },
+      "config_default": {
+        "help": "The default value set in the Coriolis service's configuration file."
+      }
+    },
+    "required": ["name", "values"],
+    "additionalProperties": false
+  }
+}

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

@@ -70,6 +70,15 @@ class WorkerClient(object):
             env=env,
             option_names=option_names)
 
+    def get_endpoint_source_options(
+            self, ctxt, platform_name, connection_info, env, option_names):
+        return self._client.call(
+            ctxt, 'get_endpoint_source_options',
+            platform_name=platform_name,
+            connection_info=connection_info,
+            env=env,
+            option_names=option_names)
+
     def get_endpoint_networks(self, ctxt, platform_name, connection_info, env):
         return self._client.call(
             ctxt, 'get_endpoint_networks',

+ 30 - 3
coriolis/worker/rpc/server.py

@@ -5,7 +5,6 @@ import multiprocessing
 
 import os
 import shutil
-import time
 import signal
 import sys
 import eventlet
@@ -273,7 +272,13 @@ class WorkerServerEndpoint(object):
     def get_endpoint_destination_options(
             self, ctxt, platform_name, connection_info, env, option_names):
         provider = providers_factory.get_provider(
-            platform_name, constants.PROVIDER_TYPE_ENDPOINT_OPTIONS, None)
+            platform_name,
+            constants.PROVIDER_TYPE_DESTINATION_ENDPOINT_OPTIONS,
+            None, raise_if_not_found=False)
+        if not provider:
+            raise exception.InvalidInput(
+                "Provider plugin for platform '%s' does not support listing "
+                "destination environment options." % platform_name)
 
         secret_connection_info = utils.get_secret_connection_info(
             ctxt, connection_info)
@@ -282,7 +287,29 @@ class WorkerServerEndpoint(object):
             ctxt, secret_connection_info, env=env, option_names=option_names)
 
         schemas.validate_value(
-            options, schemas.CORIOLIS_DESTINATION_ENVIRONMENT)
+            options, schemas.CORIOLIS_DESTINATION_ENVIRONMENT_OPTIONS_SCHEMA)
+
+        return options
+
+    def get_endpoint_source_options(
+            self, ctxt, platform_name, connection_info, env, option_names):
+        provider = providers_factory.get_provider(
+            platform_name,
+            constants.PROVIDER_TYPE_SOURCE_ENDPOINT_OPTIONS,
+            None, raise_if_not_found=False)
+        if not provider:
+            raise exception.InvalidInput(
+                "Provider plugin for platform '%s' does not support listing "
+                "source environment options." % platform_name)
+
+        secret_connection_info = utils.get_secret_connection_info(
+            ctxt, connection_info)
+
+        options = provider.get_source_environment_options(
+            ctxt, secret_connection_info, env=env, option_names=option_names)
+
+        schemas.validate_value(
+            options, schemas.CORIOLIS_SOURCE_ENVIRONMENT_OPTIONS_SCHEMA)
 
         return options