Skip to content
Draft
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
6 changes: 6 additions & 0 deletions interfaces/otlp/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Replace the requirer's rule path interface with an interface accepting an object containing rules
- Generic PromQL alert rules for requirer charms that are of the aggregator or application type

## [0.3.0]

### Added

- Added an `insecure` field to the OtlpEndpoint(s) in the databag to communicate TLS, required for some gRPC applications
17 changes: 12 additions & 5 deletions interfaces/otlp/src/charmlibs/interfaces/otlp/_otlp.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ class OtlpEndpoint(BaseModel):
telemetries: Sequence[str] = Field(
description='Telemetry signal types accepted by this endpoint.'
)
insecure: bool = Field(
description='Whether this endpoint requires an insecure connection (e.g. no TLS).',
default=False,
)


class _OtlpEndpoint(OtlpEndpoint):
Expand Down Expand Up @@ -431,13 +435,16 @@ def add_endpoint(
protocol: Literal['http', 'grpc'],
endpoint: str,
telemetries: Sequence[Literal['logs', 'metrics', 'traces']],
insecure: bool = False,
) -> 'OtlpProvider':
"""Add an OtlpEndpoint to the list of endpoints to publish.

Call this method after endpoint-changing events e.g. TLS and ingress.
"""
"""Add an OtlpEndpoint to the list of endpoints to publish."""
self._endpoints.append(
_OtlpEndpoint(protocol=protocol, endpoint=endpoint, telemetries=telemetries)
_OtlpEndpoint(
protocol=protocol,
endpoint=endpoint,
telemetries=telemetries,
insecure=insecure,
)
)
return self

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@
# See the License for the specific language governing permissions and
# limitations under the License.

__version__ = '0.2.0'
__version__ = '0.3.0'
109 changes: 68 additions & 41 deletions interfaces/otlp/tests/unit/test_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import json
from collections.abc import Sequence
from typing import Any, Final, Literal, cast
from typing import Any, Final, Literal

import ops
import pytest
Expand All @@ -14,7 +14,12 @@

from charmlibs.interfaces.otlp._otlp import DEFAULT_PROVIDER_RELATION_NAME as RECEIVE
from charmlibs.interfaces.otlp._otlp import DEFAULT_REQUIRER_RELATION_NAME as SEND
from charmlibs.interfaces.otlp._otlp import OtlpRequirer, _OtlpEndpoint, _OtlpProviderAppData
from charmlibs.interfaces.otlp._otlp import (
OtlpProvider,
OtlpRequirer,
_OtlpEndpoint,
_OtlpProviderAppData,
)
from conftest import ALL_PROTOCOLS, ALL_TELEMETRIES

PROTOCOLS: Final[list[Literal['http', 'grpc']]] = ['http', 'grpc']
Expand All @@ -28,6 +33,7 @@ def test_new_endpoint_key_is_ignored_by_databag_model():
'protocol': 'new_protocol',
'endpoint': 'http://host:4317',
'telemetries': ['logs'],
'insecure': False,
'new_key': 'value',
}

Expand All @@ -53,11 +59,13 @@ def test_new_endpoint_key_is_ignored_by_databag_model():
'protocol': 'new_protocol',
'endpoint': 'http://host:0000',
'telemetries': ['metrics'],
'insecure': False,
},
{
'protocol': 'http',
'endpoint': 'http://host:4317',
'telemetries': ['metrics'],
'insecure': False,
},
]),
},
Expand All @@ -75,13 +83,12 @@ def test_new_endpoint_key_is_ignored_by_databag_model():
'protocol': 'http',
'endpoint': 'http://host:4317',
'telemetries': ['logs', 'new_telemetry', 'traces'],
'insecure': False,
},
]),
},
_OtlpEndpoint(
protocol='http',
endpoint='http://host:4317',
telemetries=['logs', 'traces'],
protocol='http', endpoint='http://host:4317', telemetries=['logs', 'traces']
),
),
(
Expand All @@ -93,6 +100,7 @@ def test_new_endpoint_key_is_ignored_by_databag_model():
'protocol': 'http',
'endpoint': 'http://host:4317',
'telemetries': ['metrics'],
'insecure': False,
}
]),
'does_not': '"exist"',
Expand All @@ -118,11 +126,10 @@ def test_send_otlp_invalid_databag(
with otlp_requirer_ctx(otlp_requirer_ctx.on.update_status(), state=state) as mgr:
# WHEN the requirer processes the relation data
# * the requirer supports all protocols and telemetries
charm_any = cast('Any', mgr.charm)
# THEN the requirer does not raise an error
# * the returned endpoint does not include new protocols or telemetries
assert mgr.run()
endpoints = OtlpRequirer(charm_any, SEND, ALL_PROTOCOLS, ALL_TELEMETRIES).endpoints
endpoints = OtlpRequirer(mgr.charm, SEND, ALL_PROTOCOLS, ALL_TELEMETRIES).endpoints
assert endpoints[123].model_dump() == otlp_endpoint.model_dump()


Expand All @@ -135,13 +142,11 @@ def test_send_otlp_invalid_databag(
{
123: _OtlpEndpoint(
protocol='http',
endpoint='http://provider-123.endpoint:4318',
endpoint='http://provider-123:4318',
telemetries=['logs', 'metrics'],
),
456: _OtlpEndpoint(
protocol='grpc',
endpoint='http://provider-456.endpoint:4317',
telemetries=['traces'],
protocol='grpc', endpoint='http://provider-456:4317', telemetries=['traces']
),
},
),
Expand All @@ -150,9 +155,7 @@ def test_send_otlp_invalid_databag(
ALL_TELEMETRIES,
{
456: _OtlpEndpoint(
protocol='grpc',
endpoint='http://provider-456.endpoint:4317',
telemetries=['traces'],
protocol='grpc', endpoint='http://provider-456:4317', telemetries=['traces']
)
},
),
Expand All @@ -161,14 +164,10 @@ def test_send_otlp_invalid_databag(
['metrics'],
{
123: _OtlpEndpoint(
protocol='http',
endpoint='http://provider-123.endpoint:4318',
telemetries=['metrics'],
protocol='http', endpoint='http://provider-123:4318', telemetries=['metrics']
),
456: _OtlpEndpoint(
protocol='http',
endpoint='http://provider-456.endpoint:4318',
telemetries=['metrics'],
protocol='http', endpoint='http://provider-456:4318', telemetries=['metrics']
),
},
),
Expand All @@ -186,22 +185,25 @@ def test_send_otlp_with_varying_requirer_support(
'endpoints': json.dumps([
{
'protocol': 'http',
'endpoint': 'http://provider-123.endpoint:4318',
'endpoint': 'http://provider-123:4318',
'telemetries': ['logs', 'metrics'],
'insecure': False,
}
])
}
remote_app_data_2 = {
'endpoints': json.dumps([
{
'protocol': 'grpc',
'endpoint': 'http://provider-456.endpoint:4317',
'endpoint': 'http://provider-456:4317',
'telemetries': ['traces'],
'insecure': False,
},
{
'protocol': 'http',
'endpoint': 'http://provider-456.endpoint:4318',
'endpoint': 'http://provider-456:4318',
'telemetries': ['metrics'],
'insecure': False,
},
])
}
Expand All @@ -213,8 +215,7 @@ def test_send_otlp_with_varying_requirer_support(

# AND WHEN the requirer has varying support for OTLP protocols and telemetries
with otlp_requirer_ctx(otlp_requirer_ctx.on.update_status(), state=state) as mgr:
charm_any = cast('Any', mgr.charm)
remote_endpoints = OtlpRequirer(charm_any, SEND, protocols, telemetries).endpoints
remote_endpoints = OtlpRequirer(mgr.charm, SEND, protocols, telemetries).endpoints

# THEN the returned endpoints are filtered accordingly
assert {k: v.model_dump() for k, v in remote_endpoints.items()} == {
Expand All @@ -228,36 +229,35 @@ def test_send_otlp(otlp_requirer_ctx: testing.Context[ops.CharmBase]):
'endpoints': json.dumps([
{
'protocol': 'http',
'endpoint': 'http://provider-123.endpoint:4318',
'endpoint': 'http://provider-123:4318',
'telemetries': ['logs', 'metrics'],
'insecure': False,
}
])
}
remote_app_data_2 = {
'endpoints': json.dumps([
{
'protocol': 'grpc',
'endpoint': 'http://provider-456.endpoint:4317',
'endpoint': 'http://provider-456:4317',
'telemetries': ['traces'],
'insecure': False,
},
{
'protocol': 'http',
'endpoint': 'http://provider-456.endpoint:4318',
'endpoint': 'http://provider-456:4318',
'telemetries': ['metrics'],
'insecure': False,
},
])
}

expected_endpoints = {
456: _OtlpEndpoint(
protocol='http',
endpoint='http://provider-456.endpoint:4318',
telemetries=['metrics'],
protocol='http', endpoint='http://provider-456:4318', telemetries=['metrics']
),
123: _OtlpEndpoint(
protocol='http',
endpoint='http://provider-123.endpoint:4318',
telemetries=['logs', 'metrics'],
protocol='http', endpoint='http://provider-123:4318', telemetries=['logs', 'metrics']
),
}

Expand All @@ -268,8 +268,7 @@ def test_send_otlp(otlp_requirer_ctx: testing.Context[ops.CharmBase]):

# AND WHEN otelcol supports a subset of OTLP protocols and telemetries
with otlp_requirer_ctx(otlp_requirer_ctx.on.update_status(), state=state) as mgr:
charm_any = cast('Any', mgr.charm)
remote_endpoints = OtlpRequirer(charm_any, SEND, PROTOCOLS, TELEMETRIES).endpoints
remote_endpoints = OtlpRequirer(mgr.charm, SEND, PROTOCOLS, TELEMETRIES).endpoints

# THEN the returned endpoints are filtered accordingly
assert {k: v.model_dump() for k, v in remote_endpoints.items()} == {
Expand Down Expand Up @@ -299,6 +298,7 @@ def test_receive_otlp(otlp_provider_ctx: testing.Context[ops.CharmBase]):
'protocol': 'http',
'endpoint': 'http://fqdn:4318',
'telemetries': ['metrics'],
'insecure': False,
}
],
}
Expand Down Expand Up @@ -365,10 +365,37 @@ def test_favor_modern_endpoints(
state = State(leader=True)
with otlp_requirer_ctx(otlp_requirer_ctx.on.update_status(), state=state) as mgr:
# WHEN the requirer selects an endpoint
charm_any = cast('Any', mgr.charm)
result = OtlpRequirer(charm_any, SEND, PROTOCOLS, TELEMETRIES)._favor_modern_endpoints(
endpoints
)
requirer = OtlpRequirer(mgr.charm, SEND, PROTOCOLS, TELEMETRIES)

# THEN the most modern one is chosen
assert result.protocol == expected_protocol
assert requirer._favor_modern_endpoints(endpoints).protocol == expected_protocol


def test_add_endpoint_insecure(otlp_requirer_ctx: testing.Context[ops.CharmBase]):
state = State(leader=True)
with otlp_requirer_ctx(otlp_requirer_ctx.on.update_status(), state=state) as mgr:
# GIVEN a provider
provider = OtlpProvider(mgr.charm)

# WHEN insecure and secure endpoints are added
provider.add_endpoint(
protocol='http', endpoint='http://host:4318', telemetries=['metrics'], insecure=True
).add_endpoint(
protocol='grpc', endpoint='http://host:4317', telemetries=['traces'], insecure=False
)

# THEN their security settings are preserved
assert provider._endpoints == [
_OtlpEndpoint(
protocol='http',
endpoint='http://host:4318',
telemetries=['metrics'],
insecure=True,
),
_OtlpEndpoint(
protocol='grpc',
endpoint='http://host:4317',
telemetries=['traces'],
insecure=False,
),
]
10 changes: 4 additions & 6 deletions interfaces/otlp/tests/unit/test_rules.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""Feature: Rules aggregation and labeling."""

import json
from typing import Any, cast
from typing import Any

import ops
import pytest
Expand Down Expand Up @@ -92,11 +92,10 @@ def test_duplicate_rules_per_unit(
):
with otlp_requirer_ctx(otlp_requirer_ctx.on.update_status(), state=State(leader=True)) as mgr:
# GIVEN any charm
charm_any = cast('Any', mgr.charm)
# WHEN the OtlpRequirer is initialized
# * generic aggregator rules are desired
# * no peer relation name is provided
result = OtlpRequirer(charm_any)._duplicate_rules_per_unit(
result = OtlpRequirer(mgr.charm)._duplicate_rules_per_unit(
alert_rules={
'groups': [
{
Expand Down Expand Up @@ -240,9 +239,8 @@ def test_provider_rules(
state = State(leader=True, relations=[receiver], model=MODEL)
with otlp_provider_ctx(otlp_provider_ctx.on.update_status(), state=state) as mgr:
# WHEN the provider aggregates the rules from the databag
charm_any = cast('Any', mgr.charm)
logql = OtlpProvider(charm_any, RECEIVE).rules('logql')
promql = OtlpProvider(charm_any, RECEIVE).rules('promql')
logql = OtlpProvider(mgr.charm, RECEIVE).rules('logql')
promql = OtlpProvider(mgr.charm, RECEIVE).rules('promql')
assert logql
assert promql
for result in [logql, promql]:
Expand Down
Loading