odigos-opentelemetry-python 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
File without changes
@@ -0,0 +1,124 @@
1
+ import threading
2
+ import atexit
3
+ import sys
4
+ import os
5
+ import opentelemetry.sdk._configuration as sdk_config
6
+
7
+ from opentelemetry.sdk.resources import Resource
8
+ from opentelemetry.sdk.resources import ProcessResourceDetector, OTELResourceDetector
9
+ from opentelemetry.sdk.trace import TracerProvider
10
+ from opentelemetry.sdk.trace.export import BatchSpanProcessor
11
+ from opentelemetry.trace import set_tracer_provider
12
+
13
+ from .lib_handling import reorder_python_path, reload_distro_modules
14
+ from .version import VERSION
15
+
16
+ # from .odigos_sampler import OdigosSampler
17
+ # from opentelemetry.sdk.trace.sampling import ParentBased
18
+
19
+ from opamp.http_client import OpAMPHTTPClient
20
+
21
+
22
+ MINIMUM_PYTHON_SUPPORTED_VERSION = (3, 8)
23
+
24
+ def _initialize_components(trace_exporters = None, metric_exporters = None, log_exporters = None , span_processor = None):
25
+ print("_initialize_components")
26
+ resource_attributes_event = threading.Event()
27
+ client = None
28
+
29
+ try:
30
+
31
+ client = start_opamp_client(resource_attributes_event)
32
+ resource_attributes_event.wait(timeout=30) # Wait for the resource attributes to be received for 30 seconds
33
+
34
+ received_value = client.resource_attributes
35
+
36
+ if received_value:
37
+
38
+ auto_resource = {
39
+ "telemetry.distro.name": "odigos",
40
+ "telemetry.distro.version": VERSION,
41
+ }
42
+
43
+ auto_resource.update(received_value)
44
+
45
+ resource = Resource.create(auto_resource) \
46
+ .merge(OTELResourceDetector().detect()) \
47
+ .merge(ProcessResourceDetector().detect())
48
+
49
+ initialize_traces_if_enabled(trace_exporters, resource, span_processor)
50
+ initialize_metrics_if_enabled(metric_exporters, resource)
51
+ initialize_logging_if_enabled(log_exporters, resource)
52
+
53
+ # Reorder the python sys.path to ensure that the user application's dependencies take precedence over the agent's dependencies.
54
+ # This is necessary because the user application's dependencies may be incompatible with those used by the agent.
55
+ reorder_python_path()
56
+ # Reload distro modules to ensure the new path is used.
57
+ reload_distro_modules()
58
+
59
+ except Exception as e:
60
+ if client is not None:
61
+ client.shutdown(custom_failure_message=str(e))
62
+
63
+
64
+ def initialize_traces_if_enabled(trace_exporters, resource, span_processor = None):
65
+ traces_enabled = os.getenv(sdk_config.OTEL_TRACES_EXPORTER, "none").strip().lower()
66
+ if traces_enabled != "none":
67
+
68
+ provider = TracerProvider()
69
+ # TODO: uncomment once the OdigosSampler is implemented
70
+ # odigos_sampler = OdigosSampler()
71
+ # sampler = ParentBased(odigos_sampler)
72
+
73
+ # Exporting using exporters
74
+ if trace_exporters is not None:
75
+ id_generator_name = sdk_config._get_id_generator()
76
+ id_generator = sdk_config._import_id_generator(id_generator_name)
77
+
78
+ provider.id_generator = id_generator
79
+ provider.resource = resource
80
+
81
+ set_tracer_provider(provider)
82
+
83
+ for _, exporter_class in trace_exporters.items():
84
+ exporter_args = {}
85
+ provider.add_span_processor(
86
+ BatchSpanProcessor(exporter_class(**exporter_args))
87
+ )
88
+ # Exporting using EBPF
89
+ else:
90
+ if span_processor is not None:
91
+ provider.add_span_processor(span_processor)
92
+
93
+ # return sampler
94
+
95
+ def initialize_metrics_if_enabled(metric_exporters, resource):
96
+ metrics_enabled = os.getenv(sdk_config.OTEL_METRICS_EXPORTER, "none").strip().lower()
97
+ if metrics_enabled != "none":
98
+ sdk_config._init_metrics(metric_exporters, resource)
99
+
100
+ def initialize_logging_if_enabled(log_exporters, resource):
101
+ logging_enabled = os.getenv(sdk_config.OTEL_LOGS_EXPORTER, "none").strip().lower()
102
+ if logging_enabled != "none":
103
+ sdk_config._init_logging(log_exporters, resource)
104
+
105
+
106
+ def start_opamp_client(event):
107
+ condition = threading.Condition(threading.Lock())
108
+ client = OpAMPHTTPClient(event, condition)
109
+
110
+ python_version_supported = is_supported_python_version()
111
+
112
+ client.start(python_version_supported)
113
+
114
+ def shutdown():
115
+ client.shutdown()
116
+
117
+ # Ensure that the shutdown function is called on program exit
118
+ atexit.register(shutdown)
119
+
120
+ return client
121
+
122
+
123
+ def is_supported_python_version():
124
+ return sys.version_info >= MINIMUM_PYTHON_SUPPORTED_VERSION
@@ -0,0 +1,34 @@
1
+ import sys
2
+
3
+ def reorder_python_path():
4
+ paths_to_move = [path for path in sys.path if path.startswith('/var/odigos/python')]
5
+
6
+ for path in paths_to_move:
7
+ sys.path.remove(path)
8
+ sys.path.append(path)
9
+
10
+
11
+ def reload_distro_modules() -> None:
12
+ # Delete distro modules and their sub-modules, as they have been imported before the path was reordered.
13
+ # The distro modules will be re-imported from the new path.
14
+ needed_module_prefixes = [
15
+ 'google.protobuf',
16
+ 'requests',
17
+ 'charset_normalizer',
18
+ 'certifi',
19
+ 'asgiref'
20
+ 'idna',
21
+ 'deprecated',
22
+ 'importlib_metadata',
23
+ 'packaging',
24
+ 'psutil',
25
+ 'zipp',
26
+ 'urllib3',
27
+ 'uuid_extensions.uuid7',
28
+ 'typing_extensions',
29
+ ]
30
+
31
+ for module in list(sys.modules):
32
+ # Check if the module starts with any of the needed prefixes
33
+ if any(module.startswith(prefix) for prefix in needed_module_prefixes):
34
+ del sys.modules[module]
@@ -0,0 +1,58 @@
1
+ from opentelemetry.sdk.trace.sampling import Sampler, Decision, SamplingResult
2
+ from threading import Lock
3
+ import random
4
+
5
+ class OdigosSampler(Sampler):
6
+ def __init__(self):
7
+ self._lock = Lock()
8
+ self._config = None
9
+
10
+ def should_sample(self, parent_context, trace_id, name, kind, attributes, links):
11
+ with self._lock:
12
+ print(f'attributes : {attributes}')
13
+ print(f'name : {name}')
14
+ print(f'kind : {kind}')
15
+ if self._config is None:
16
+ print('Recording and sampling- no config has been set')
17
+ return SamplingResult(Decision.RECORD_AND_SAMPLE)
18
+
19
+
20
+ rules = self._config.get('rules', [])
21
+ global_fraction = self._config.get('fallbackFraction', 1)
22
+ for rule in rules:
23
+ operands = rule.get('operands', [])
24
+
25
+ print(f'rule operands are: {operands}')
26
+
27
+ rule_fraction = rule.get('fraction', 1)
28
+ for operand in operands: # TODO: implement AND logic
29
+ key = operand.get('key')
30
+ value = operand.get('value')
31
+ print(f'Operand key : {key} and value : {value}')
32
+ if key in attributes:
33
+ print(f'key is in attributes')
34
+ print(f'attributes[key]: {attributes[key]}')
35
+ if attributes[key] == value:
36
+ print(f'attributes[key] == value')
37
+ if random.random() < rule_fraction:
38
+ print(f'sampling brcause the attribute {key} is {value} and the fraction is {rule_fraction}')
39
+ return SamplingResult(Decision.RECORD_AND_SAMPLE)
40
+ else:
41
+ print(f'dropping because the attribute {key} is {value} and the fraction is {rule_fraction}')
42
+ return SamplingResult(Decision.DROP)
43
+
44
+ if random.random() < global_fraction:
45
+ print(f'sampling because the global fraction is {global_fraction} and no rule match')
46
+ return SamplingResult(Decision.RECORD_AND_SAMPLE)
47
+ else:
48
+ print(f'dropping because the global fraction is {global_fraction} and no rule match')
49
+ return SamplingResult(Decision.DROP)
50
+
51
+
52
+ def get_description(self):
53
+ return "OdigosSampler"
54
+
55
+ def update_config(self, new_config):
56
+ with self._lock:
57
+ print('config is updated')
58
+ self._config = new_config
initializer/version.py ADDED
@@ -0,0 +1,2 @@
1
+ # the content of the file is replaced with the real version in the odiglet DOCKERFILE
2
+ VERSION = "development"
@@ -0,0 +1,59 @@
1
+ Metadata-Version: 2.1
2
+ Name: odigos-opentelemetry-python
3
+ Version: 0.1.0
4
+ Summary: Odigos Initializer for Python OpenTelemetry Components
5
+ Author: Tamir David
6
+ Author-email: tamir@odigos.io
7
+ Requires-Python: >=3.8
8
+ Requires-Dist: requests ~=2.7
9
+ Requires-Dist: protobuf <5.0,>=3.19
10
+ Requires-Dist: uuid7 ==0.1.0
11
+ Requires-Dist: opentelemetry-distro ==0.45b0
12
+ Requires-Dist: opentelemetry-exporter-otlp-proto-http ==1.24.0
13
+ Requires-Dist: opentelemetry-instrumentation ==0.45b0
14
+ Requires-Dist: opentelemetry-instrumentation-aio-pika ==0.45b0
15
+ Requires-Dist: opentelemetry-instrumentation-aiohttp-client ==0.45b0
16
+ Requires-Dist: opentelemetry-instrumentation-aiopg ==0.45b0
17
+ Requires-Dist: opentelemetry-instrumentation-asgi ==0.45b0
18
+ Requires-Dist: opentelemetry-instrumentation-asyncio ==0.45b0
19
+ Requires-Dist: opentelemetry-instrumentation-asyncpg ==0.45b0
20
+ Requires-Dist: opentelemetry-instrumentation-boto ==0.45b0
21
+ Requires-Dist: opentelemetry-instrumentation-boto3sqs ==0.45b0
22
+ Requires-Dist: opentelemetry-instrumentation-botocore ==0.45b0
23
+ Requires-Dist: opentelemetry-instrumentation-cassandra ==0.45b0
24
+ Requires-Dist: opentelemetry-instrumentation-celery ==0.45b0
25
+ Requires-Dist: opentelemetry-instrumentation-confluent-kafka ==0.45b0
26
+ Requires-Dist: opentelemetry-instrumentation-dbapi ==0.45b0
27
+ Requires-Dist: opentelemetry-instrumentation-django ==0.45b0
28
+ Requires-Dist: opentelemetry-instrumentation-elasticsearch ==0.45b0
29
+ Requires-Dist: opentelemetry-instrumentation-falcon ==0.45b0
30
+ Requires-Dist: opentelemetry-instrumentation-fastapi ==0.45b0
31
+ Requires-Dist: opentelemetry-instrumentation-flask ==0.45b0
32
+ Requires-Dist: opentelemetry-instrumentation-grpc ==0.45b0
33
+ Requires-Dist: opentelemetry-instrumentation-httpx ==0.45b0
34
+ Requires-Dist: opentelemetry-instrumentation-jinja2 ==0.45b0
35
+ Requires-Dist: opentelemetry-instrumentation-kafka-python ==0.45b0
36
+ Requires-Dist: opentelemetry-instrumentation-logging ==0.45b0
37
+ Requires-Dist: opentelemetry-instrumentation-mysql ==0.45b0
38
+ Requires-Dist: opentelemetry-instrumentation-mysqlclient ==0.45b0
39
+ Requires-Dist: opentelemetry-instrumentation-pika ==0.45b0
40
+ Requires-Dist: opentelemetry-instrumentation-psycopg ==0.45b0
41
+ Requires-Dist: opentelemetry-instrumentation-psycopg2 ==0.45b0
42
+ Requires-Dist: opentelemetry-instrumentation-pymemcache ==0.45b0
43
+ Requires-Dist: opentelemetry-instrumentation-pymongo ==0.45b0
44
+ Requires-Dist: opentelemetry-instrumentation-pymysql ==0.45b0
45
+ Requires-Dist: opentelemetry-instrumentation-pyramid ==0.45b0
46
+ Requires-Dist: opentelemetry-instrumentation-redis ==0.45b0
47
+ Requires-Dist: opentelemetry-instrumentation-remoulade ==0.45b0
48
+ Requires-Dist: opentelemetry-instrumentation-requests ==0.45b0
49
+ Requires-Dist: opentelemetry-instrumentation-sklearn ==0.45b0
50
+ Requires-Dist: opentelemetry-instrumentation-sqlalchemy ==0.45b0
51
+ Requires-Dist: opentelemetry-instrumentation-sqlite3 ==0.45b0
52
+ Requires-Dist: opentelemetry-instrumentation-starlette ==0.45b0
53
+ Requires-Dist: opentelemetry-instrumentation-system-metrics ==0.45b0
54
+ Requires-Dist: opentelemetry-instrumentation-tornado ==0.45b0
55
+ Requires-Dist: opentelemetry-instrumentation-tortoiseorm ==0.45b0
56
+ Requires-Dist: opentelemetry-instrumentation-urllib ==0.45b0
57
+ Requires-Dist: opentelemetry-instrumentation-urllib3 ==0.45b0
58
+ Requires-Dist: opentelemetry-instrumentation-wsgi ==0.45b0
59
+
@@ -0,0 +1,24 @@
1
+ initializer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ initializer/components.py,sha256=8eu3S-PCw0aUfO0yCp_AuAvZ2S33wsre53gKZ-ZvLFc,4613
3
+ initializer/lib_handling.py,sha256=hyeoO6Ob-kGFXKSJEDMYps-yPqwJp9LlsC_umJhANWk,1030
4
+ initializer/odigos_sampler.py,sha256=AJHFt5QfNTOBf-SuzQs7iuPy2xBdFYbh_3zsRPrOKaA,2656
5
+ initializer/version.py,sha256=K1qI7nPdstg724kyNFVrbxtEbF6OBPY0eSHpXxNBsVU,109
6
+ initializer/__pycache__/lib_handling.cpython-312.pyc,sha256=IPPJ6PeknIO3eDb4v2b8GkpT41nhzlxTdH2MAIzOk38,1477
7
+ initializer/__pycache__/odigos_sampler.cpython-312.pyc,sha256=vyjPSIr1Y_z6G7X8xhyq0ioRbLBAZgZlqC64JoTDiWM,3804
8
+ initializer/__pycache__/version.cpython-312.pyc,sha256=lxiniFUHa0MQ9WWqWb_HQDMCbK-ZCqUl4DKisiyFNj4,159
9
+ opamp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ opamp/anyvalue_pb2.py,sha256=1PlYEeJ5cy0bRZZblGGB5taLyA4_Cw1ySI1HbjuH8_I,2619
11
+ opamp/health_status.py,sha256=UJCutxQIDkcY1EiqCAh3HBeumfjvD-4iglGVTKh-nLM,242
12
+ opamp/http_client.py,sha256=ILMnjZAXWVWrm4ZacNAJOIK1tS5-1W1RWFCtLPdfiQc,11789
13
+ opamp/opamp_pb2.py,sha256=xR8LCf-BzWOh6EQZaNR0ydxUKi2KSPmeYR8_JLqa4rg,19537
14
+ opamp/utils.py,sha256=tPza7EyalDSEE68KvuaVmAAK1T2PnhpnppJ69wMlwm8,957
15
+ opamp/__pycache__/__init__.cpython-312.pyc,sha256=BHqZMVcKl_e6txu2GZwcRtVbhqooul0C28N3ub95Fl8,126
16
+ opamp/__pycache__/anyvalue_pb2.cpython-312.pyc,sha256=gtJpW1Gake1_pHlORvkBmXailD8Ui4zrWkGbOUawfGg,2320
17
+ opamp/__pycache__/health_status.cpython-312.pyc,sha256=HWzj9rHIT2zyPDMG32aXDPDjTkrAeNHhR1Rx2OCJKf0,559
18
+ opamp/__pycache__/http_client.cpython-312.pyc,sha256=Mbdbis6f8wbBG3OfyFauCbOqSTk8ws9UCmNHV9-uiug,15874
19
+ opamp/__pycache__/opamp_pb2.cpython-312.pyc,sha256=pdafOdoJt9tdGtY9PPhhG-HmUWrsMZ1GFds6kiVEDKY,14455
20
+ opamp/__pycache__/utils.cpython-312.pyc,sha256=LnnYWjyBRER4CNXpqOXwDAq7q5XLTOj3dGf-Ly40sT0,1566
21
+ odigos_opentelemetry_python-0.1.0.dist-info/METADATA,sha256=LUJ9pY1xfi84E0-tZlqTU0BBuwI066SdztDKeC1OmaU,3282
22
+ odigos_opentelemetry_python-0.1.0.dist-info/WHEEL,sha256=HiCZjzuy6Dw0hdX5R3LCFPDmFS4BWl8H-8W39XfmgX4,91
23
+ odigos_opentelemetry_python-0.1.0.dist-info/top_level.txt,sha256=_HVsa4JqWWNGY6QsUeZw_0MqdxZth0fRptSuwDtrsjU,18
24
+ odigos_opentelemetry_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (72.2.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ initializer
2
+ opamp
opamp/__init__.py ADDED
File without changes
Binary file
opamp/anyvalue_pb2.py ADDED
@@ -0,0 +1,33 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: anyvalue.proto
4
+ # Protobuf Python Version: 4.25.0
5
+ """Generated protocol buffer code."""
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ from google.protobuf.internal import builder as _builder
10
+ # @@protoc_insertion_point(imports)
11
+
12
+ _sym_db = _symbol_database.Default()
13
+
14
+
15
+
16
+
17
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0e\x61nyvalue.proto\x12\x0bopamp.proto\"\xbc\x02\n\x08\x41nyValue\x12#\n\x0cstring_value\x18\x01 \x01(\tH\x00R\x0bstringValue\x12\x1f\n\nbool_value\x18\x02 \x01(\x08H\x00R\tboolValue\x12\x1d\n\tint_value\x18\x03 \x01(\x03H\x00R\x08intValue\x12#\n\x0c\x64ouble_value\x18\x04 \x01(\x01H\x00R\x0b\x64oubleValue\x12:\n\x0b\x61rray_value\x18\x05 \x01(\x0b\x32\x17.opamp.proto.ArrayValueH\x00R\narrayValue\x12>\n\x0ckvlist_value\x18\x06 \x01(\x0b\x32\x19.opamp.proto.KeyValueListH\x00R\x0bkvlistValue\x12!\n\x0b\x62ytes_value\x18\x07 \x01(\x0cH\x00R\nbytesValueB\x07\n\x05value\";\n\nArrayValue\x12-\n\x06values\x18\x01 \x03(\x0b\x32\x15.opamp.proto.AnyValueR\x06values\"=\n\x0cKeyValueList\x12-\n\x06values\x18\x01 \x03(\x0b\x32\x15.opamp.proto.KeyValueR\x06values\"I\n\x08KeyValue\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12+\n\x05value\x18\x02 \x01(\x0b\x32\x15.opamp.proto.AnyValueR\x05valueB\xa0\x01\n\x0f\x63om.opamp.protoB\rAnyvalueProtoP\x01Z1github.com/odigos-io/odigos/opampserver/protobufs\xa2\x02\x03OPX\xaa\x02\x0bOpamp.Proto\xca\x02\x0bOpamp\\Proto\xe2\x02\x17Opamp\\Proto\\GPBMetadata\xea\x02\x0cOpamp::Protob\x06proto3')
18
+
19
+ _globals = globals()
20
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
21
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'anyvalue_pb2', _globals)
22
+ if _descriptor._USE_C_DESCRIPTORS == False:
23
+ _globals['DESCRIPTOR']._options = None
24
+ _globals['DESCRIPTOR']._serialized_options = b'\n\017com.opamp.protoB\rAnyvalueProtoP\001Z1github.com/odigos-io/odigos/opampserver/protobufs\242\002\003OPX\252\002\013Opamp.Proto\312\002\013Opamp\\Proto\342\002\027Opamp\\Proto\\GPBMetadata\352\002\014Opamp::Proto'
25
+ _globals['_ANYVALUE']._serialized_start=32
26
+ _globals['_ANYVALUE']._serialized_end=348
27
+ _globals['_ARRAYVALUE']._serialized_start=350
28
+ _globals['_ARRAYVALUE']._serialized_end=409
29
+ _globals['_KEYVALUELIST']._serialized_start=411
30
+ _globals['_KEYVALUELIST']._serialized_end=472
31
+ _globals['_KEYVALUE']._serialized_start=474
32
+ _globals['_KEYVALUE']._serialized_end=547
33
+ # @@protoc_insertion_point(module_scope)
opamp/health_status.py ADDED
@@ -0,0 +1,8 @@
1
+ from enum import Enum
2
+
3
+ class AgentHealthStatus(str, Enum):
4
+ HEALTHY = "Healthy"
5
+ STARTING = "Starting"
6
+ UNSUPPORTED_RUNTIME_VERSION = "UnsupportedRuntimeVersion"
7
+ TERMINATED = "ProcessTerminated"
8
+ AGENT_FAILURE = "AgentFailure"
opamp/http_client.py ADDED
@@ -0,0 +1,265 @@
1
+ import os
2
+ import sys
3
+ import time
4
+ import threading
5
+ import requests
6
+ import logging
7
+
8
+ from uuid_extensions import uuid7
9
+ from opentelemetry.semconv.resource import ResourceAttributes
10
+ from opentelemetry.context import (
11
+ _SUPPRESS_HTTP_INSTRUMENTATION_KEY,
12
+ attach,
13
+ detach,
14
+ set_value,
15
+ )
16
+
17
+ from opamp import opamp_pb2, anyvalue_pb2, utils
18
+ from opamp.health_status import AgentHealthStatus
19
+
20
+ # Setup the logger
21
+ opamp_logger = logging.getLogger(__name__)
22
+ opamp_logger.setLevel(logging.DEBUG)
23
+ opamp_logger.disabled = True # Comment this line to enable the logger
24
+
25
+
26
+ class OpAMPHTTPClient:
27
+ def __init__(self, event, condition: threading.Condition):
28
+ self.server_host = os.getenv('ODIGOS_OPAMP_SERVER_HOST')
29
+ self.instrumentation_device_id = os.getenv('ODIGOS_INSTRUMENTATION_DEVICE_ID')
30
+ self.server_url = f"http://{self.server_host}/v1/opamp"
31
+ self.resource_attributes = {}
32
+ self.running = True
33
+ self.condition = condition
34
+ self.event = event
35
+ self.next_sequence_num = 0
36
+ self.instance_uid = uuid7().__str__()
37
+ self.remote_config_status = None
38
+
39
+
40
+ def start(self, python_version_supported: bool = None):
41
+ if not python_version_supported:
42
+
43
+ python_version = f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}'
44
+ error_message = f"Opentelemetry SDK require Python in version 3.8 or higher [{python_version} is not supported]"
45
+
46
+ opamp_logger.warning(f"{error_message}, sending disconnect message to OpAMP server...")
47
+ self.send_unsupported_version_disconnect_message(error_message=error_message)
48
+ self.event.set()
49
+ return
50
+
51
+ self.client_thread = threading.Thread(target=self.run, name="OpAMPClientThread", daemon=True)
52
+ self.client_thread.start()
53
+
54
+ def run(self):
55
+ try:
56
+ if not self.mandatory_env_vars_set():
57
+ self.event.set()
58
+ return
59
+
60
+ self.send_first_message_with_retry()
61
+ self.event.set()
62
+
63
+ self.worker()
64
+
65
+ except Exception as e:
66
+ opamp_logger.error(f"Error running OpAMP client: {e}")
67
+ failure_message = self.get_agent_failure_disconnect_message(error_message=str(e))
68
+ self.send_agent_to_server_message(failure_message)
69
+
70
+ # Exiting the opamp thread and set the event to notify the main thread
71
+ self.event.set()
72
+ sys.exit()
73
+
74
+ def get_agent_failure_disconnect_message(self, error_message: str) -> None:
75
+ agent_failure_message = opamp_pb2.AgentToServer()
76
+
77
+ agent_disconnect = self.get_agent_disconnect()
78
+ agent_failure_message.agent_disconnect.CopyFrom(agent_disconnect)
79
+
80
+ agent_health = self.get_agent_health(component_health=False, last_error=error_message, status=AgentHealthStatus.AGENT_FAILURE.value)
81
+ agent_failure_message.health.CopyFrom(agent_health)
82
+
83
+ return agent_failure_message
84
+
85
+ def send_unsupported_version_disconnect_message(self, error_message: str) -> None:
86
+ first_disconnect_message = opamp_pb2.AgentToServer()
87
+
88
+ agent_description = self.get_agent_description()
89
+
90
+ first_disconnect_message.agent_description.CopyFrom(agent_description)
91
+
92
+ agent_disconnect = self.get_agent_disconnect()
93
+ first_disconnect_message.agent_disconnect.CopyFrom(agent_disconnect)
94
+
95
+ agent_health = self.get_agent_health(component_health=False, last_error=error_message, status=AgentHealthStatus.UNSUPPORTED_RUNTIME_VERSION.value)
96
+ first_disconnect_message.health.CopyFrom(agent_health)
97
+
98
+ self.send_agent_to_server_message(first_disconnect_message)
99
+
100
+ def send_first_message_with_retry(self) -> None:
101
+ max_retries = 5
102
+ delay = 2
103
+ for attempt in range(1, max_retries + 1):
104
+ try:
105
+ # Send first message to OpAMP server, Health is false as the component is not initialized
106
+ agent_health = self.get_agent_health(component_health=False, last_error="Python OpenTelemetry agent is starting", status=AgentHealthStatus.STARTING.value)
107
+ agent_description = self.get_agent_description()
108
+ first_message_server_to_agent = self.send_agent_to_server_message(opamp_pb2.AgentToServer(agent_description=agent_description, health=agent_health))
109
+
110
+ self.update_remote_config_status(first_message_server_to_agent)
111
+ self.resource_attributes = utils.parse_first_message_to_resource_attributes(first_message_server_to_agent, opamp_logger)
112
+
113
+ # Send healthy message to OpAMP server
114
+ opamp_logger.info("Reporting healthy to OpAMP server...")
115
+ agent_health = self.get_agent_health(component_health=True, status=AgentHealthStatus.HEALTHY.value)
116
+ self.send_agent_to_server_message(opamp_pb2.AgentToServer(health=agent_health))
117
+
118
+ break
119
+ except Exception as e:
120
+ opamp_logger.error(f"Error sending full state to OpAMP server: {e}")
121
+
122
+ if attempt < max_retries:
123
+ time.sleep(delay)
124
+
125
+ def worker(self):
126
+ while self.running:
127
+ with self.condition:
128
+ try:
129
+ server_to_agent = self.send_heartbeat()
130
+ if self.update_remote_config_status(server_to_agent):
131
+ opamp_logger.info("Remote config updated, applying changes...")
132
+ # TODO: implement changes based on the remote config
133
+
134
+ if server_to_agent.flags & opamp_pb2.ServerToAgentFlags_ReportFullState:
135
+ opamp_logger.info("Received request to report full state")
136
+
137
+ agent_description = self.get_agent_description()
138
+ agent_health = self.get_agent_health(component_health=True, status=AgentHealthStatus.HEALTHY.value)
139
+ agent_to_server = opamp_pb2.AgentToServer(agent_description=agent_description, health=agent_health)
140
+
141
+ server_to_agent = self.send_agent_to_server_message(agent_to_server)
142
+
143
+ self.update_remote_config_status(server_to_agent)
144
+
145
+ except requests.RequestException as e:
146
+ opamp_logger.error(f"Error fetching data: {e}")
147
+ self.condition.wait(30)
148
+
149
+ def send_heartbeat(self) -> opamp_pb2.ServerToAgent:
150
+ opamp_logger.debug("Sending heartbeat to OpAMP server...")
151
+ try:
152
+ agent_to_server = opamp_pb2.AgentToServer(remote_config_status=self.remote_config_status)
153
+ return self.send_agent_to_server_message(agent_to_server)
154
+ except requests.RequestException as e:
155
+ opamp_logger.error(f"Error sending heartbeat to OpAMP server: {e}")
156
+
157
+ def get_agent_description(self) -> opamp_pb2.AgentDescription:
158
+ identifying_attributes = [
159
+ anyvalue_pb2.KeyValue(
160
+ key=ResourceAttributes.SERVICE_INSTANCE_ID,
161
+ value=anyvalue_pb2.AnyValue(string_value=self.instance_uid)
162
+ ),
163
+ anyvalue_pb2.KeyValue(
164
+ key=ResourceAttributes.PROCESS_PID,
165
+ value=anyvalue_pb2.AnyValue(int_value=os.getpid())
166
+ ),
167
+ anyvalue_pb2.KeyValue(
168
+ key=ResourceAttributes.TELEMETRY_SDK_LANGUAGE,
169
+ value=anyvalue_pb2.AnyValue(string_value="python")
170
+ )
171
+ ]
172
+
173
+ return opamp_pb2.AgentDescription(
174
+ identifying_attributes=identifying_attributes,
175
+ non_identifying_attributes=[]
176
+ )
177
+
178
+ def get_agent_disconnect(self) -> opamp_pb2.AgentDisconnect:
179
+ return opamp_pb2.AgentDisconnect()
180
+
181
+ def get_agent_health(self, component_health: bool = None, last_error : str = None, status: str = None) -> opamp_pb2.ComponentHealth:
182
+ health = opamp_pb2.ComponentHealth(
183
+ )
184
+ if component_health is not None:
185
+ health.healthy = component_health
186
+ if last_error is not None:
187
+ health.last_error = last_error
188
+ if status is not None:
189
+ health.status = status
190
+
191
+ return health
192
+
193
+
194
+ def send_agent_to_server_message(self, message: opamp_pb2.AgentToServer) -> opamp_pb2.ServerToAgent:
195
+
196
+ message.instance_uid = self.instance_uid.encode('utf-8')
197
+ message.sequence_num = self.next_sequence_num
198
+ if self.remote_config_status:
199
+ message.remote_config_status.CopyFrom(self.remote_config_status)
200
+
201
+ self.next_sequence_num += 1
202
+ message_bytes = message.SerializeToString()
203
+
204
+ headers = {
205
+ "Content-Type": "application/x-protobuf",
206
+ "X-Odigos-DeviceId": self.instrumentation_device_id
207
+ }
208
+
209
+ try:
210
+ agent_message = attach(set_value(_SUPPRESS_HTTP_INSTRUMENTATION_KEY, True))
211
+ response = requests.post(self.server_url, data=message_bytes, headers=headers, timeout=5)
212
+ response.raise_for_status()
213
+ except requests.Timeout:
214
+ opamp_logger.error("Timeout sending message to OpAMP server")
215
+ return opamp_pb2.ServerToAgent()
216
+ except requests.ConnectionError as e:
217
+ opamp_logger.error(f"Error sending message to OpAMP server: {e}")
218
+ return opamp_pb2.ServerToAgent()
219
+ finally:
220
+ detach(agent_message)
221
+
222
+ server_to_agent = opamp_pb2.ServerToAgent()
223
+ try:
224
+ server_to_agent.ParseFromString(response.content)
225
+ except NotImplementedError as e:
226
+ opamp_logger.error(f"Error parsing response from OpAMP server: {e}")
227
+ return opamp_pb2.ServerToAgent()
228
+ return server_to_agent
229
+
230
+ def mandatory_env_vars_set(self):
231
+ mandatory_env_vars = {
232
+ "ODIGOS_OPAMP_SERVER_HOST": self.server_host,
233
+ "ODIGOS_INSTRUMENTATION_DEVICE_ID": self.instrumentation_device_id
234
+ }
235
+
236
+ for env_var, value in mandatory_env_vars.items():
237
+ if not value:
238
+ opamp_logger.error(f"{env_var} environment variable not set")
239
+ return False
240
+
241
+ return True
242
+
243
+ def shutdown(self, custom_failure_message: str = None):
244
+ self.running = False
245
+ opamp_logger.info("Sending agent disconnect message to OpAMP server...")
246
+ if custom_failure_message:
247
+ disconnect_message = self.get_agent_failure_disconnect_message(error_message=custom_failure_message)
248
+ else:
249
+ agent_health = self.get_agent_health(component_health=False, last_error="Python runtime is exiting", status=AgentHealthStatus.TERMINATED.value)
250
+ disconnect_message = opamp_pb2.AgentToServer(agent_disconnect=opamp_pb2.AgentDisconnect(), health=agent_health)
251
+
252
+ with self.condition:
253
+ self.condition.notify_all()
254
+ self.client_thread.join()
255
+
256
+ self.send_agent_to_server_message(disconnect_message)
257
+
258
+ def update_remote_config_status(self, server_to_agent: opamp_pb2.ServerToAgent) -> bool:
259
+ if server_to_agent.HasField("remote_config"):
260
+ remote_config_hash = server_to_agent.remote_config.config_hash
261
+ remote_config_status = opamp_pb2.RemoteConfigStatus(last_remote_config_hash=remote_config_hash)
262
+ self.remote_config_status = remote_config_status
263
+ return True
264
+
265
+ return False
opamp/opamp_pb2.py ADDED
@@ -0,0 +1,129 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # source: opamp.proto
4
+ # Protobuf Python Version: 4.25.0
5
+ """Generated protocol buffer code."""
6
+ from google.protobuf import descriptor as _descriptor
7
+ from google.protobuf import descriptor_pool as _descriptor_pool
8
+ from google.protobuf import symbol_database as _symbol_database
9
+ from google.protobuf.internal import builder as _builder
10
+ # @@protoc_insertion_point(imports)
11
+
12
+ _sym_db = _symbol_database.Default()
13
+
14
+ from opamp import anyvalue_pb2
15
+
16
+
17
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x0bopamp.proto\x12\x0bopamp.proto\x1a\x0e\x61nyvalue.proto\"\xbc\x06\n\rAgentToServer\x12!\n\x0cinstance_uid\x18\x01 \x01(\x0cR\x0binstanceUid\x12!\n\x0csequence_num\x18\x02 \x01(\x04R\x0bsequenceNum\x12J\n\x11\x61gent_description\x18\x03 \x01(\x0b\x32\x1d.opamp.proto.AgentDescriptionR\x10\x61gentDescription\x12\"\n\x0c\x63\x61pabilities\x18\x04 \x01(\x04R\x0c\x63\x61pabilities\x12\x34\n\x06health\x18\x05 \x01(\x0b\x32\x1c.opamp.proto.ComponentHealthR\x06health\x12G\n\x10\x65\x66\x66\x65\x63tive_config\x18\x06 \x01(\x0b\x32\x1c.opamp.proto.EffectiveConfigR\x0f\x65\x66\x66\x65\x63tiveConfig\x12Q\n\x14remote_config_status\x18\x07 \x01(\x0b\x32\x1f.opamp.proto.RemoteConfigStatusR\x12remoteConfigStatus\x12G\n\x10package_statuses\x18\x08 \x01(\x0b\x32\x1c.opamp.proto.PackageStatusesR\x0fpackageStatuses\x12G\n\x10\x61gent_disconnect\x18\t \x01(\x0b\x32\x1c.opamp.proto.AgentDisconnectR\x0f\x61gentDisconnect\x12\x14\n\x05\x66lags\x18\n \x01(\x04R\x05\x66lags\x12\x66\n\x1b\x63onnection_settings_request\x18\x0b \x01(\x0b\x32&.opamp.proto.ConnectionSettingsRequestR\x19\x63onnectionSettingsRequest\x12P\n\x13\x63ustom_capabilities\x18\x0c \x01(\x0b\x32\x1f.opamp.proto.CustomCapabilitiesR\x12\x63ustomCapabilities\x12\x41\n\x0e\x63ustom_message\x18\r \x01(\x0b\x32\x1a.opamp.proto.CustomMessageR\rcustomMessage\"\x11\n\x0f\x41gentDisconnect\"^\n\x19\x43onnectionSettingsRequest\x12\x41\n\x05opamp\x18\x01 \x01(\x0b\x32+.opamp.proto.OpAMPConnectionSettingsRequestR\x05opamp\"r\n\x1eOpAMPConnectionSettingsRequest\x12P\n\x13\x63\x65rtificate_request\x18\x01 \x01(\x0b\x32\x1f.opamp.proto.CertificateRequestR\x12\x63\x65rtificateRequest\"&\n\x12\x43\x65rtificateRequest\x12\x10\n\x03\x63sr\x18\x01 \x01(\x0cR\x03\x63sr\"\xc8\x05\n\rServerToAgent\x12!\n\x0cinstance_uid\x18\x01 \x01(\x0cR\x0binstanceUid\x12G\n\x0e\x65rror_response\x18\x02 \x01(\x0b\x32 .opamp.proto.ServerErrorResponseR\rerrorResponse\x12\x43\n\rremote_config\x18\x03 \x01(\x0b\x32\x1e.opamp.proto.AgentRemoteConfigR\x0cremoteConfig\x12V\n\x13\x63onnection_settings\x18\x04 \x01(\x0b\x32%.opamp.proto.ConnectionSettingsOffersR\x12\x63onnectionSettings\x12M\n\x12packages_available\x18\x05 \x01(\x0b\x32\x1e.opamp.proto.PackagesAvailableR\x11packagesAvailable\x12\x14\n\x05\x66lags\x18\x06 \x01(\x04R\x05\x66lags\x12\"\n\x0c\x63\x61pabilities\x18\x07 \x01(\x04R\x0c\x63\x61pabilities\x12S\n\x14\x61gent_identification\x18\x08 \x01(\x0b\x32 .opamp.proto.AgentIdentificationR\x13\x61gentIdentification\x12;\n\x07\x63ommand\x18\t \x01(\x0b\x32!.opamp.proto.ServerToAgentCommandR\x07\x63ommand\x12P\n\x13\x63ustom_capabilities\x18\n \x01(\x0b\x32\x1f.opamp.proto.CustomCapabilitiesR\x12\x63ustomCapabilities\x12\x41\n\x0e\x63ustom_message\x18\x0b \x01(\x0b\x32\x1a.opamp.proto.CustomMessageR\rcustomMessage\"\xbb\x01\n\x17OpAMPConnectionSettings\x12\x31\n\x14\x64\x65stination_endpoint\x18\x01 \x01(\tR\x13\x64\x65stinationEndpoint\x12.\n\x07headers\x18\x02 \x01(\x0b\x32\x14.opamp.proto.HeadersR\x07headers\x12=\n\x0b\x63\x65rtificate\x18\x03 \x01(\x0b\x32\x1b.opamp.proto.TLSCertificateR\x0b\x63\x65rtificate\"\xbf\x01\n\x1bTelemetryConnectionSettings\x12\x31\n\x14\x64\x65stination_endpoint\x18\x01 \x01(\tR\x13\x64\x65stinationEndpoint\x12.\n\x07headers\x18\x02 \x01(\x0b\x32\x14.opamp.proto.HeadersR\x07headers\x12=\n\x0b\x63\x65rtificate\x18\x03 \x01(\x0b\x32\x1b.opamp.proto.TLSCertificateR\x0b\x63\x65rtificate\"\xdd\x02\n\x17OtherConnectionSettings\x12\x31\n\x14\x64\x65stination_endpoint\x18\x01 \x01(\tR\x13\x64\x65stinationEndpoint\x12.\n\x07headers\x18\x02 \x01(\x0b\x32\x14.opamp.proto.HeadersR\x07headers\x12=\n\x0b\x63\x65rtificate\x18\x03 \x01(\x0b\x32\x1b.opamp.proto.TLSCertificateR\x0b\x63\x65rtificate\x12^\n\x0eother_settings\x18\x04 \x03(\x0b\x32\x37.opamp.proto.OtherConnectionSettings.OtherSettingsEntryR\rotherSettings\x1a@\n\x12OtherSettingsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value:\x02\x38\x01\"8\n\x07Headers\x12-\n\x07headers\x18\x01 \x03(\x0b\x32\x13.opamp.proto.HeaderR\x07headers\"0\n\x06Header\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n\x05value\x18\x02 \x01(\tR\x05value\"t\n\x0eTLSCertificate\x12\x1d\n\npublic_key\x18\x01 \x01(\x0cR\tpublicKey\x12\x1f\n\x0bprivate_key\x18\x02 \x01(\x0cR\nprivateKey\x12\"\n\rca_public_key\x18\x03 \x01(\x0cR\x0b\x63\x61PublicKey\"\x98\x04\n\x18\x43onnectionSettingsOffers\x12\x12\n\x04hash\x18\x01 \x01(\x0cR\x04hash\x12:\n\x05opamp\x18\x02 \x01(\x0b\x32$.opamp.proto.OpAMPConnectionSettingsR\x05opamp\x12I\n\x0bown_metrics\x18\x03 \x01(\x0b\x32(.opamp.proto.TelemetryConnectionSettingsR\nownMetrics\x12G\n\nown_traces\x18\x04 \x01(\x0b\x32(.opamp.proto.TelemetryConnectionSettingsR\townTraces\x12\x43\n\x08own_logs\x18\x05 \x01(\x0b\x32(.opamp.proto.TelemetryConnectionSettingsR\x07ownLogs\x12h\n\x11other_connections\x18\x06 \x03(\x0b\x32;.opamp.proto.ConnectionSettingsOffers.OtherConnectionsEntryR\x10otherConnections\x1ai\n\x15OtherConnectionsEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12:\n\x05value\x18\x02 \x01(\x0b\x32$.opamp.proto.OtherConnectionSettingsR\x05value:\x02\x38\x01\"\xe5\x01\n\x11PackagesAvailable\x12H\n\x08packages\x18\x01 \x03(\x0b\x32,.opamp.proto.PackagesAvailable.PackagesEntryR\x08packages\x12*\n\x11\x61ll_packages_hash\x18\x02 \x01(\x0cR\x0f\x61llPackagesHash\x1aZ\n\rPackagesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x33\n\x05value\x18\x02 \x01(\x0b\x32\x1d.opamp.proto.PackageAvailableR\x05value:\x02\x38\x01\"\xa1\x01\n\x10PackageAvailable\x12,\n\x04type\x18\x01 \x01(\x0e\x32\x18.opamp.proto.PackageTypeR\x04type\x12\x18\n\x07version\x18\x02 \x01(\tR\x07version\x12\x31\n\x04\x66ile\x18\x03 \x01(\x0b\x32\x1d.opamp.proto.DownloadableFileR\x04\x66ile\x12\x12\n\x04hash\x18\x04 \x01(\x0cR\x04hash\"v\n\x10\x44ownloadableFile\x12!\n\x0c\x64ownload_url\x18\x01 \x01(\tR\x0b\x64ownloadUrl\x12!\n\x0c\x63ontent_hash\x18\x02 \x01(\x0cR\x0b\x63ontentHash\x12\x1c\n\tsignature\x18\x03 \x01(\x0cR\tsignature\"\xb8\x01\n\x13ServerErrorResponse\x12\x38\n\x04type\x18\x01 \x01(\x0e\x32$.opamp.proto.ServerErrorResponseTypeR\x04type\x12#\n\rerror_message\x18\x02 \x01(\tR\x0c\x65rrorMessage\x12\x37\n\nretry_info\x18\x03 \x01(\x0b\x32\x16.opamp.proto.RetryInfoH\x00R\tretryInfoB\t\n\x07\x44\x65tails\"C\n\tRetryInfo\x12\x36\n\x17retry_after_nanoseconds\x18\x01 \x01(\x04R\x15retryAfterNanoseconds\"D\n\x14ServerToAgentCommand\x12,\n\x04type\x18\x01 \x01(\x0e\x32\x18.opamp.proto.CommandTypeR\x04type\"\xb5\x01\n\x10\x41gentDescription\x12L\n\x16identifying_attributes\x18\x01 \x03(\x0b\x32\x15.opamp.proto.KeyValueR\x15identifyingAttributes\x12S\n\x1anon_identifying_attributes\x18\x02 \x03(\x0b\x32\x15.opamp.proto.KeyValueR\x18nonIdentifyingAttributes\"\x93\x03\n\x0f\x43omponentHealth\x12\x18\n\x07healthy\x18\x01 \x01(\x08R\x07healthy\x12/\n\x14start_time_unix_nano\x18\x02 \x01(\x06R\x11startTimeUnixNano\x12\x1d\n\nlast_error\x18\x03 \x01(\tR\tlastError\x12\x16\n\x06status\x18\x04 \x01(\tR\x06status\x12\x31\n\x15status_time_unix_nano\x18\x05 \x01(\x06R\x12statusTimeUnixNano\x12\x66\n\x14\x63omponent_health_map\x18\x06 \x03(\x0b\x32\x34.opamp.proto.ComponentHealth.ComponentHealthMapEntryR\x12\x63omponentHealthMap\x1a\x63\n\x17\x43omponentHealthMapEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x32\n\x05value\x18\x02 \x01(\x0b\x32\x1c.opamp.proto.ComponentHealthR\x05value:\x02\x38\x01\"M\n\x0f\x45\x66\x66\x65\x63tiveConfig\x12:\n\nconfig_map\x18\x01 \x01(\x0b\x32\x1b.opamp.proto.AgentConfigMapR\tconfigMap\"\xab\x01\n\x12RemoteConfigStatus\x12\x35\n\x17last_remote_config_hash\x18\x01 \x01(\x0cR\x14lastRemoteConfigHash\x12\x39\n\x06status\x18\x02 \x01(\x0e\x32!.opamp.proto.RemoteConfigStatusesR\x06status\x12#\n\rerror_message\x18\x03 \x01(\tR\x0c\x65rrorMessage\"\xa1\x02\n\x0fPackageStatuses\x12\x46\n\x08packages\x18\x01 \x03(\x0b\x32*.opamp.proto.PackageStatuses.PackagesEntryR\x08packages\x12H\n!server_provided_all_packages_hash\x18\x02 \x01(\x0cR\x1dserverProvidedAllPackagesHash\x12#\n\rerror_message\x18\x03 \x01(\tR\x0c\x65rrorMessage\x1aW\n\rPackagesEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x30\n\x05value\x18\x02 \x01(\x0b\x32\x1a.opamp.proto.PackageStatusR\x05value:\x02\x38\x01\"\xb8\x02\n\rPackageStatus\x12\x12\n\x04name\x18\x01 \x01(\tR\x04name\x12*\n\x11\x61gent_has_version\x18\x02 \x01(\tR\x0f\x61gentHasVersion\x12$\n\x0e\x61gent_has_hash\x18\x03 \x01(\x0cR\x0c\x61gentHasHash\x12\x34\n\x16server_offered_version\x18\x04 \x01(\tR\x14serverOfferedVersion\x12.\n\x13server_offered_hash\x18\x05 \x01(\x0cR\x11serverOfferedHash\x12\x36\n\x06status\x18\x06 \x01(\x0e\x32\x1e.opamp.proto.PackageStatusEnumR\x06status\x12#\n\rerror_message\x18\x07 \x01(\tR\x0c\x65rrorMessage\"?\n\x13\x41gentIdentification\x12(\n\x10new_instance_uid\x18\x01 \x01(\x0cR\x0enewInstanceUid\"i\n\x11\x41gentRemoteConfig\x12\x33\n\x06\x63onfig\x18\x01 \x01(\x0b\x32\x1b.opamp.proto.AgentConfigMapR\x06\x63onfig\x12\x1f\n\x0b\x63onfig_hash\x18\x02 \x01(\x0cR\nconfigHash\"\xb7\x01\n\x0e\x41gentConfigMap\x12I\n\nconfig_map\x18\x01 \x03(\x0b\x32*.opamp.proto.AgentConfigMap.ConfigMapEntryR\tconfigMap\x1aZ\n\x0e\x43onfigMapEntry\x12\x10\n\x03key\x18\x01 \x01(\tR\x03key\x12\x32\n\x05value\x18\x02 \x01(\x0b\x32\x1c.opamp.proto.AgentConfigFileR\x05value:\x02\x38\x01\"H\n\x0f\x41gentConfigFile\x12\x12\n\x04\x62ody\x18\x01 \x01(\x0cR\x04\x62ody\x12!\n\x0c\x63ontent_type\x18\x02 \x01(\tR\x0b\x63ontentType\"8\n\x12\x43ustomCapabilities\x12\"\n\x0c\x63\x61pabilities\x18\x01 \x03(\tR\x0c\x63\x61pabilities\"W\n\rCustomMessage\x12\x1e\n\ncapability\x18\x01 \x01(\tR\ncapability\x12\x12\n\x04type\x18\x02 \x01(\tR\x04type\x12\x12\n\x04\x64\x61ta\x18\x03 \x01(\x0cR\x04\x64\x61ta*c\n\x12\x41gentToServerFlags\x12\"\n\x1e\x41gentToServerFlags_Unspecified\x10\x00\x12)\n%AgentToServerFlags_RequestInstanceUid\x10\x01*`\n\x12ServerToAgentFlags\x12\"\n\x1eServerToAgentFlags_Unspecified\x10\x00\x12&\n\"ServerToAgentFlags_ReportFullState\x10\x01*\xf7\x02\n\x12ServerCapabilities\x12\"\n\x1eServerCapabilities_Unspecified\x10\x00\x12$\n ServerCapabilities_AcceptsStatus\x10\x01\x12)\n%ServerCapabilities_OffersRemoteConfig\x10\x02\x12-\n)ServerCapabilities_AcceptsEffectiveConfig\x10\x04\x12%\n!ServerCapabilities_OffersPackages\x10\x08\x12,\n(ServerCapabilities_AcceptsPackagesStatus\x10\x10\x12/\n+ServerCapabilities_OffersConnectionSettings\x10 \x12\x37\n3ServerCapabilities_AcceptsConnectionSettingsRequest\x10@*>\n\x0bPackageType\x12\x18\n\x14PackageType_TopLevel\x10\x00\x12\x15\n\x11PackageType_Addon\x10\x01*\x8f\x01\n\x17ServerErrorResponseType\x12#\n\x1fServerErrorResponseType_Unknown\x10\x00\x12&\n\"ServerErrorResponseType_BadRequest\x10\x01\x12\'\n#ServerErrorResponseType_Unavailable\x10\x02*&\n\x0b\x43ommandType\x12\x17\n\x13\x43ommandType_Restart\x10\x00*\xef\x04\n\x11\x41gentCapabilities\x12!\n\x1d\x41gentCapabilities_Unspecified\x10\x00\x12#\n\x1f\x41gentCapabilities_ReportsStatus\x10\x01\x12)\n%AgentCapabilities_AcceptsRemoteConfig\x10\x02\x12,\n(AgentCapabilities_ReportsEffectiveConfig\x10\x04\x12%\n!AgentCapabilities_AcceptsPackages\x10\x08\x12,\n(AgentCapabilities_ReportsPackageStatuses\x10\x10\x12&\n\"AgentCapabilities_ReportsOwnTraces\x10 \x12\'\n#AgentCapabilities_ReportsOwnMetrics\x10@\x12%\n AgentCapabilities_ReportsOwnLogs\x10\x80\x01\x12\x35\n0AgentCapabilities_AcceptsOpAMPConnectionSettings\x10\x80\x02\x12\x35\n0AgentCapabilities_AcceptsOtherConnectionSettings\x10\x80\x04\x12,\n\'AgentCapabilities_AcceptsRestartCommand\x10\x80\x08\x12$\n\x1f\x41gentCapabilities_ReportsHealth\x10\x80\x10\x12*\n%AgentCapabilities_ReportsRemoteConfig\x10\x80 *\x9c\x01\n\x14RemoteConfigStatuses\x12\x1e\n\x1aRemoteConfigStatuses_UNSET\x10\x00\x12 \n\x1cRemoteConfigStatuses_APPLIED\x10\x01\x12!\n\x1dRemoteConfigStatuses_APPLYING\x10\x02\x12\x1f\n\x1bRemoteConfigStatuses_FAILED\x10\x03*\xa1\x01\n\x11PackageStatusEnum\x12\x1f\n\x1bPackageStatusEnum_Installed\x10\x00\x12$\n PackageStatusEnum_InstallPending\x10\x01\x12 \n\x1cPackageStatusEnum_Installing\x10\x02\x12#\n\x1fPackageStatusEnum_InstallFailed\x10\x03\x42\x9d\x01\n\x0f\x63om.opamp.protoB\nOpampProtoP\x01Z1github.com/odigos-io/odigos/opampserver/protobufs\xa2\x02\x03OPX\xaa\x02\x0bOpamp.Proto\xca\x02\x0bOpamp\\Proto\xe2\x02\x17Opamp\\Proto\\GPBMetadata\xea\x02\x0cOpamp::Protob\x06proto3')
18
+
19
+ _globals = globals()
20
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
21
+ _builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'opamp_pb2', _globals)
22
+ if _descriptor._USE_C_DESCRIPTORS == False:
23
+ _globals['DESCRIPTOR']._options = None
24
+ _globals['DESCRIPTOR']._serialized_options = b'\n\017com.opamp.protoB\nOpampProtoP\001Z1github.com/odigos-io/odigos/opampserver/protobufs\242\002\003OPX\252\002\013Opamp.Proto\312\002\013Opamp\\Proto\342\002\027Opamp\\Proto\\GPBMetadata\352\002\014Opamp::Proto'
25
+ _globals['_OTHERCONNECTIONSETTINGS_OTHERSETTINGSENTRY']._options = None
26
+ _globals['_OTHERCONNECTIONSETTINGS_OTHERSETTINGSENTRY']._serialized_options = b'8\001'
27
+ _globals['_CONNECTIONSETTINGSOFFERS_OTHERCONNECTIONSENTRY']._options = None
28
+ _globals['_CONNECTIONSETTINGSOFFERS_OTHERCONNECTIONSENTRY']._serialized_options = b'8\001'
29
+ _globals['_PACKAGESAVAILABLE_PACKAGESENTRY']._options = None
30
+ _globals['_PACKAGESAVAILABLE_PACKAGESENTRY']._serialized_options = b'8\001'
31
+ _globals['_COMPONENTHEALTH_COMPONENTHEALTHMAPENTRY']._options = None
32
+ _globals['_COMPONENTHEALTH_COMPONENTHEALTHMAPENTRY']._serialized_options = b'8\001'
33
+ _globals['_PACKAGESTATUSES_PACKAGESENTRY']._options = None
34
+ _globals['_PACKAGESTATUSES_PACKAGESENTRY']._serialized_options = b'8\001'
35
+ _globals['_AGENTCONFIGMAP_CONFIGMAPENTRY']._options = None
36
+ _globals['_AGENTCONFIGMAP_CONFIGMAPENTRY']._serialized_options = b'8\001'
37
+ _globals['_AGENTTOSERVERFLAGS']._serialized_start=6233
38
+ _globals['_AGENTTOSERVERFLAGS']._serialized_end=6332
39
+ _globals['_SERVERTOAGENTFLAGS']._serialized_start=6334
40
+ _globals['_SERVERTOAGENTFLAGS']._serialized_end=6430
41
+ _globals['_SERVERCAPABILITIES']._serialized_start=6433
42
+ _globals['_SERVERCAPABILITIES']._serialized_end=6808
43
+ _globals['_PACKAGETYPE']._serialized_start=6810
44
+ _globals['_PACKAGETYPE']._serialized_end=6872
45
+ _globals['_SERVERERRORRESPONSETYPE']._serialized_start=6875
46
+ _globals['_SERVERERRORRESPONSETYPE']._serialized_end=7018
47
+ _globals['_COMMANDTYPE']._serialized_start=7020
48
+ _globals['_COMMANDTYPE']._serialized_end=7058
49
+ _globals['_AGENTCAPABILITIES']._serialized_start=7061
50
+ _globals['_AGENTCAPABILITIES']._serialized_end=7684
51
+ _globals['_REMOTECONFIGSTATUSES']._serialized_start=7687
52
+ _globals['_REMOTECONFIGSTATUSES']._serialized_end=7843
53
+ _globals['_PACKAGESTATUSENUM']._serialized_start=7846
54
+ _globals['_PACKAGESTATUSENUM']._serialized_end=8007
55
+ _globals['_AGENTTOSERVER']._serialized_start=45
56
+ _globals['_AGENTTOSERVER']._serialized_end=873
57
+ _globals['_AGENTDISCONNECT']._serialized_start=875
58
+ _globals['_AGENTDISCONNECT']._serialized_end=892
59
+ _globals['_CONNECTIONSETTINGSREQUEST']._serialized_start=894
60
+ _globals['_CONNECTIONSETTINGSREQUEST']._serialized_end=988
61
+ _globals['_OPAMPCONNECTIONSETTINGSREQUEST']._serialized_start=990
62
+ _globals['_OPAMPCONNECTIONSETTINGSREQUEST']._serialized_end=1104
63
+ _globals['_CERTIFICATEREQUEST']._serialized_start=1106
64
+ _globals['_CERTIFICATEREQUEST']._serialized_end=1144
65
+ _globals['_SERVERTOAGENT']._serialized_start=1147
66
+ _globals['_SERVERTOAGENT']._serialized_end=1859
67
+ _globals['_OPAMPCONNECTIONSETTINGS']._serialized_start=1862
68
+ _globals['_OPAMPCONNECTIONSETTINGS']._serialized_end=2049
69
+ _globals['_TELEMETRYCONNECTIONSETTINGS']._serialized_start=2052
70
+ _globals['_TELEMETRYCONNECTIONSETTINGS']._serialized_end=2243
71
+ _globals['_OTHERCONNECTIONSETTINGS']._serialized_start=2246
72
+ _globals['_OTHERCONNECTIONSETTINGS']._serialized_end=2595
73
+ _globals['_OTHERCONNECTIONSETTINGS_OTHERSETTINGSENTRY']._serialized_start=2531
74
+ _globals['_OTHERCONNECTIONSETTINGS_OTHERSETTINGSENTRY']._serialized_end=2595
75
+ _globals['_HEADERS']._serialized_start=2597
76
+ _globals['_HEADERS']._serialized_end=2653
77
+ _globals['_HEADER']._serialized_start=2655
78
+ _globals['_HEADER']._serialized_end=2703
79
+ _globals['_TLSCERTIFICATE']._serialized_start=2705
80
+ _globals['_TLSCERTIFICATE']._serialized_end=2821
81
+ _globals['_CONNECTIONSETTINGSOFFERS']._serialized_start=2824
82
+ _globals['_CONNECTIONSETTINGSOFFERS']._serialized_end=3360
83
+ _globals['_CONNECTIONSETTINGSOFFERS_OTHERCONNECTIONSENTRY']._serialized_start=3255
84
+ _globals['_CONNECTIONSETTINGSOFFERS_OTHERCONNECTIONSENTRY']._serialized_end=3360
85
+ _globals['_PACKAGESAVAILABLE']._serialized_start=3363
86
+ _globals['_PACKAGESAVAILABLE']._serialized_end=3592
87
+ _globals['_PACKAGESAVAILABLE_PACKAGESENTRY']._serialized_start=3502
88
+ _globals['_PACKAGESAVAILABLE_PACKAGESENTRY']._serialized_end=3592
89
+ _globals['_PACKAGEAVAILABLE']._serialized_start=3595
90
+ _globals['_PACKAGEAVAILABLE']._serialized_end=3756
91
+ _globals['_DOWNLOADABLEFILE']._serialized_start=3758
92
+ _globals['_DOWNLOADABLEFILE']._serialized_end=3876
93
+ _globals['_SERVERERRORRESPONSE']._serialized_start=3879
94
+ _globals['_SERVERERRORRESPONSE']._serialized_end=4063
95
+ _globals['_RETRYINFO']._serialized_start=4065
96
+ _globals['_RETRYINFO']._serialized_end=4132
97
+ _globals['_SERVERTOAGENTCOMMAND']._serialized_start=4134
98
+ _globals['_SERVERTOAGENTCOMMAND']._serialized_end=4202
99
+ _globals['_AGENTDESCRIPTION']._serialized_start=4205
100
+ _globals['_AGENTDESCRIPTION']._serialized_end=4386
101
+ _globals['_COMPONENTHEALTH']._serialized_start=4389
102
+ _globals['_COMPONENTHEALTH']._serialized_end=4792
103
+ _globals['_COMPONENTHEALTH_COMPONENTHEALTHMAPENTRY']._serialized_start=4693
104
+ _globals['_COMPONENTHEALTH_COMPONENTHEALTHMAPENTRY']._serialized_end=4792
105
+ _globals['_EFFECTIVECONFIG']._serialized_start=4794
106
+ _globals['_EFFECTIVECONFIG']._serialized_end=4871
107
+ _globals['_REMOTECONFIGSTATUS']._serialized_start=4874
108
+ _globals['_REMOTECONFIGSTATUS']._serialized_end=5045
109
+ _globals['_PACKAGESTATUSES']._serialized_start=5048
110
+ _globals['_PACKAGESTATUSES']._serialized_end=5337
111
+ _globals['_PACKAGESTATUSES_PACKAGESENTRY']._serialized_start=5250
112
+ _globals['_PACKAGESTATUSES_PACKAGESENTRY']._serialized_end=5337
113
+ _globals['_PACKAGESTATUS']._serialized_start=5340
114
+ _globals['_PACKAGESTATUS']._serialized_end=5652
115
+ _globals['_AGENTIDENTIFICATION']._serialized_start=5654
116
+ _globals['_AGENTIDENTIFICATION']._serialized_end=5717
117
+ _globals['_AGENTREMOTECONFIG']._serialized_start=5719
118
+ _globals['_AGENTREMOTECONFIG']._serialized_end=5824
119
+ _globals['_AGENTCONFIGMAP']._serialized_start=5827
120
+ _globals['_AGENTCONFIGMAP']._serialized_end=6010
121
+ _globals['_AGENTCONFIGMAP_CONFIGMAPENTRY']._serialized_start=5920
122
+ _globals['_AGENTCONFIGMAP_CONFIGMAPENTRY']._serialized_end=6010
123
+ _globals['_AGENTCONFIGFILE']._serialized_start=6012
124
+ _globals['_AGENTCONFIGFILE']._serialized_end=6084
125
+ _globals['_CUSTOMCAPABILITIES']._serialized_start=6086
126
+ _globals['_CUSTOMCAPABILITIES']._serialized_end=6142
127
+ _globals['_CUSTOMMESSAGE']._serialized_start=6144
128
+ _globals['_CUSTOMMESSAGE']._serialized_end=6231
129
+ # @@protoc_insertion_point(module_scope)
opamp/utils.py ADDED
@@ -0,0 +1,24 @@
1
+ import json
2
+ import logging
3
+ from opamp import opamp_pb2
4
+
5
+ def parse_first_message_to_resource_attributes(first_message_server_to_agent: opamp_pb2.ServerToAgent, logger: logging.Logger) -> dict:
6
+ config_map = first_message_server_to_agent.remote_config.config.config_map
7
+
8
+ if "SDK" not in config_map:
9
+ logger.error("SDK not found in config map, returning empty resource attributes")
10
+ return {}
11
+
12
+ try:
13
+ sdk_config = json.loads(config_map["SDK"].body)
14
+ except json.JSONDecodeError as e:
15
+ logger.error(f"Error decoding SDK config: {e}")
16
+ return {}
17
+
18
+ remote_resource_attributes = sdk_config.get('remoteResourceAttributes', [])
19
+
20
+ if not remote_resource_attributes:
21
+ logger.error('missing "remoteResourceAttributes" section in OpAMP server remote config on first server to agent message')
22
+ return {}
23
+
24
+ return {item['key']: item['value'] for item in remote_resource_attributes}