diff --git a/storlets/swift_middleware/handlers/obj.py b/storlets/swift_middleware/handlers/obj.py index e4cb30cb..b62c512f 100644 --- a/storlets/swift_middleware/handlers/obj.py +++ b/storlets/swift_middleware/handlers/obj.py @@ -13,6 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from swift.common.request_helpers import get_sys_meta_prefix from swift.common.swob import HTTPMethodNotAllowed, \ HTTPRequestedRangeNotSatisfiable, Range from swift.common.utils import public @@ -44,6 +45,26 @@ class StorletObjectHandler(StorletBaseHandler): """ return self.request.params.get('multipart-manifest') == 'get' + def is_symlink_response(self, resp): + """ + Determins whether the response is a symlink one + + :param resp: swob.Response instance + :return: Whenther the response is a slo one + """ + self.logger.debug( + 'Verify if {0} is a symlink'.format(self.path)) + + symlink_header = get_sys_meta_prefix('object') + 'symlink-target' + is_symlink = symlink_header in resp.headers + if is_symlink: + self.logger.debug( + '{0} is indeed a symlink'.format(self.path)) + else: + self.logger.debug( + '{0} is NOT a symlink'.format(self.path)) + return is_symlink + def _get_storlet_invocation_options(self, req): options = super(StorletObjectHandler, self).\ _get_storlet_invocation_options(req) @@ -108,7 +129,9 @@ class StorletObjectHandler(StorletBaseHandler): [self.execute_on_proxy, self.execute_range_on_proxy, self.is_slo_get_request, - self.is_slo_response(orig_resp)]) + self.is_slo_response(orig_resp), + self.is_symlink_response(orig_resp) + ]) if not_runnable: # Storlet must be invoked on proxy as it is: diff --git a/storlets/swift_middleware/handlers/proxy.py b/storlets/swift_middleware/handlers/proxy.py index 0ba6e350..b66e5d2a 100644 --- a/storlets/swift_middleware/handlers/proxy.py +++ b/storlets/swift_middleware/handlers/proxy.py @@ -93,16 +93,17 @@ class StorletProxyHandler(StorletBaseHandler): :param resp: swob.Response instance :return: Whether we should execute the storlet at proxy """ - # SLO / proxy only case: - # storlet to be invoked now at proxy side: - slo_resposne = False + checks = [ + self.execute_on_proxy, + self.execute_range_on_proxy + ] if resp: - slo_resposne = self.is_slo_response(resp) + checks.extend([ + self.is_slo_response(resp), + self.is_symlink_response(resp) + ]) - runnable = any( - [self.execute_on_proxy, - self.execute_range_on_proxy, - slo_resposne]) + runnable = any(checks) return runnable @property @@ -124,6 +125,26 @@ class StorletProxyHandler(StorletBaseHandler): def is_put_copy_request(self): return 'X-Copy-From' in self.request.headers + def is_symlink_response(self, resp): + """ + Determins whether the response is a symlink one + + :param resp: swob.Response instance + :return: Whenther the response is a slo one + """ + self.logger.debug( + 'Verify if {0} is a symlink'.format(self.path)) + + symlink_header = 'X-Symlink-Target' + is_symlink = symlink_header in resp.headers + if is_symlink: + self.logger.debug( + '{0} is indeed a symlink'.format(self.path)) + else: + self.logger.debug( + '{0} is NOT a symlink'.format(self.path)) + return is_symlink + def _parse_storlet_params(self, headers): """ Parse storlet parameters from storlet/dependency object metadata diff --git a/tests/functional/python/test_symlink.py b/tests/functional/python/test_symlink.py new file mode 100644 index 00000000..09c1b19f --- /dev/null +++ b/tests/functional/python/test_symlink.py @@ -0,0 +1,60 @@ +# Copyright (c) 2010-2017 OpenStack Foundation +# +# 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. + +from swiftclient import client +from tests.functional.python import StorletPythonFunctionalTest +import unittest + + +class TestSymlink(StorletPythonFunctionalTest): + def setUp(self): + self.storlet_log = 'simple-symlink.log' + self.additional_headers = {} + self.content = b'abcdefghijklmonp' + super(TestSymlink, self).setUp( + storlet_dir='simple', + storlet_name='simple.py', + storlet_main='simple.SimpleStorlet', + storlet_file='source.txt', + headers={}) + + symlink_target = '/'.join([self.container, self.storlet_file]) + client.put_object(self.url, self.token, self.container, 'test_link', + '', headers={'X-Symlink-Target': symlink_target}) + + def test_get(self): + req_headers = {'X-Run-Storlet': self.storlet_name} + headers, content = client.get_object( + self.url, self.token, self.container, self.storlet_file, + headers=req_headers) + self.assertEqual('simple', headers['x-object-meta-test']) + self.assertEqual(self.content, content) + + req_headers = {'X-Run-Storlet': self.storlet_name} + headers, content = client.get_object( + self.url, self.token, self.container, 'test_link', + headers=req_headers) + self.assertEqual('simple', headers['x-object-meta-test']) + self.assertEqual(self.content, content) + + +class TestSymlinkOnProxy(TestSymlink): + def setUp(self): + super(TestSymlinkOnProxy, self).setUp() + self.additional_headers = {'X-Storlet-Run-On-Proxy': ''} + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/swift_middleware/handlers/test_obj.py b/tests/unit/swift_middleware/handlers/test_obj.py index 4d23851c..35203abd 100644 --- a/tests/unit/swift_middleware/handlers/test_obj.py +++ b/tests/unit/swift_middleware/handlers/test_obj.py @@ -36,7 +36,7 @@ class TestStorletMiddlewareObject(BaseTestStorletMiddleware): def test_call_unsupported_method(self): def call(method): path = '/sda1/p/AUTH_a/c/o' - headers = {'X-Backend-Storlet-Policy-Index': '0', + headers = {'X-Backend-Storage-Policy-Index': '0', 'X-Run-Storlet': 'Storlet-1.0.jar'} req = Request.blank(path, environ={'REQUEST_METHOD': method}, headers=headers) @@ -64,7 +64,7 @@ class TestStorletMiddlewareObject(BaseTestStorletMiddleware): def basic_get(path): req = Request.blank( path, environ={'REQUEST_METHOD': 'GET'}, - headers={'X-Backend-Storlet-Policy-Index': '0'}) + headers={'X-Backend-Storage-Policy-Index': '0'}) self.base_app.register('GET', path, HTTPOk, body=b'FAKE APP') resp = self.get_response(req) self.assertEqual('200 OK', resp.status) @@ -81,7 +81,7 @@ class TestStorletMiddlewareObject(BaseTestStorletMiddleware): req = Request.blank( target, environ={'REQUEST_METHOD': 'GET'}, - headers={'X-Backend-Storlet-Policy-Index': '0', + headers={'X-Backend-Storage-Policy-Index': '0', 'X-Run-Storlet': 'Storlet-1.0.jar'}) resp = self.get_response(req) self.assertEqual('200 OK', resp.status) @@ -93,7 +93,7 @@ class TestStorletMiddlewareObject(BaseTestStorletMiddleware): req = Request.blank( target, environ={'REQUEST_METHOD': 'GET'}, - headers={'X-Backend-Storlet-Policy-Index': '0', + headers={'X-Backend-Storage-Policy-Index': '0', 'X-Run-Storlet': 'Storlet-1.0.jar', 'Range': 'bytes=10-20'}) resp = self.get_response(req) @@ -106,7 +106,7 @@ class TestStorletMiddlewareObject(BaseTestStorletMiddleware): req = Request.blank( target, environ={'REQUEST_METHOD': 'GET'}, - headers={'X-Backend-Storlet-Policy-Index': '0', + headers={'X-Backend-Storage-Policy-Index': '0', 'X-Run-Storlet': 'Storlet-1.0.jar', 'X-Storlet-Range': 'bytes=1-6', 'Range': 'bytes=1-6'}) @@ -122,7 +122,7 @@ class TestStorletMiddlewareObject(BaseTestStorletMiddleware): req = Request.blank( target, environ={'REQUEST_METHOD': 'GET'}, - headers={'X-Backend-Storlet-Policy-Index': '0', + headers={'X-Backend-Storage-Policy-Index': '0', 'X-Run-Storlet': 'Storlet-1.0.jar', 'X-Storlet-Range': 'bytes=1-6', 'Range': 'bytes=1-6', @@ -141,7 +141,7 @@ class TestStorletMiddlewareObject(BaseTestStorletMiddleware): req = Request.blank( target, environ={'REQUEST_METHOD': 'GET'}, - headers={'X-Backend-Storlet-Policy-Index': '0', + headers={'X-Backend-Storage-Policy-Index': '0', 'X-Run-Storlet': 'Storlet-1.0.jar'}) resp = self.get_response(req) self.assertEqual('200 OK', resp.status) @@ -155,19 +155,34 @@ class TestStorletMiddlewareObject(BaseTestStorletMiddleware): req = Request.blank( target, environ={'REQUEST_METHOD': 'GET'}, - headers={'X-Backend-Storlet-Policy-Index': '0', + headers={'X-Backend-Storage-Policy-Index': '0', 'multipart-manifest': 'get', 'X-Run-Storlet': 'Storlet-1.0.jar'}) resp = self.get_response(req) self.assertEqual('200 OK', resp.status) self.assertEqual(b'FAKE SEGMENT', resp.body) + def test_GET_symlink_with_storlets(self): + target = '/sda1/p/AUTH_a/c/o' + self.base_app.register( + 'GET', target, HTTPOk, + headers={'X-Object-Sysmeta-Symlink-Target': 'c/o2'}, + body=b'FAKE CONTENT') + + req = Request.blank( + target, environ={'REQUEST_METHOD': 'GET'}, + headers={'X-Backend-Storage-Policy-Index': '0', + 'X-Run-Storlet': 'Storlet-1.0.jar'}) + resp = self.get_response(req) + self.assertEqual('200 OK', resp.status) + self.assertEqual(b'FAKE CONTENT', resp.body) + def test_storlets_with_invalid_method(self): target = '/sda1/p/AUTH_a/c/o' req = Request.blank( target, environ={'REQUEST_METHOD': '_parse_vaco'}, - headers={'X-Backend-Storlet-Policy-Index': '0', + headers={'X-Backend-Storage-Policy-Index': '0', 'X-Run-Storlet': 'Storlet-1.0.jar'}) resp = self.get_response(req) self.assertEqual('405 Method Not Allowed', resp.status) diff --git a/tests/unit/swift_middleware/handlers/test_proxy.py b/tests/unit/swift_middleware/handlers/test_proxy.py index 60e6a8cb..4f612221 100644 --- a/tests/unit/swift_middleware/handlers/test_proxy.py +++ b/tests/unit/swift_middleware/handlers/test_proxy.py @@ -220,6 +220,23 @@ class TestStorletMiddlewareProxy(BaseTestStorletMiddleware): calls = self.base_app.get_calls() self.assertEqual(2, len(calls)) + def test_GET_symlink_with_storlets(self): + target = '/v1/AUTH_a/c/o' + self.base_app.register('GET', target, HTTPOk, + headers={'x-symlink-target': 'c/o2'}, + body=b'FAKE APP') + storlet = '/v1/AUTH_a/storlet/Storlet-1.0.jar' + self.base_app.register('GET', storlet, HTTPOk, body=b'jar binary') + + with storlet_enabled(): + headers = {'X-Run-Storlet': 'Storlet-1.0.jar'} + resp = self.get_request_response(target, 'GET', headers=headers) + self.assertEqual('200 OK', resp.status) + self.assertEqual(b'FAKE APP', resp.body) + + calls = self.base_app.get_calls() + self.assertEqual(2, len(calls)) + def test_GET_with_storlets_no_object(self): target = '/v1/AUTH_a/c/' self.base_app.register('GET', target, HTTPOk,