storlets/Engine/swift/storlet_middleware/storlet_handler.py

310 lines
14 KiB
Python
Executable File

'''-------------------------------------------------------------------------
Copyright IBM Corp. 2015, 2015 All Rights Reserved
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
Limitations under the License.
-------------------------------------------------------------------------'''
'''
Created on Feb 18, 2014
@author: Gil Vernik
'''
import ConfigParser
from eventlet import Timeout
from storlet_common import StorletException
from storlet_common import StorletTimeout
from swift.common.exceptions import ConnectionTimeout
from swift.common.swob import HTTPBadRequest
from swift.common.swob import HTTPInternalServerError
from swift.common.swob import HTTPUnauthorized
from swift.common.swob import Request
from swift.common.swob import Response
from swift.common.swob import wsgify
from swift.common.utils import config_true_value
from swift.common.utils import get_logger
from swift.common.utils import is_success
from swift.common.utils import register_swift_info
from swift.proxy.controllers.base import get_account_info
class StorletHandlerMiddleware(object):
def __init__(self, app, conf, storlet_conf):
self.app = app
self.logger = get_logger(conf, log_route='storlet_handler')
self.stimeout = int(storlet_conf.get('storlet_timeout'))
self.storlet_containers = [storlet_conf.get('storlet_container'),
storlet_conf.get('storlet_dependency')]
self.execution_server = storlet_conf.get('execution_server')
self.gateway_module = storlet_conf['gateway_module']
self.proxy_only_storlet_execution = \
storlet_conf['storlet_execute_on_proxy_only']
self.gateway_conf = storlet_conf
@wsgify
def __call__(self, req):
try:
if self.execution_server == 'proxy':
version, account, container, obj = req.split_path(
2, 4, rest_with_last=True)
else:
device, partition, account, container, obj = \
req.split_path(5, 5, rest_with_last=True)
version = '0'
except Exception as e:
return req.get_response(self.app)
self.logger.debug('storlet_handler call in %s: with %s/%s/%s' %
(self.execution_server, account, container, obj))
storlet_execution = False
if 'X-Run-Storlet' in req.headers:
storlet_execution = True
if (storlet_execution is True and account and container and obj) or \
(container in self.storlet_containers and obj):
gateway = self.gateway_module(self.gateway_conf,
self.logger, self.app, version,
account, container, obj)
else:
return req.get_response(self.app)
try:
if self.execution_server == 'object' and storlet_execution:
if req.method == 'GET':
self.logger.info('GET. Run storlet')
orig_resp = req.get_response(self.app)
if not is_success(orig_resp.status_int):
return orig_resp
if self._is_range_request(req) is True or \
self._is_slo_get_request(req, orig_resp, account,
container, obj) or \
self.proxy_only_storlet_execution is True:
# For SLOs, and proxy only mode
# Storlet are executed on the proxy
# Therefore we return the object part without
# Storlet invocation:
self.logger.info('storlet_handler: invocation '
'over %s/%s/%s %s' %
(account, container, obj,
'to be executed on proxy'))
return orig_resp
else:
# We apply here the Storlet:
self.logger.info('storlet_handler: invocation '
'over %s/%s/%s %s' %
(account, container, obj,
'to be executed locally'))
old_env = req.environ.copy()
orig_req = Request.blank(old_env['PATH_INFO'], old_env)
(out_md, app_iter) = \
gateway.gatewayObjectGetFlow(req, container,
obj, orig_resp)
if 'Content-Length' in orig_resp.headers:
orig_resp.headers.pop('Content-Length')
if 'Transfer-Encoding' in orig_resp.headers:
orig_resp.headers.pop('Transfer-Encoding')
return Response(app_iter, headers=orig_resp.headers,
request=orig_req,
conditional_response=True)
elif (self.execution_server == 'proxy'):
if (storlet_execution or container in self.storlet_containers):
account_meta = get_account_info(req.environ,
self.app)['meta']
storlets_enabled = account_meta.get('storlet-enabled',
'False')
if storlets_enabled == 'False':
self.logger.info('Account disabled for storlets')
return HTTPBadRequest('Account disabled for storlets')
if req.method == 'GET' and storlet_execution:
if not gateway.authorizeStorletExecution(req):
return HTTPUnauthorized('Storlet: no permission')
# The get request may be a SLO object GET request.
# Simplest solution would be to invoke a HEAD
# for every GET request to test if we are in SLO case.
# In order to save the HEAD overhead we implemented
# a slightly more involved flow:
# At proxy side, we augment request with Storlet stuff
# and let the request flow.
# At object side, we invoke the plain (non Storlet)
# request and test if we are in SLO case.
# and invoke Storlet only if non SLO case.
# Back at proxy side, we test if test received
# full object to detect if we are in SLO case,
# and invoke Storlet only if in SLO case.
gateway.augmentStorletRequest(req)
original_resp = req.get_response(self.app)
if self._is_range_request(req) is True or \
self._is_slo_get_request(req, original_resp,
account,
container, obj) or \
self.proxy_only_storlet_execution is True:
# SLO / proxy only case:
# storlet to be invoked now at proxy side:
(out_md, app_iter) = \
gateway.gatewayProxyGETFlow(req, container, obj,
original_resp)
# adapted from non SLO GET flow
if is_success(original_resp.status_int):
old_env = req.environ.copy()
orig_req = Request.blank(old_env['PATH_INFO'],
old_env)
resp_headers = original_resp.headers
resp_headers['Content-Length'] = None
return Response(app_iter=app_iter,
headers=resp_headers,
request=orig_req,
conditional_response=True)
return original_resp
else:
# Non proxy GET case: Storlet was already invoked at
# object side
if 'Transfer-Encoding' in original_resp.headers:
original_resp.headers.pop('Transfer-Encoding')
if is_success(original_resp.status_int):
old_env = req.environ.copy()
orig_req = Request.blank(old_env['PATH_INFO'],
old_env)
resp_headers = original_resp.headers
resp_headers['Content-Length'] = None
return Response(app_iter=original_resp.app_iter,
headers=resp_headers,
request=orig_req,
conditional_response=True)
return original_resp
elif req.method == 'PUT':
if (container in self.storlet_containers):
ret = gateway.validateStorletUpload(req)
if ret:
return HTTPBadRequest(body=ret)
else:
if not gateway.authorizeStorletExecution(req):
return HTTPUnauthorized('Storlet: no permissions')
if storlet_execution:
gateway.augmentStorletRequest(req)
(out_md, app_iter) = \
gateway.gatewayProxyPutFlow(req, container, obj)
req.environ['wsgi.input'] = app_iter
if 'CONTENT_LENGTH' in req.environ:
req.environ.pop('CONTENT_LENGTH')
req.headers['Transfer-Encoding'] = 'chunked'
return req.get_response(self.app)
except (StorletTimeout, ConnectionTimeout, Timeout) as e:
StorletException.handle(self.logger, e)
return HTTPInternalServerError(body='Storlet execution timed out')
except Exception as e:
StorletException.handle(self.logger, e)
return HTTPInternalServerError(body='Storlet execution failed')
return req.get_response(self.app)
'''
Determines whether the request is a byte-range request
args:
req: the request
'''
def _is_range_request(self, req):
if 'Range' in req.headers:
return True
return False
'''
Determines from a GET request and its associated response
if the object is a SLO
args:
req: the request
resp: the response
account: the account as extracted from req
container: the response as extracted from req
obj: the response as extracted from req
'''
def _is_slo_get_request(self, req, resp, account, container, obj):
if req.method != 'GET':
return False
if req.params.get('multipart-manifest') == 'get':
return False
self.logger.info('Verify if {0}/{1}/{2} is an SLO assembly object'.
format(account, container, obj))
if resp.status_int < 300 and resp.status_int >= 200:
for key in resp.headers:
if (key.lower() == 'x-static-large-object'
and config_true_value(resp.headers[key])):
self.logger.info('{0}/{1}/{2} is indeed an SLO assembly '
'object'.format(account, container, obj))
return True
self.logger.info('{0}/{1}/{2} is NOT an SLO assembly object'.
format(account, container, obj))
return False
self.logger.error('Failed to check if {0}/{1}/{2} is an SLO assembly '
'object. Got status {3}'.
format(account, container, obj, resp.status))
raise Exception('Failed to check if {0}/{1}/{2} is an SLO assembly '
'object. Got status {3}'.format(account, container,
obj, resp.status))
def filter_factory(global_conf, **local_conf):
conf = global_conf.copy()
conf.update(local_conf)
storlet_conf = dict()
storlet_conf['storlet_timeout'] = conf.get('storlet_timeout', 40)
storlet_conf['storlet_container'] = \
conf.get('storlet_container', 'storlet')
storlet_conf['storlet_dependency'] = conf.get('storlet_dependency',
'dependency')
storlet_conf['execution_server'] = conf.get('execution_server', '')
storlet_conf['storlet_execute_on_proxy_only'] = \
config_true_value(conf.get('storlet_execute_on_proxy_only', 'false'))
storlet_conf['gateway_conf'] = {}
module_name = conf.get('storlet_gateway_module', '')
mo = module_name[:module_name.rfind(':')]
cl = module_name[module_name.rfind(':') + 1:]
module = __import__(mo, fromlist=[cl])
the_class = getattr(module, cl)
configParser = ConfigParser.RawConfigParser()
configParser.read(conf.get('storlet_gateway_conf',
'/etc/swift/storlet_stub_gateway.conf'))
additional_items = configParser.items("DEFAULT")
for key, val in additional_items:
storlet_conf[key] = val
swift_info = {}
storlet_conf["gateway_module"] = the_class
register_swift_info('storlet_handler', False, **swift_info)
def storlet_handler_filter(app):
return StorletHandlerMiddleware(app, conf, storlet_conf)
return storlet_handler_filter