Skip to content

Commit 94e18e3

Browse files
author
Hanzhang Zeng (Roger)
committed
Merge branch 'dev'
2 parents 5b33092 + 8744c89 commit 94e18e3

File tree

33 files changed

+486
-76
lines changed

33 files changed

+486
-76
lines changed

.ci/linux_devops_e2e_tests.sh

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,7 @@ export AzureWebJobsStorage=$LINUXSTORAGECONNECTIONSTRING
55
export AzureWebJobsCosmosDBConnectionString=$LINUXCOSMOSDBCONNECTIONSTRING
66
export AzureWebJobsEventHubConnectionString=$LINUXEVENTHUBCONNECTIONSTRING
77
export AzureWebJobsServiceBusConnectionString=$LINUXSERVICEBUSCONNECTIONSTRING
8+
export AzureWebJobsEventGridTopicUri=$LINUXEVENTGRIDTOPICURI
9+
export AzureWebJobsEventGridConnectionKey=$LINUXEVENTGRIDTOPICCONNECTIONKEY
810

9-
pytest --instafail --cov=./azure_functions_worker --cov-report xml --cov-branch --cov-append tests/endtoend
11+
pytest --instafail --cov=./azure_functions_worker --cov-report xml --cov-branch --cov-append tests/endtoend

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,3 +119,6 @@ py3env/
119119
# PyCharm
120120
.idea/
121121
.idea_modules/
122+
123+
# Profiling info
124+
prof/

azure-pipelines.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,24 @@ jobs:
3131
linuxCosmosDB: $(LinuxCosmosDBConnectionString36)
3232
linuxEventHub: $(LinuxEventHubConnectionString36)
3333
linuxServiceBus: $(LinuxServiceBusConnectionString36)
34+
linuxEventGridTopicUri: $(LinuxEventGridTopicUriString36)
35+
linuxEventGridConnectionKey: $(LinuxEventGridConnectionKeyString36)
3436
Python37:
3537
pythonVersion: '3.7'
3638
linuxStorage: $(LinuxStorageConnectionString37)
3739
linuxCosmosDB: $(LinuxCosmosDBConnectionString37)
3840
linuxEventHub: $(LinuxEventHubConnectionString37)
3941
linuxServiceBus: $(LinuxServiceBusConnectionString37)
42+
linuxEventGridTopicUri: $(LinuxEventGridTopicUriString37)
43+
linuxEventGridConnectionKey: $(LinuxEventGridConnectionKeyString37)
4044
Python38:
4145
pythonVersion: '3.8'
4246
linuxStorage: $(LinuxStorageConnectionString38)
4347
linuxCosmosDB: $(LinuxCosmosDBConnectionString38)
4448
linuxEventHub: $(LinuxEventHubConnectionString38)
4549
linuxServiceBus: $(LinuxServiceBusConnectionString38)
50+
linuxEventGridTopicUri: $(LinuxEventGridTopicUriString38)
51+
linuxEventGridConnectionKey: $(LinuxEventGridConnectionKeyString38)
4652
steps:
4753
- task: UsePythonVersion@0
4854
inputs:
@@ -70,6 +76,8 @@ jobs:
7076
LINUXCOSMOSDBCONNECTIONSTRING: $(linuxCosmosDB)
7177
LINUXEVENTHUBCONNECTIONSTRING: $(linuxEventHub)
7278
LINUXSERVICEBUSCONNECTIONSTRING: $(linuxServiceBus)
79+
LINUXEVENTGRIDTOPICURI: $(linuxEventGridTopicUri)
80+
LINUXEVENTGRIDTOPICCONNECTIONKEY: $(linuxEventGridConnectionKey)
7381
displayName: 'E2E Tests'
7482
- task: PublishCodeCoverageResults@1
7583
inputs:

azure_functions_worker/dispatcher.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,7 @@ async def _handle__invocation_request(self, req):
399399
exception=self._serialize_exception(ex))))
400400

401401
async def _handle__function_environment_reload_request(self, req):
402+
'''Only runs on Linux Consumption placeholder specialization.'''
402403
try:
403404
logger.info('Received FunctionEnvironmentReloadRequest, '
404405
'request ID: %s', self.request_id)
@@ -410,12 +411,16 @@ async def _handle__function_environment_reload_request(self, req):
410411
# customer use
411412
import azure.functions # NoQA
412413

414+
# Append function project root to module finding sys.path
415+
if func_env_reload_request.function_app_directory:
416+
sys.path.append(func_env_reload_request.function_app_directory)
417+
418+
# Clear sys.path import cache, reload all module from new sys.path
413419
sys.path_importer_cache.clear()
414420

421+
# Reload environment variables
415422
os.environ.clear()
416-
417423
env_vars = func_env_reload_request.environment_variables
418-
419424
for var in env_vars:
420425
os.environ[var] = env_vars[var]
421426

azure_functions_worker/testutils.py

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,14 @@
4444
E2E_TESTS_ROOT = TESTS_ROOT / E2E_TESTS_FOLDER
4545
UNIT_TESTS_FOLDER = pathlib.Path('unittests')
4646
UNIT_TESTS_ROOT = TESTS_ROOT / UNIT_TESTS_FOLDER
47-
DEFAULT_WEBHOST_DLL_PATH = PROJECT_ROOT / 'build' / 'webhost' / \
48-
'Microsoft.Azure.WebJobs.Script.WebHost.dll'
47+
WEBHOST_DLL = "Microsoft.Azure.WebJobs.Script.WebHost.dll"
48+
DEFAULT_WEBHOST_DLL_PATH = PROJECT_ROOT / 'build' / 'webhost' / WEBHOST_DLL
4949
EXTENSIONS_PATH = PROJECT_ROOT / 'build' / 'extensions' / 'bin'
5050
FUNCS_PATH = TESTS_ROOT / UNIT_TESTS_FOLDER / 'http_functions'
5151
WORKER_PATH = PROJECT_ROOT / 'python' / 'test'
5252
WORKER_CONFIG = PROJECT_ROOT / '.testconfig'
5353
ON_WINDOWS = platform.system() == 'Windows'
54+
LOCALHOST = "127.0.0.1"
5455

5556
HOST_JSON_TEMPLATE = """\
5657
{
@@ -317,7 +318,7 @@ def __init__(self, loop, scripts_dir):
317318
self._server = grpc.server(self._threadpool)
318319
self._servicer = _MockWebHostServicer(self)
319320
protos.add_FunctionRpcServicer_to_server(self._servicer, self._server)
320-
self._port = self._server.add_insecure_port('127.0.0.1:0')
321+
self._port = self._server.add_insecure_port(f'{LOCALHOST}:0')
321322

322323
self._worker_id = self.make_id()
323324
self._request_id = self.make_id()
@@ -459,7 +460,7 @@ async def __aenter__(self):
459460
await self._host.start()
460461

461462
self._worker = await dispatcher. \
462-
Dispatcher.connect('127.0.0.1', self._host._port,
463+
Dispatcher.connect(LOCALHOST, self._host._port,
463464
self._host.worker_id,
464465
self._host.request_id, connect_timeout=5.0)
465466

@@ -469,6 +470,7 @@ async def __aenter__(self):
469470
wait([self._host._connected_fut, self._worker_task],
470471
return_when=asyncio.FIRST_COMPLETED)
471472

473+
# noinspection PyBroadException
472474
try:
473475
if self._worker_task in done:
474476
self._worker_task.result()
@@ -502,7 +504,7 @@ async def __aexit__(self, *exc):
502504
def start_mockhost(*, script_root=FUNCS_PATH):
503505
tests_dir = TESTS_ROOT
504506
scripts_dir = tests_dir / script_root
505-
if not scripts_dir.exists() or not scripts_dir.is_dir():
507+
if not (scripts_dir.exists() and scripts_dir.is_dir()):
506508
raise RuntimeError(
507509
f'invalid script_root argument: '
508510
f'{scripts_dir} directory does not exist')
@@ -536,7 +538,7 @@ def close(self):
536538

537539
def _find_open_port():
538540
with socket.socket() as s:
539-
s.bind(('127.0.0.1', 0))
541+
s.bind((LOCALHOST, 0))
540542
s.listen(1)
541543
return s.getsockname()[1]
542544

@@ -572,10 +574,10 @@ def popen_webhost(*, stdout, stderr, script_root=FUNCS_PATH, port=None):
572574
if not dll:
573575
dll = DEFAULT_WEBHOST_DLL_PATH
574576

575-
secrets = SECRETS_TEMPLATE
576-
577577
os.makedirs(dll.parent / 'Secrets', exist_ok=True)
578578
with open(dll.parent / 'Secrets' / 'host.json', 'w') as f:
579+
secrets = SECRETS_TEMPLATE
580+
579581
f.write(secrets)
580582

581583
if dll and pathlib.Path(dll).exists():
@@ -605,11 +607,7 @@ def popen_webhost(*, stdout, stderr, script_root=FUNCS_PATH, port=None):
605607
]))
606608

607609
worker_path = os.environ.get('PYAZURE_WORKER_DIR')
608-
if not worker_path:
609-
worker_path = WORKER_PATH
610-
else:
611-
worker_path = pathlib.Path(worker_path)
612-
610+
worker_path = WORKER_PATH if not worker_path else pathlib.Path(worker_path)
613611
if not worker_path.exists():
614612
raise RuntimeError(f'Worker path {worker_path} does not exist')
615613

@@ -640,6 +638,15 @@ def popen_webhost(*, stdout, stderr, script_root=FUNCS_PATH, port=None):
640638
if servicebus:
641639
extra_env['AzureWebJobsServiceBusConnectionString'] = servicebus
642640

641+
eventgrid_topic_uri = testconfig['azure'].get('eventgrid_topic_uri')
642+
if eventgrid_topic_uri:
643+
extra_env['AzureWebJobsEventGridTopicUri'] = eventgrid_topic_uri
644+
645+
eventgrid_topic_key = testconfig['azure'].get('eventgrid_topic_key')
646+
if eventgrid_topic_key:
647+
extra_env['AzureWebJobsEventGridConnectionKey'] = \
648+
eventgrid_topic_key
649+
643650
if port is not None:
644651
extra_env['ASPNETCORE_URLS'] = f'http://*:{port}'
645652

@@ -655,11 +662,7 @@ def popen_webhost(*, stdout, stderr, script_root=FUNCS_PATH, port=None):
655662

656663

657664
def start_webhost(*, script_dir=None, stdout=None):
658-
if script_dir:
659-
script_root = TESTS_ROOT / script_dir
660-
else:
661-
script_root = FUNCS_PATH
662-
665+
script_root = TESTS_ROOT / script_dir if script_dir else FUNCS_PATH
663666
if stdout is None:
664667
if is_envvar_true(PYAZURE_WEBHOST_DEBUG):
665668
stdout = sys.stdout
@@ -670,8 +673,8 @@ def start_webhost(*, script_dir=None, stdout=None):
670673
proc = popen_webhost(stdout=stdout, stderr=subprocess.STDOUT,
671674
script_root=script_root, port=port)
672675

673-
addr = f'http://127.0.0.1:{port}'
674-
for n in range(10):
676+
addr = f'http://{LOCALHOST}:{port}'
677+
for _ in range(10):
675678
try:
676679
r = requests.get(f'{addr}/api/ping',
677680
params={'code': 'testFunctionKey'})
@@ -682,11 +685,11 @@ def start_webhost(*, script_dir=None, stdout=None):
682685
except requests.exceptions.ConnectionError:
683686
pass
684687

685-
time.sleep(0.5)
688+
time.sleep(1)
686689
else:
687690
proc.terminate()
688691
try:
689-
proc.wait(10)
692+
proc.wait(20)
690693
except subprocess.TimeoutExpired:
691694
proc.kill()
692695
raise RuntimeError('could not start the webworker')
@@ -697,7 +700,7 @@ def start_webhost(*, script_dir=None, stdout=None):
697700
def create_dummy_dispatcher():
698701
dummy_event_loop = asyncio.new_event_loop()
699702
disp = dispatcher.Dispatcher(
700-
dummy_event_loop, '127.0.0.1', 0,
703+
dummy_event_loop, LOCALHOST, 0,
701704
'test_worker_id', 'test_request_id',
702705
1.0, 1000)
703706
dummy_event_loop.close()

python/prodV2/worker.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,33 @@
1313
# Azure environment variables
1414
AZURE_WEBSITE_INSTANCE_ID = "WEBSITE_INSTANCE_ID"
1515
AZURE_CONTAINER_NAME = "CONTAINER_NAME"
16+
AZURE_WEBJOBS_SCRIPT_ROOT = "AzureWebJobsScriptRoot"
1617

1718

1819
def is_azure_environment():
20+
'''Check if the function app is running on the cloud'''
1921
return (AZURE_CONTAINER_NAME in os.environ
2022
or AZURE_WEBSITE_INSTANCE_ID in os.environ)
2123

2224

25+
def add_script_root_to_sys_path():
26+
'''Append function project root to module finding sys.path'''
27+
functions_script_root = os.getenv(AZURE_WEBJOBS_SCRIPT_ROOT)
28+
if functions_script_root is not None:
29+
sys.path.append(functions_script_root)
30+
31+
2332
def determine_user_pkg_paths():
33+
'''This finds the user packages when function apps are running on the cloud
34+
35+
For Python 3.6 app, the third-party packages can live in any of the paths:
36+
/home/site/wwwroot/.python_packages/lib/site-packages
37+
/home/site/wwwroot/.python_packages/lib/python3.6/site-packages
38+
/home/site/wwwroot/worker_venv/lib/python3.6/site-packages
39+
40+
For Python 3.7, we only accept:
41+
/home/site/wwwroot/.python_packages/lib/site-packages
42+
'''
2443
minor_version = sys.version_info[1]
2544

2645
home = Path.home()
@@ -49,13 +68,19 @@ def determine_user_pkg_paths():
4968
user_pkg_paths = determine_user_pkg_paths()
5069

5170
joined_pkg_paths = os.pathsep.join(user_pkg_paths)
71+
72+
# On cloud, we prioritize third-party user packages
73+
# over worker packages in PYTHONPATH
5274
env['PYTHONPATH'] = f'{joined_pkg_paths}:{func_worker_dir}'
5375
os.execve(sys.executable,
5476
[sys.executable, '-m', 'azure_functions_worker']
5577
+ sys.argv[1:],
5678
env)
5779
else:
80+
# On local development, we prioritize worker packages over
81+
# third-party user packages (in .venv)
5882
sys.path.insert(1, func_worker_dir)
83+
add_script_root_to_sys_path()
5984
from azure_functions_worker import main
6085

6186
main.main()

python/prodV3/worker.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,33 @@
1313
# Azure environment variables
1414
AZURE_WEBSITE_INSTANCE_ID = "WEBSITE_INSTANCE_ID"
1515
AZURE_CONTAINER_NAME = "CONTAINER_NAME"
16+
AZURE_WEBJOBS_SCRIPT_ROOT = "AzureWebJobsScriptRoot"
1617

1718

1819
def is_azure_environment():
20+
'''Check if the function app is running on the cloud'''
1921
return (AZURE_CONTAINER_NAME in os.environ
2022
or AZURE_WEBSITE_INSTANCE_ID in os.environ)
2123

2224

25+
def add_script_root_to_sys_path():
26+
'''Append function project root to module finding sys.path'''
27+
functions_script_root = os.getenv(AZURE_WEBJOBS_SCRIPT_ROOT)
28+
if functions_script_root is not None:
29+
sys.path.append(functions_script_root)
30+
31+
2332
def determine_user_pkg_paths():
33+
'''This finds the user packages when function apps are running on the cloud
34+
35+
For Python 3.6 app, the third-party packages can live in any of the paths:
36+
/home/site/wwwroot/.python_packages/lib/site-packages
37+
/home/site/wwwroot/.python_packages/lib/python3.6/site-packages
38+
/home/site/wwwroot/worker_venv/lib/python3.6/site-packages
39+
40+
For Python 3.7 and Python 3.8, we only accept:
41+
/home/site/wwwroot/.python_packages/lib/site-packages
42+
'''
2443
minor_version = sys.version_info[1]
2544

2645
home = Path.home()
@@ -49,13 +68,19 @@ def determine_user_pkg_paths():
4968
user_pkg_paths = determine_user_pkg_paths()
5069

5170
joined_pkg_paths = os.pathsep.join(user_pkg_paths)
71+
72+
# On cloud, we prioritize third-party user packages
73+
# over worker packages in PYTHONPATH
5274
env['PYTHONPATH'] = f'{joined_pkg_paths}:{func_worker_dir}'
5375
os.execve(sys.executable,
5476
[sys.executable, '-m', 'azure_functions_worker']
5577
+ sys.argv[1:],
5678
env)
5779
else:
80+
# On local development, we prioritize worker packages over
81+
# third-party user packages (in .venv)
5882
sys.path.insert(1, func_worker_dir)
83+
add_script_root_to_sys_path()
5984
from azure_functions_worker import main
6085

6186
main.main()

python/test/worker.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
1+
import sys
2+
import os
13
from azure_functions_worker import main
24

5+
6+
# Azure environment variables
7+
AZURE_WEBJOBS_SCRIPT_ROOT = "AzureWebJobsScriptRoot"
8+
9+
10+
def add_script_root_to_sys_path():
11+
'''Append function project root to module finding sys.path'''
12+
functions_script_root = os.getenv(AZURE_WEBJOBS_SCRIPT_ROOT)
13+
if functions_script_root is not None:
14+
sys.path.append(functions_script_root)
15+
16+
317
if __name__ == '__main__':
18+
add_script_root_to_sys_path()
419
main.main()

setup.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717

1818

1919
# TODO: change this to something more stable when available.
20-
WEBHOST_URL = ('https://ci.appveyor.com/api/buildjobs/19gqd7drpxhkedea'
21-
'/artifacts/Functions.Binaries.2.0.13036.no-runtime.zip')
20+
WEBHOST_URL = (
21+
'https://github.com/Azure/azure-functions-host/releases/download/'
22+
'/v2.0.14361/Functions.Binaries.2.0.14361.no-runtime.zip'
23+
)
2224

2325
# Extensions necessary for non-core bindings.
2426
AZURE_EXTENSIONS = """\
@@ -195,9 +197,8 @@ def _install_webhost(self):
195197
print('Downloading Azure Functions Web Host...')
196198
urllib.request.urlretrieve(self.webhost_url, zipf.name)
197199
except Exception as e:
198-
print(
199-
f"could not download Azure Functions Web Host binaries "
200-
f"from {self.webhost_url}: {e!r}", file=sys.stderr)
200+
print(f"could not download Azure Functions Web Host binaries "
201+
f"from {self.webhost_url}: {e!r}", file=sys.stderr)
201202
sys.exit(1)
202203

203204
if not self.webhost_dir.exists():
@@ -257,7 +258,7 @@ def run(self):
257258

258259
setup(
259260
name='azure-functions-worker',
260-
version='1.1.4',
261+
version='1.1.5',
261262
description='Python Language Worker for Azure Functions Host',
262263
long_description=long_description,
263264
long_description_content_type='text/markdown',
@@ -285,7 +286,7 @@ def run(self):
285286
],
286287
extras_require={
287288
'dev': [
288-
'azure-functions==1.3.0',
289+
'azure-functions==1.3.1',
289290
'azure-eventhub~=5.1.0',
290291
'python-dateutil~=2.8.1',
291292
'flake8~=3.7.9',

0 commit comments

Comments
 (0)