Repository: Azure/azure-functions-python-worker (language: python)

Issue:
Update python language worker to support handling new command line arguments with functions- prefix
The command-line arguments that the language worker receives from the Functions Host have generic names (such as 'host' and 'port'). This can cause conflicts with other environment variables in certain environments. To address this issue, the Functions Host will now send a new set of arguments prefixed with `functions-<argumentname>`. This will be in addition to the existing arguments.

With the new planned host change, host code will send command line arguments like below. 

> -host 127.0.0.1 --port 50821 --workerId e9efd817-47a1-45dc-9e20-e6f975d7a025 --requestId cbef5957-cdb3-4462-9ee7-ac9f91be0a51 --grpcMaxMessageLength 2147483647 --functions-uri http://127.0.0.1:50821 --functions-workerid e9efd817-47a1-45dc-9e20-e6f975d7a025 --functions-requestid cbef5957-cdb3-4462-9ee7-ac9f91be0a51 --functions-grpcmaxmessagelength 2147483647

The new arguments sent are: 

1. functions-uri
2. functions-request-id
3. functions-worker-id
4. functions-grpc-max-message-length

Initial sanity testing showed that python worker fails to start when the new arguments are sent. We need to fix the python worker to handle these new arguments.

#### Expected behavior with the fix:

1. Use the new arguments (the one with "functions-" prefix).
2. If the new arguments are not present, use the old ones (PORT, HOST etc..)

See https://github.com/Azure/azure-functions-host/issues/9504 for more details.





Discussion / hints:
cc @vrdmr
~For any language workers which are consumed as a NuGet package to host, we do not necessarily need the fallback code (using the old arguments). This is because the next host version which will consume the language worker package, will be always sending the new arguments.~

Fix patch:
diff --git a/azure_functions_worker/main.py b/azure_functions_worker/main.py
index 3edf2840..4e1f70a7 100644
--- a/azure_functions_worker/main.py
+++ b/azure_functions_worker/main.py
@@ -2,7 +2,6 @@
 # Licensed under the MIT License.
 """Main entrypoint."""
 
-
 import argparse
 
 
@@ -26,6 +25,18 @@ def parse_args():
                              'syslog, or a file path')
     parser.add_argument('--grpcMaxMessageLength', type=int,
                         dest='grpc_max_msg_len')
+    parser.add_argument('--functions-uri', dest='functions_uri', type=str,
+                        help='URI with IP Address and Port used to'
+                             ' connect to the Host via gRPC.')
+    parser.add_argument('--functions-request-id', dest='functions_request_id',
+                        type=str, help='Request ID used for gRPC communication '
+                                       'with the Host.')
+    parser.add_argument('--functions-worker-id',
+                        dest='functions_worker_id', type=str,
+                        help='Worker ID assigned to this language worker.')
+    parser.add_argument('--functions-grpc-max-message-length', type=int,
+                        dest='functions_grpc_max_msg_len',
+                        help='Max grpc message length for Functions')
     return parser.parse_args()
 
 


Test patch:
diff --git a/tests/unittests/test_main.py b/tests/unittests/test_main.py
new file mode 100644
index 00000000..2fe29c8c
--- /dev/null
+++ b/tests/unittests/test_main.py
@@ -0,0 +1,79 @@
+import unittest
+import sys
+from unittest.mock import patch
+from azure_functions_worker.main import parse_args
+
+
+class TestMain(unittest.TestCase):
+
+    @patch.object(sys, 'argv',
+                  ['xxx', '--host', '127.0.0.1',
+                   '--port', '50821',
+                   '--workerId', 'e9efd817-47a1-45dc-9e20-e6f975d7a025',
+                   '--requestId', 'cbef5957-cdb3-4462-9ee7-ac9f91be0a51',
+                   '--grpcMaxMessageLength', '2147483647',
+                   '--functions-uri', 'http://127.0.0.1:50821',
+                   '--functions-worker-id',
+                   'e9efd817-47a1-45dc-9e20-e6f975d7a025',
+                   '--functions-request-id',
+                   'cbef5957-cdb3-4462-9ee7-ac9f91be0a51',
+                   '--functions-grpc-max-message-length', '2147483647'])
+    def test_all_args(self):
+        args = parse_args()
+        self.assertEqual(args.host, '127.0.0.1')
+        self.assertEqual(args.port, 50821)
+        self.assertEqual(args.worker_id,
+                         'e9efd817-47a1-45dc-9e20-e6f975d7a025')
+        self.assertEqual(args.request_id,
+                         'cbef5957-cdb3-4462-9ee7-ac9f91be0a51')
+        self.assertEqual(args.grpc_max_msg_len, 2147483647)
+        self.assertEqual(args.functions_uri, 'http://127.0.0.1:50821')
+        self.assertEqual(args.functions_worker_id,
+                         'e9efd817-47a1-45dc-9e20-e6f975d7a025')
+        self.assertEqual(args.functions_request_id,
+                         'cbef5957-cdb3-4462-9ee7-ac9f91be0a51')
+        self.assertEqual(args.functions_grpc_max_msg_len, 2147483647)
+
+    @patch.object(sys, 'argv',
+                  ['xxx', '--host', '127.0.0.1',
+                   '--port', '50821',
+                   '--workerId', 'e9efd817-47a1-45dc-9e20-e6f975d7a025',
+                   '--requestId', 'cbef5957-cdb3-4462-9ee7-ac9f91be0a51',
+                   '--grpcMaxMessageLength', '2147483647'])
+    def test_old_args(self):
+        args = parse_args()
+        self.assertEqual(args.host, '127.0.0.1')
+        self.assertEqual(args.port, 50821)
+        self.assertEqual(args.worker_id,
+                         'e9efd817-47a1-45dc-9e20-e6f975d7a025')
+        self.assertEqual(args.request_id,
+                         'cbef5957-cdb3-4462-9ee7-ac9f91be0a51')
+        self.assertEqual(args.grpc_max_msg_len, 2147483647)
+        self.assertIsNone(args.functions_uri)
+        self.assertIsNone(args.functions_worker_id)
+        self.assertIsNone(args.functions_request_id)
+        self.assertIsNone(args.functions_grpc_max_msg_len)
+
+    @patch.object(sys, 'argv',
+                  ['xxx', '--functions-uri', 'http://127.0.0.1:50821',
+                   '--functions-worker-id',
+                   'e9efd817-47a1-45dc-9e20-e6f975d7a025',
+                   '--functions-request-id',
+                   'cbef5957-cdb3-4462-9ee7-ac9f91be0a51',
+                   '--functions-grpc-max-message-length', '2147483647'])
+    def test_new_args(self):
+        args = parse_args()
+        self.assertEqual(args.functions_uri, 'http://127.0.0.1:50821')
+        self.assertEqual(args.functions_worker_id,
+                         'e9efd817-47a1-45dc-9e20-e6f975d7a025')
+        self.assertEqual(args.functions_request_id,
+                         'cbef5957-cdb3-4462-9ee7-ac9f91be0a51')
+        self.assertEqual(args.functions_grpc_max_msg_len, 2147483647)
+
+    @patch.object(sys, 'argv', ['xxx', '--host', 'dummy_host',
+                                '--port', '12345',
+                                '--invalid-arg', 'invalid_value'])
+    def test_invalid_args(self):
+        with self.assertRaises(SystemExit) as context:
+            parse_args()
+        self.assertEqual(context.exception.code, 2)