Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,33 @@ def _import_id_generator(id_generator_name: str) -> IdGenerator:
raise RuntimeError(f"{id_generator_name} is not an IdGenerator")


def _import_opamp() -> Callable[[Resource], None] | None:
# this in development, at the moment we are looking for a callable that takes
# the resource and instantiate an OpAMP agent.
# Since configuration is not specified every implementers may have its own.
# Refer to opentelemetry-opamp-client package on how to setup the OpAMP agent.
entry_point = None
try:
entry_point = next(
iter(
entry_points(
group="_opentelemetry_opamp", name="pre_sdk_init_function"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like in its current form, this is not OpAMP specific but rather a general, SDK pre-initialization Callable that accepts a Resource. Do you envision this evolving into something OpAMP specific as this matures from a sketch to a production implementation? Otherwise the entrypoints could have a general names like group="opentelemetry_sdk", name="pre_init" which would leave them open for other use cases (and for a post_init later).

)
)
)
return entry_point.load()
except StopIteration:
_logger.debug("No OpAMP init function found")
except AttributeError as exc:
_logger.warning(
"Failed to load OpAMP init function from entry point, %s: %s",
entry_point,
exc,
)

return None


def _initialize_components(
auto_instrumentation_version: str | None = None,
trace_exporter_names: list[str] | None = None,
Expand Down Expand Up @@ -618,6 +645,15 @@ def _initialize_components(
# from the env variable else defaults to "unknown_service"
resource = Resource.create(resource_attributes)

# OpAMP is a system created to configure OpenTelemetry SDKs with a remote config.
# This is different than other init helpers because setting up OpAMP requires distro
# provided code as it's not strictly specified. We call OpAMP init before other code
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my distro I initialize the OpAMP client after the sdk has been setup but can't exclude other scenarios

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add this a SIG topic to get the execution order?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've renamed the entry point to pre_sdk_init_function so that we can add a post_sdk_init_function if required

# because people may want to have it blocking to get an updated config before setting
# up the rest of the SDK.
_init_opamp = _import_opamp()
if _init_opamp is not None:
_init_opamp(resource)

_init_tracing(
exporters=span_exporters,
id_generator=id_generator,
Expand Down
64 changes: 64 additions & 0 deletions opentelemetry-sdk/tests/test_configurator.py
Original file line number Diff line number Diff line change
Expand Up @@ -1573,3 +1573,67 @@ def f(x):
self.assertEqual(logging.config.dictConfig.__name__, "dictConfig")
self.assertEqual(logging.basicConfig.__name__, "basicConfig")
self.assertEqual(logging.config.fileConfig.__name__, "fileConfig")


class TestOpAMPInit(TestCase):
@patch("opentelemetry.sdk._configuration.entry_points")
@patch("opentelemetry.sdk._configuration.Resource")
def test_init_function_found(self, mock_resource, mock_entry_points):
init_function = mock.Mock()
mock_entry_points.configure_mock(
return_value=[
IterEntryPoint("pre_sdk_init_function", init_function)
]
)

_initialize_components(id_generator=1)

mock_entry_points.assert_has_calls(
[
mock.call(
group="_opentelemetry_opamp", name="pre_sdk_init_function"
)
]
)
init_function.assert_called_once_with(
mock_resource.create.return_value
)

@patch("opentelemetry.sdk._configuration.entry_points")
def test_init_function_load_failure(self, mock_entry_points):
entry_point_mock = mock.Mock()
entry_point_mock.load.side_effect = AttributeError(
"module 'foo' has no attribute 'OpampInit'"
)
mock_entry_points.configure_mock(
return_value=[entry_point_mock],
)
entry_point_mock.__str__ = lambda x: "<EntryPoint>"

with self.assertLogs(level="WARNING") as cm:
_initialize_components(id_generator=1)

mock_entry_points.assert_has_calls(
[
mock.call(
group="_opentelemetry_opamp", name="pre_sdk_init_function"
)
]
)

self.assertIn(
"WARNING:opentelemetry.sdk._configuration:Failed to load OpAMP init function from entry point,"
" <EntryPoint>: module 'foo' has no attribute 'OpampInit'",
cm.output,
)

@patch("opentelemetry.sdk._configuration.entry_points")
def test_init_function_not_found(self, mock_entry_points):
mock_entry_points.configure_mock(return_value=[])

with self.assertLogs(level="DEBUG") as cm:
_initialize_components(id_generator=1)
self.assertIn(
"DEBUG:opentelemetry.sdk._configuration:No OpAMP init function found",
cm.output,
)
Loading