Repository: Azure/azure-functions-python-worker

Issue:
Populate WorkerMetadata in WorkerInitResponse message
The protobuf has been updated to include a `WorkerMetadata` property within the `WorkerInitResponse` message. The worker should include `WorkerMetadata` when sending the `WorkerInitResponse` to the host.

[PR](https://github.com/Azure/azure-functions-language-worker-protobuf/pull/76) for reference on how to fill in the fields.

You will need to pull in the latest protobuf version first (this [doc](https://dev.azure.com/msazure/One/_git/AAPT-Antares-Docs?path=/TeamDocs/FunctionTeamDocs/EngineeringProcesses/ReleaseProcesses/ProtobufReleaseProcess.md&version=GBmaster&_a=preview) may help).

```proto
message WorkerMetadata {
  // The runtime/stack name
  string runtime_name = 1;

  // The version of the runtime/stack
  string runtime_version = 2;

  // The version of the worker or worker SDK
  string worker_version = 3;

  // The worker bitness/architecture
  string worker_bitness = 4;

  // Optional additional custom properties
  map<string, string> custom_properties = 5;
}
```

Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/Azure/azure-functions-python-worker/pull/1166?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) Report
> Merging [#1166](https://codecov.io/gh/Azure/azure-functions-python-worker/pull/1166?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) (b94fa91) into [dev](https://codecov.io/gh/Azure/azure-functions-python-worker/commit/052b416ef65b6a404d3cf12d0504fc854646281b?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) (052b416) will **increase** coverage by `0.19%`.
> The diff coverage is `100.00%`.

```diff
@@            Coverage Diff             @@
##              dev    #1166      +/-   ##
==========================================
+ Coverage   83.71%   83.90%   +0.19%     
==========================================
  Files          34       34              
  Lines        1885     1889       +4     
  Branches      276      363      +87     
==========================================
+ Hits         1578     1585       +7     
+ Misses        236      233       -3     
  Partials       71       71              
```

| Flag | Coverage Δ | |
|---|---|---|
| unittests | `83.85% <100.00%> (+0.19%)` | :arrow_up: |

Flags with carried forward coverage won't be shown. [Click here](https://docs.codecov.io/docs/carryforward-flags?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#carryforward-flags-in-the-pull-request-comment) to find out more.

| [Impacted Files](https://codecov.io/gh/Azure/azure-functions-python-worker/pull/1166?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure) | Coverage Δ | |
|---|---|---|
| [azure\_functions\_worker/protos/\_\_init\_\_.py](https://codecov.io/gh/Azure/azure-functions-python-worker/pull/1166?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVfZnVuY3Rpb25zX3dvcmtlci9wcm90b3MvX19pbml0X18ucHk=) | `100.00% <ø> (ø)` | |
| [azure\_functions\_worker/dispatcher.py](https://codecov.io/gh/Azure/azure-functions-python-worker/pull/1166?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVfZnVuY3Rpb25zX3dvcmtlci9kaXNwYXRjaGVyLnB5) | `88.97% <100.00%> (+0.11%)` | :arrow_up: |
| [.../shared\_memory\_data\_transfer/file\_accessor\_unix.py](https://codecov.io/gh/Azure/azure-functions-python-worker/pull/1166?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure#diff-YXp1cmVfZnVuY3Rpb25zX3dvcmtlci9iaW5kaW5ncy9zaGFyZWRfbWVtb3J5X2RhdGFfdHJhbnNmZXIvZmlsZV9hY2Nlc3Nvcl91bml4LnB5) | `77.45% <0.00%> (+2.94%)` | :arrow_up: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Azure)

YunchuWang: https://github.com/Azure/azure-functions-python-worker/issues/1057

Patch:
diff --git a/azure_functions_worker/dispatcher.py b/azure_functions_worker/dispatcher.py
index 24c25ec..1e60917 100644
--- a/azure_functions_worker/dispatcher.py
+++ b/azure_functions_worker/dispatcher.py
@@ -9,6 +9,7 @@ import asyncio
 import concurrent.futures
 import logging
 import os
+import platform
 import queue
 import sys
 import threading
@@ -24,7 +25,8 @@ from .constants import (PYTHON_THREADPOOL_THREAD_COUNT,
                         PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT,
                         PYTHON_THREADPOOL_THREAD_COUNT_MAX_37,
                         PYTHON_THREADPOOL_THREAD_COUNT_MIN,
-                        PYTHON_ENABLE_DEBUG_LOGGING, SCRIPT_FILE_NAME)
+                        PYTHON_ENABLE_DEBUG_LOGGING, SCRIPT_FILE_NAME,
+                        PYTHON_LANGUAGE_RUNTIME)
 from .extension import ExtensionManager
 from .logging import disable_console_logging, enable_console_logging
 from .logging import enable_debug_logging_recommendation
@@ -93,6 +95,16 @@ class Dispatcher(metaclass=DispatcherMeta):
         self._grpc_thread: threading.Thread = threading.Thread(
             name='grpc-thread', target=self.__poll_grpc)
 
+    @staticmethod
+    def get_worker_metadata():
+        return protos.WorkerMetadata(
+            runtime_name=PYTHON_LANGUAGE_RUNTIME,
+            runtime_version=f"{sys.version_info.major}."
+                            f"{sys.version_info.minor}",
+            worker_version=VERSION,
+            worker_bitness=platform.machine(),
+            custom_properties={})
+
     def get_sync_tp_workers_set(self):
         """We don't know the exact value of the threadcount set for the Python
          3.9 scenarios (as we'll start passing only None by default), and we
@@ -280,6 +292,7 @@ class Dispatcher(metaclass=DispatcherMeta):
             request_id=self.request_id,
             worker_init_response=protos.WorkerInitResponse(
                 capabilities=capabilities,
+                worker_metadata=self.get_worker_metadata(),
                 result=protos.StatusResult(
                     status=protos.StatusResult.Success)))
 
@@ -552,6 +565,8 @@ class Dispatcher(metaclass=DispatcherMeta):
                     func_env_reload_request.function_app_directory)
 
             success_response = protos.FunctionEnvironmentReloadResponse(
+                capabilities={},
+                worker_metadata=self.get_worker_metadata(),
                 result=protos.StatusResult(
                     status=protos.StatusResult.Success))
 
diff --git a/azure_functions_worker/protos/__init__.py b/azure_functions_worker/protos/__init__.py
index 45c31d3..8e74b5c 100644
--- a/azure_functions_worker/protos/__init__.py
+++ b/azure_functions_worker/protos/__init__.py
@@ -31,7 +31,8 @@ from .FunctionRpc_pb2 import (  # NoQA
     CloseSharedMemoryResourcesRequest,
     CloseSharedMemoryResourcesResponse,
     FunctionsMetadataRequest,
-    FunctionMetadataResponse)
+    FunctionMetadataResponse,
+    WorkerMetadata)
 
 from .shared.NullableTypes_pb2 import (
     NullableString,
diff --git a/azure_functions_worker/protos/_src/src/proto/FunctionRpc.proto b/azure_functions_worker/protos/_src/src/proto/FunctionRpc.proto
index 155af60..f5cc48b 100644
--- a/azure_functions_worker/protos/_src/src/proto/FunctionRpc.proto
+++ b/azure_functions_worker/protos/_src/src/proto/FunctionRpc.proto
@@ -238,6 +238,13 @@ message FunctionEnvironmentReloadRequest {
 }
 
 message FunctionEnvironmentReloadResponse {
+  // After specialization, worker sends capabilities & metadata.
+  // Worker metadata captured for telemetry purposes
+  WorkerMetadata worker_metadata = 1;
+
+  // A map of worker supported features/capabilities
+  map<string, string> capabilities = 2;
+
   // Status of the response
   StatusResult result = 3;
 }
@@ -431,6 +438,7 @@ message TypedData {
     CollectionDouble collection_double = 10;
     CollectionSInt64 collection_sint64 = 11;
     ModelBindingData model_binding_data = 12;
+    CollectionModelBindingData collection_model_binding_data = 13;
   }
 }
 
@@ -668,4 +676,9 @@ message ModelBindingData
 
     // The binding data content
     bytes content = 4;
+}
+
+// Used to encapsulate collection model_binding_data
+message CollectionModelBindingData {
+  repeated ModelBindingData model_binding_data = 1;
 }
\ No newline at end of file


Test patch:
diff --git a/tests/unittests/test_dispatcher.py b/tests/unittests/test_dispatcher.py
index 5aab8d3..4c8b058 100644
--- a/tests/unittests/test_dispatcher.py
+++ b/tests/unittests/test_dispatcher.py
@@ -8,6 +8,7 @@ from typing import Optional, Tuple
 from unittest.mock import patch
 
 from azure_functions_worker import protos
+from azure_functions_worker.version import VERSION
 from tests.utils import testutils
 from azure_functions_worker.constants import PYTHON_THREADPOOL_THREAD_COUNT, \
     PYTHON_THREADPOOL_THREAD_COUNT_DEFAULT, \
@@ -62,6 +63,27 @@ class TestThreadPoolSettingsPython37(testutils.AsyncTestCase):
         async with self._ctrl as host:
             r = await host.init_worker('3.0.12345')
             self.assertIsInstance(r.response, protos.WorkerInitResponse)
+            self.assertIsInstance(r.response.worker_metadata,
+                                  protos.WorkerMetadata)
+            self.assertEquals(r.response.worker_metadata.runtime_name,
+                              "python")
+            self.assertEquals(r.response.worker_metadata.worker_version,
+                              VERSION)
+
+    async def test_dispatcher_environment_reload(self):
+        """Test function environment reload response
+        """
+        async with self._ctrl as host:
+            # Reload environment variable on specialization
+            r = await host.reload_environment(environment={})
+            self.assertIsInstance(r.response,
+                                  protos.FunctionEnvironmentReloadResponse)
+            self.assertIsInstance(r.response.worker_metadata,
+                                  protos.WorkerMetadata)
+            self.assertEquals(r.response.worker_metadata.runtime_name,
+                              "python")
+            self.assertEquals(r.response.worker_metadata.worker_version,
+                              VERSION)
 
     async def test_dispatcher_initialize_worker_logging(self):
         """Test if the dispatcher's log can be flushed out during worker