Alessandro Pilotti пре 10 година
родитељ
комит
4f2ac15752

+ 5 - 1
coriolis/api/__init__.py

@@ -20,6 +20,7 @@ WSGI middleware for OpenStack API controllers.
 
 from oslo_log import log as logging
 from oslo_service import wsgi as base_wsgi
+from paste import urlmap
 import routes
 
 from coriolis.api import wsgi
@@ -29,6 +30,10 @@ from coriolis.i18n import _, _LW
 LOG = logging.getLogger(__name__)
 
 
+def root_app_factory(loader, global_conf, **local_conf):
+    return urlmap.urlmap_factory(loader, global_conf, **local_conf)
+
+
 class APIMapper(routes.Mapper):
     def routematch(self, url=None, environ=None):
         if url is "":
@@ -68,7 +73,6 @@ class APIRouter(base_wsgi.Router):
 
     @classmethod
     def factory(cls, global_config, **local_config):
-        """Simple paste factory, :class:`cinder.wsgi.Router` doesn't have."""
         return cls()
 
     def __init__(self, ext_mgr=None):

+ 62 - 0
coriolis/api/auth.py

@@ -0,0 +1,62 @@
+import webob
+
+from oslo_log import log as logging
+from oslo_middleware import request_id
+from oslo_serialization import jsonutils
+
+from coriolis.api import wsgi
+from coriolis import context
+from coriolis.i18n import _
+
+LOG = logging.getLogger(__name__)
+
+
+class CoriolisKeystoneContext(wsgi.Middleware):
+    @webob.dec.wsgify(RequestClass=wsgi.Request)
+    def __call__(self, req):
+        user = req.headers.get('X_USER')
+        user = req.headers.get('X_USER_ID', user)
+        if user is None:
+            LOG.debug("Neither X_USER_ID nor X_USER found in request")
+            return webob.exc.HTTPUnauthorized()
+
+        # get the roles
+        roles = [r.strip() for r in req.headers.get('X_ROLE', '').split(',')]
+        if 'X_TENANT_ID' in req.headers:
+            # This is the new header since Keystone went to ID/Name
+            tenant = req.headers['X_TENANT_ID']
+        else:
+            # This is for legacy compatibility
+            tenant = req.headers['X_TENANT']
+
+        project_name = req.headers.get('X_TENANT_NAME')
+
+        req_id = req.environ.get(request_id.ENV_REQUEST_ID)
+
+        # Get the auth token
+        auth_token = req.headers.get('X_AUTH_TOKEN',
+                                     req.headers.get('X_STORAGE_TOKEN'))
+
+        # Build a context, including the auth_token...
+        remote_address = req.remote_addr
+
+        service_catalog = None
+        if req.headers.get('X_SERVICE_CATALOG') is not None:
+            try:
+                catalog_header = req.headers.get('X_SERVICE_CATALOG')
+                service_catalog = jsonutils.loads(catalog_header)
+            except ValueError:
+                raise webob.exc.HTTPInternalServerError(
+                    explanation=_('Invalid service catalog json.'))
+
+        ctx = context.RequestContext(user,
+                                     tenant,
+                                     project_name=project_name,
+                                     roles=roles,
+                                     auth_token=auth_token,
+                                     remote_address=remote_address,
+                                     service_catalog=service_catalog,
+                                     request_id=req_id)
+
+        req.environ['coriolis.context'] = ctx
+        return self.application

+ 9 - 5
coriolis/api/v1/migrations.py

@@ -12,15 +12,18 @@ class MigrationController(object):
 
     def show(self, req, id):
         return migration_view.format_migration(
-            req, self._migration_api.get_migration(id))
+            req, self._migration_api.get_migration(
+                req.environ["coriolis.context"], id))
 
     def index(self, req):
         return migration_view.collection(
-            req, self._migration_api.get_migrations())
+            req, self._migration_api.get_migrations(
+                req.environ['coriolis.context']))
 
     def detail(self, req):
         return migration_view.collection(
-            req, self._migration_api.get_migrations())
+            req, self._migration_api.get_migrations(
+                req.environ['coriolis.context']))
 
     def _validate_create_body(self, body):
         migration = body["migration"]
@@ -46,10 +49,11 @@ class MigrationController(object):
 
     def create(self, req, body):
         origin, destination, instances = self._validate_create_body(body)
-        self._migration_api.start(origin, destination, instances)
+        self._migration_api.start(
+            req.environ['coriolis.context'], origin, destination, instances)
 
     def delete(self, req, id):
-        self._migration_api.stop(id)
+        self._migration_api.stop(req.environ['coriolis.context'], id)
 
 
 def create_resource():

+ 55 - 4
coriolis/api/wsgi.py

@@ -19,7 +19,6 @@ import math
 import time
 
 from oslo_log import log as logging
-from oslo_log import versionutils
 from oslo_serialization import jsonutils
 from oslo_utils import excutils
 import six
@@ -56,11 +55,11 @@ class Application(object):
 
             [app:wadl]
             latest_version = 1.3
-            paste.app_factory = cinder.api.fancy_api:Wadl.factory
+            paste.app_factory = coriolis.api.fancy_api:Wadl.factory
 
         which would result in a call to the `Wadl` class as
 
-            import cinder.api.fancy_api
+            import coriolis.api.fancy_api
             fancy_api.Wadl(latest_version='1.3')
 
         You could of course re-implement the `factory` method in subclasses,
@@ -318,6 +317,58 @@ class Request(webob.Request):
         return self.accept_language.best_match(all_languages)
 
 
+class Middleware(Application):
+    """Base WSGI middleware.
+    These classes require an application to be
+    initialized that will be called next.  By default the middleware will
+    simply call its wrapped app, or you can override __call__ to customize its
+    behavior.
+    """
+
+    @classmethod
+    def factory(cls, global_config, **local_config):
+        """Used for paste app factories in paste.deploy config files.
+        Any local configuration (that is, values under the [filter:APPNAME]
+        section of the paste config) will be passed into the `__init__` method
+        as kwargs.
+        A hypothetical configuration would look like:
+            [filter:analytics]
+            redis_host = 127.0.0.1
+            paste.filter_factory = coriolis.api.analytics:Analytics.factory
+        which would result in a call to the `Analytics` class as
+            import coriolis.api.analytics
+            analytics.Analytics(app_from_paste, redis_host='127.0.0.1')
+        You could of course re-implement the `factory` method in subclasses,
+        but using the kwarg passing it shouldn't be necessary.
+        """
+        def _factory(app):
+            return cls(app, **local_config)
+        return _factory
+
+    def __init__(self, application):
+        self.application = application
+
+    def process_request(self, req):
+        """Called on each request.
+        If this returns None, the next application down the stack will be
+        executed. If it returns a response then that response will be returned
+        and execution will stop here.
+        """
+        return None
+
+    def process_response(self, response):
+        """Do whatever you'd like to the response."""
+        return response
+
+    @webob.dec.wsgify(RequestClass=Request)
+    def __call__(self, req):
+        response = self.process_request(req)
+        if response:
+            return response
+        response = req.get_response(self.application)
+        return self.process_response(response)
+
+
 class ActionDispatcher(object):
     """Maps method name to local methods through action name."""
 
@@ -852,7 +903,7 @@ class Resource(Application):
 
         project_id = action_args.pop("project_id", None)
         context = request.environ.get('coriolis.context')
-        if (context and project_id and (project_id != context.project_id)):
+        if (context and project_id and (project_id != context.tenant)):
             msg = _("Malformed request url")
             return Fault(webob.exc.HTTPBadRequest(explanation=msg))
 

+ 1 - 1
coriolis/cmd/api.py

@@ -19,7 +19,7 @@ def main():
          version="1.0.0")
 
     launcher = service.get_process_launcher()
-    server = service.WSGIService('osapi_migration')
+    server = service.WSGIService('coriolis-api')
     launcher.launch_service(server, workers=server.get_workers_count())
     launcher.wait()
 

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

@@ -20,24 +20,12 @@ class ConductorServerEndpoint(object):
         self._rpc_worker_client = rpc_worker_client.WorkerClient()
 
     def get_migrations(self, ctxt):
-        # TODO: fix context
-        from coriolis import context
-        ctxt = context.CoriolisContext()
-
         return db_api.get_migrations(ctxt)
 
     def get_migration(self, ctxt, migration_id):
-        # TODO: fix context
-        from coriolis import context
-        ctxt = context.CoriolisContext()
-
         return db_api.get_migration(ctxt, migration_id)
 
     def migrate_instances(self, ctxt, origin, destination, instances):
-        # TODO: fix context
-        from coriolis import context
-        ctxt = context.CoriolisContext()
-
         migration = models.Migration()
         migration.user_id = "todo"
         migration.status = constants.MIGRATION_STATUS_STARTED
@@ -60,10 +48,6 @@ class ConductorServerEndpoint(object):
                 ctxt.to_dict(), task.id, origin, instance)
 
     def stop_instances_migration(self, ctxt, migration_id):
-        # TODO: fix context
-        from coriolis import context
-        ctxt = context.CoriolisContext()
-
         migration = db_api.get_migration(ctxt, migration_id)
         for task in migration.tasks:
             if task.status == constants.TASK_STATUS_STARTED:
@@ -71,16 +55,9 @@ class ConductorServerEndpoint(object):
                     ctxt.to_dict(), task.host, task.process_id)
 
     def set_task_host(self, ctxt, task_id, host, process_id):
-        # TODO: fix context
-        from coriolis import context
-        ctxt = context.CoriolisContext()
         db_api.set_task_host(ctxt, task_id, host, process_id)
 
     def export_completed(self, ctxt, task_id, export_info):
-        # TODO: fix context
-        from coriolis import context
-        ctxt = context.CoriolisContext()
-
         db_api.update_task_status(
             ctxt, task_id, constants.TASK_STATUS_COMPLETE)
         op_export = db_api.get_task(ctxt, task_id)
@@ -101,18 +78,10 @@ class ConductorServerEndpoint(object):
             export_info)
 
     def import_completed(self, ctxt, task_id):
-        # TODO: fix context
-        from coriolis import context
-        ctxt = context.CoriolisContext()
-
         db_api.update_task_status(
             ctxt, task_id, constants.TASK_STATUS_COMPLETE)
 
     def set_task_error(self, ctxt, task_id, exception_details):
-        # TODO: fix context
-        from coriolis import context
-        ctxt = context.CoriolisContext()
-
         db_api.update_task_status(
             ctxt, task_id, constants.TASK_STATUS_ERROR,
             exception_details)

+ 39 - 9
coriolis/context.py

@@ -1,16 +1,46 @@
 from oslo_context import context
 from oslo_db.sqlalchemy import enginefacade
+from oslo_utils import timeutils
 
 
 @enginefacade.transaction_context_provider
-class CoriolisContext(context.RequestContext):
-    def __init__(self):
-        self.user_id = "todo"
+class RequestContext(context.RequestContext):
+    def __init__(self, user, tenant, is_admin=None,
+                 roles=None, project_name=None, remote_address=None,
+                 timestamp=None, request_id=None, auth_token=None,
+                 overwrite=True, domain=None, user_domain=None,
+                 project_domain=None, **kwargs):
+
+        super(RequestContext, self).__init__(auth_token=auth_token,
+                                             user=user,
+                                             tenant=tenant,
+                                             domain=domain,
+                                             user_domain=user_domain,
+                                             project_domain=project_domain,
+                                             is_admin=is_admin,
+                                             request_id=request_id,
+                                             overwrite=overwrite)
+        self.roles = roles or []
+        self.project_name = project_name
+        self.remote_address = remote_address
+        if not timestamp:
+            timestamp = timeutils.utcnow()
+        elif isinstance(timestamp, str):
+            timestamp = timeutils.parse_isotime(timestamp)
+        self.timestamp = timestamp
 
     def to_dict(self):
-        # values = super(CoriolisContext, self).to_dict()
-        values = {}
-        values.update({
-            'user_id': self.user_id,
-        })
-        return values
+        result = super(RequestContext, self).to_dict()
+        result['user'] = self.user
+        result['tenant'] = self.tenant
+        result['project_name'] = self.project_name
+        result['domain'] = self.domain
+        result['roles'] = self.roles
+        result['remote_address'] = self.remote_address
+        result['timestamp'] = self.timestamp.isoformat()
+        result['request_id'] = self.request_id
+        return result
+
+    @classmethod
+    def from_dict(cls, values):
+        return cls(**values)

+ 1 - 1
coriolis/i18n.py

@@ -20,7 +20,7 @@ See http://docs.openstack.org/developer/oslo.i18n/usage.html .
 
 import oslo_i18n as i18n
 
-DOMAIN = 'cinder'
+DOMAIN = 'coriolis'
 
 _translators = i18n.TranslatorFactory(domain=DOMAIN)
 

+ 4 - 9
coriolis/migrations/api.py

@@ -1,25 +1,20 @@
 from coriolis.conductor.rpc import client as rpc_client
-from coriolis import context
 
 
 class API(object):
     def __init__(self):
         self._rpc_client = rpc_client.ConductorClient()
 
-    def start(self, origin, destination, instances):
-        ctxt = context.CoriolisContext()
+    def start(self, ctxt, origin, destination, instances):
         self._rpc_client.begin_migrate_instances(
             ctxt.to_dict(), origin, destination, instances)
 
-    def stop(self, migration_id):
-        ctxt = context.CoriolisContext()
+    def stop(ctxt, self, migration_id):
         self._rpc_client.stop_instances_migration(
             ctxt.to_dict(), migration_id)
 
-    def get_migrations(self):
-        ctxt = context.CoriolisContext()
+    def get_migrations(self, ctxt):
         return self._rpc_client.get_migrations(ctxt.to_dict())
 
-    def get_migration(self, migration_id):
-        ctxt = context.CoriolisContext()
+    def get_migration(self, ctxt, migration_id):
         return self._rpc_client.get_migration(ctxt.to_dict(), migration_id)

+ 12 - 2
etc/coriolis/api-paste.ini

@@ -1,5 +1,9 @@
-[pipeline:osapi_migration]
-pipeline = authtoken apiv1
+[composite:coriolis-api]
+use = call:coriolis.api:root_app_factory
+/v1: coriolis-api-v1
+
+[pipeline:coriolis-api-v1]
+pipeline = request_id authtoken keystonecontext apiv1
 
 [app:apiv1]
 paste.app_factory = coriolis.api.v1.router:APIRouter.factory
@@ -7,3 +11,9 @@ paste.app_factory = coriolis.api.v1.router:APIRouter.factory
 # Auth middleware that validates token against keystone
 [filter:authtoken]
 paste.filter_factory = keystonemiddleware.auth_token:filter_factory
+
+[filter:keystonecontext]
+paste.filter_factory = coriolis.api.auth:CoriolisKeystoneContext.factory
+
+[filter:request_id]
+paste.filter_factory = oslo_middleware.request_id:RequestId.factory