From ceac558cfe326ce35957fad8bbd2c796b01fd579 Mon Sep 17 00:00:00 2001 From: wangbill Date: Fri, 13 Sep 2024 11:38:34 -0700 Subject: [PATCH] fix: Http V2 route params update (#1491) * Http V2 route params update Worker Changes to get route parameters of http invocation request from invocation request "route_params" attributes after host made update in https://github.com/Azure/azure-functions-host/pull/9997. TODO: Update lc test after lc image released with host version containing the change. * test: add unit tests * chore: fix linting * fix: linting --------- Co-authored-by: gavin-aguiar <80794152+gavin-aguiar@users.noreply.github.com> --- azure_functions_worker/constants.py | 2 +- azure_functions_worker/dispatcher.py | 14 +++++-- azure_functions_worker/http_v2.py | 7 +--- tests/unittests/test_dispatcher.py | 62 +++++++++++++++++++++++++++- 4 files changed, 74 insertions(+), 11 deletions(-) diff --git a/azure_functions_worker/constants.py b/azure_functions_worker/constants.py index 6611a919..b916252c 100644 --- a/azure_functions_worker/constants.py +++ b/azure_functions_worker/constants.py @@ -11,7 +11,7 @@ WORKER_STATUS = "WorkerStatus" SHARED_MEMORY_DATA_TRANSFER = "SharedMemoryDataTransfer" FUNCTION_DATA_CACHE = "FunctionDataCache" HTTP_URI = "HttpUri" - +REQUIRES_ROUTE_PARAMETERS = "RequiresRouteParameters" # When this capability is enabled, logs are not piped back to the # host from the worker. Logs will directly go to where the user has # configured them to go. This is to ensure that the logs are not diff --git a/azure_functions_worker/dispatcher.py b/azure_functions_worker/dispatcher.py index a9849b28..b9e35061 100644 --- a/azure_functions_worker/dispatcher.py +++ b/azure_functions_worker/dispatcher.py @@ -39,6 +39,8 @@ from .constants import ( PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT, PYTHON_THREADPOOL_THREAD_COUNT_MAX_37, PYTHON_THREADPOOL_THREAD_COUNT_MIN, + REQUIRES_ROUTE_PARAMETERS, + HTTP_URI ) from .extension import ExtensionManager from .http_v2 import ( @@ -403,8 +405,9 @@ class Dispatcher(metaclass=DispatcherMeta): caller_info="worker_init_request") if HttpV2Registry.http_v2_enabled(): - capabilities[constants.HTTP_URI] = \ + capabilities[HTTP_URI] = \ initialize_http_server(self._host) + capabilities[REQUIRES_ROUTE_PARAMETERS] = _TRUE except HttpServerInitError: raise @@ -640,8 +643,10 @@ class Dispatcher(metaclass=DispatcherMeta): http_request = await http_coordinator.get_http_request_async( invocation_id) - await sync_http_request(http_request, invoc_request) - args[fi.trigger_metadata.get('param_name')] = http_request + trigger_arg_name = fi.trigger_metadata.get('param_name') + func_http_request = args[trigger_arg_name] + await sync_http_request(http_request, func_http_request) + args[trigger_arg_name] = http_request fi_context = self._get_context(invoc_request, fi.name, fi.directory) @@ -790,8 +795,9 @@ class Dispatcher(metaclass=DispatcherMeta): caller_info="environment_reload_request") if HttpV2Registry.http_v2_enabled(): - capabilities[constants.HTTP_URI] = \ + capabilities[HTTP_URI] = \ initialize_http_server(self._host) + capabilities[REQUIRES_ROUTE_PARAMETERS] = _TRUE except HttpServerInitError: raise except Exception as ex: diff --git a/azure_functions_worker/http_v2.py b/azure_functions_worker/http_v2.py index 2aaf18bd..4eeeea9d 100644 --- a/azure_functions_worker/http_v2.py +++ b/azure_functions_worker/http_v2.py @@ -246,14 +246,11 @@ def initialize_http_server(host_addr, **kwargs): from e -async def sync_http_request(http_request, invoc_request): +async def sync_http_request(http_request, func_http_request): # Sync http request route params from invoc_request to http_request - route_params = {key: item.string for key, item - in invoc_request.trigger_metadata.items() - if key not in ['Headers', 'Query']} (HttpV2Registry.ext_base().RequestTrackerMeta .get_synchronizer() - .sync_route_params(http_request, route_params)) + .sync_route_params(http_request, func_http_request.route_params)) class HttpV2Registry: diff --git a/tests/unittests/test_dispatcher.py b/tests/unittests/test_dispatcher.py index 32eca34b..2bb7efdb 100644 --- a/tests/unittests/test_dispatcher.py +++ b/tests/unittests/test_dispatcher.py @@ -20,7 +20,7 @@ from azure_functions_worker.constants import ( PYTHON_THREADPOOL_THREAD_COUNT, PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT, PYTHON_THREADPOOL_THREAD_COUNT_MAX_37, - PYTHON_THREADPOOL_THREAD_COUNT_MIN, + PYTHON_THREADPOOL_THREAD_COUNT_MIN, HTTP_URI, REQUIRES_ROUTE_PARAMETERS, ) from azure_functions_worker.dispatcher import Dispatcher, ContextEnabledTask from azure_functions_worker.version import VERSION @@ -33,6 +33,8 @@ DISPATCHER_STEIN_FUNCTIONS_DIR = testutils.UNIT_TESTS_FOLDER / \ 'dispatcher_functions_stein' FUNCTION_APP_DIRECTORY = UNIT_TESTS_ROOT / 'dispatcher_functions' / \ 'dispatcher_functions_stein' +HTTPV2_FUNCTION_APP_DIRECTORY = UNIT_TESTS_ROOT / 'dispatcher_functions' / \ + 'http_v2' / 'fastapi' class TestThreadPoolSettingsPython37(testutils.AsyncTestCase): @@ -767,6 +769,7 @@ class TestDispatcherIndexingInInit(unittest.TestCase): asyncio.set_event_loop(self.loop) self.dispatcher = testutils.create_dummy_dispatcher() sys.path.append(str(FUNCTION_APP_DIRECTORY)) + sys.path.append(str(HTTPV2_FUNCTION_APP_DIRECTORY)) def tearDown(self): self.loop.close() @@ -991,6 +994,63 @@ class TestDispatcherIndexingInInit(unittest.TestCase): response.function_load_response.result.exception.message, "Exception: Mocked Exception") + @patch.dict(os.environ, {PYTHON_ENABLE_INIT_INDEXING: 'true'}) + @patch("azure_functions_worker.http_v2.HttpV2Registry.http_v2_enabled", + return_value=True) + def test_dispatcher_http_v2_init_request_fail(self, mock_http_v2_enabled): + request = protos.StreamingMessage( + worker_init_request=protos.WorkerInitRequest( + host_version="2.3.4", + function_app_directory=str(HTTPV2_FUNCTION_APP_DIRECTORY) + ) + ) + + resp = self.loop.run_until_complete( + self.dispatcher._handle__worker_init_request(request) + ) + + mock_http_v2_enabled.assert_called_once() + self.assertIsNotNone(self.dispatcher._function_metadata_exception) + + capabilities = resp.worker_init_response.capabilities + self.assertNotIn(HTTP_URI, capabilities) + self.assertNotIn(REQUIRES_ROUTE_PARAMETERS, capabilities) + + # Cleanup + del sys.modules['function_app'] + + @patch.dict(os.environ, {PYTHON_ENABLE_INIT_INDEXING: 'true'}) + @patch("azure_functions_worker.http_v2.HttpV2Registry.http_v2_enabled", + return_value=True) + @patch("azure_functions_worker.dispatcher.initialize_http_server", + return_value="http://localhost:8080") + @patch("azure_functions_worker.dispatcher.Dispatcher" + ".load_function_metadata") + def test_dispatcher_http_v2_init_request_pass(self, mock_http_v2_enabled, + mock_init_http_server, + mock_load_func_metadata): + request = protos.StreamingMessage( + worker_init_request=protos.WorkerInitRequest( + host_version="2.3.4", + function_app_directory=str(HTTPV2_FUNCTION_APP_DIRECTORY) + ) + ) + + resp = self.loop.run_until_complete( + self.dispatcher._handle__worker_init_request(request) + ) + + mock_http_v2_enabled.assert_called_once() + mock_init_http_server.assert_called_once() + mock_load_func_metadata.assert_called_once() + self.assertIsNone(self.dispatcher._function_metadata_exception) + + capabilities = resp.worker_init_response.capabilities + self.assertIn(HTTP_URI, capabilities) + self.assertEqual(capabilities[HTTP_URI], "http://localhost:8080") + self.assertIn(REQUIRES_ROUTE_PARAMETERS, capabilities) + self.assertEqual(capabilities[REQUIRES_ROUTE_PARAMETERS], "true") + class TestContextEnabledTask(unittest.TestCase): def setUp(self):