Skip to content

Commit 8fe759b

Browse files
[DPE-5312] Update tracing charm libs to retrieve them from tempo coordinator repo (#324)
## Issue There are a bunch of tempo tracing related updates: 1. tempo-k8s has been deprecated in favor of tempo ha 2. tracing charm libs need to be fetch-ed from tempo_coordinator_k8s 3. tracing traffic is now relayed through grafana-agent-k8s (like the remaining COS traffic) ## Solution 2. update tracing charm libs from new repo Test integration with tempo ha + test relay of traffic through grafana-agent-k8s ## To be done simultaneously Update mysql-router-k8s tracing related documentation (in progress)
1 parent 1e2ded4 commit 8fe759b

File tree

8 files changed

+140
-42
lines changed

8 files changed

+140
-42
lines changed

lib/charms/data_platform_libs/v0/data_interfaces.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -331,7 +331,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent):
331331

332332
# Increment this PATCH version before using `charmcraft publish-lib` or reset
333333
# to 0 if you are raising the major API version
334-
LIBPATCH = 39
334+
LIBPATCH = 40
335335

336336
PYDEPS = ["ops>=2.0.0"]
337337

@@ -391,6 +391,10 @@ class IllegalOperationError(DataInterfacesError):
391391
"""To be used when an operation is not allowed to be performed."""
392392

393393

394+
class PrematureDataAccessError(DataInterfacesError):
395+
"""To be raised when the Relation Data may be accessed (written) before protocol init complete."""
396+
397+
394398
##############################################################################
395399
# Global helpers / utilities
396400
##############################################################################
@@ -1453,6 +1457,8 @@ def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
14531457
class ProviderData(Data):
14541458
"""Base provides-side of the data products relation."""
14551459

1460+
RESOURCE_FIELD = "database"
1461+
14561462
def __init__(
14571463
self,
14581464
model: Model,
@@ -1618,6 +1624,15 @@ def _fetch_my_specific_relation_data(
16181624
def _update_relation_data(self, relation: Relation, data: Dict[str, str]) -> None:
16191625
"""Set values for fields not caring whether it's a secret or not."""
16201626
req_secret_fields = []
1627+
1628+
keys = set(data.keys())
1629+
if self.fetch_relation_field(relation.id, self.RESOURCE_FIELD) is None and (
1630+
keys - {"endpoints", "read-only-endpoints", "replset"}
1631+
):
1632+
raise PrematureDataAccessError(
1633+
"Premature access to relation data, update is forbidden before the connection is initialized."
1634+
)
1635+
16211636
if relation.app:
16221637
req_secret_fields = get_encoded_list(relation, relation.app, REQ_SECRET_FIELDS)
16231638

@@ -3290,6 +3305,8 @@ class KafkaRequiresEvents(CharmEvents):
32903305
class KafkaProviderData(ProviderData):
32913306
"""Provider-side of the Kafka relation."""
32923307

3308+
RESOURCE_FIELD = "topic"
3309+
32933310
def __init__(self, model: Model, relation_name: str) -> None:
32943311
super().__init__(model, relation_name)
32953312

@@ -3539,6 +3556,8 @@ class OpenSearchRequiresEvents(CharmEvents):
35393556
class OpenSearchProvidesData(ProviderData):
35403557
"""Provider-side of the OpenSearch relation."""
35413558

3559+
RESOURCE_FIELD = "index"
3560+
35423561
def __init__(self, model: Model, relation_name: str) -> None:
35433562
super().__init__(model, relation_name)
35443563

lib/charms/loki_k8s/v1/loki_push_api.py

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,25 @@ def _alert_rules_error(self, event):
480480
481481
Units of consumer charm send their alert rules over app relation data using the `alert_rules`
482482
key.
483+
484+
## Charm logging
485+
The `charms.loki_k8s.v0.charm_logging` library can be used in conjunction with this one to configure python's
486+
logging module to forward all logs to Loki via the loki-push-api interface.
487+
488+
```python
489+
from lib.charms.loki_k8s.v0.charm_logging import log_charm
490+
from lib.charms.loki_k8s.v1.loki_push_api import charm_logging_config, LokiPushApiConsumer
491+
492+
@log_charm(logging_endpoint="my_endpoints", server_cert="cert_path")
493+
class MyCharm(...):
494+
_cert_path = "/path/to/cert/on/charm/container.crt"
495+
def __init__(self, ...):
496+
self.logging = LokiPushApiConsumer(...)
497+
self.my_endpoints, self.cert_path = charm_logging_config(
498+
self.logging, self._cert_path)
499+
```
500+
501+
Do this, and all charm logs will be forwarded to Loki as soon as a relation is formed.
483502
"""
484503

485504
import json
@@ -527,7 +546,7 @@ def _alert_rules_error(self, event):
527546

528547
# Increment this PATCH version before using `charmcraft publish-lib` or reset
529548
# to 0 if you are raising the major API version
530-
LIBPATCH = 12
549+
LIBPATCH = 13
531550

532551
PYDEPS = ["cosl"]
533552

@@ -577,7 +596,11 @@ def _alert_rules_error(self, event):
577596
GRPC_LISTEN_PORT_START = 9095 # odd start port
578597

579598

580-
class RelationNotFoundError(ValueError):
599+
class LokiPushApiError(Exception):
600+
"""Base class for errors raised by this module."""
601+
602+
603+
class RelationNotFoundError(LokiPushApiError):
581604
"""Raised if there is no relation with the given name."""
582605

583606
def __init__(self, relation_name: str):
@@ -587,7 +610,7 @@ def __init__(self, relation_name: str):
587610
super().__init__(self.message)
588611

589612

590-
class RelationInterfaceMismatchError(Exception):
613+
class RelationInterfaceMismatchError(LokiPushApiError):
591614
"""Raised if the relation with the given name has a different interface."""
592615

593616
def __init__(
@@ -607,7 +630,7 @@ def __init__(
607630
super().__init__(self.message)
608631

609632

610-
class RelationRoleMismatchError(Exception):
633+
class RelationRoleMismatchError(LokiPushApiError):
611634
"""Raised if the relation with the given name has a different direction."""
612635

613636
def __init__(
@@ -2555,7 +2578,7 @@ def _on_pebble_ready(self, event: PebbleReadyEvent):
25552578

25562579
self._update_endpoints(event.workload, loki_endpoints)
25572580

2558-
def _update_logging(self, _):
2581+
def _update_logging(self, event: RelationEvent):
25592582
"""Update the log forwarding to match the active Loki endpoints."""
25602583
if not (loki_endpoints := self._retrieve_endpoints_from_relation()):
25612584
logger.warning("No Loki endpoints available")
@@ -2566,6 +2589,8 @@ def _update_logging(self, _):
25662589
self._update_endpoints(container, loki_endpoints)
25672590
# else: `_update_endpoints` will be called on pebble-ready anyway.
25682591

2592+
self._handle_alert_rules(event.relation)
2593+
25692594
def _retrieve_endpoints_from_relation(self) -> dict:
25702595
loki_endpoints = {}
25712596

@@ -2750,3 +2775,49 @@ def _exec(self, cmd) -> str:
27502775
result = subprocess.run(cmd, check=True, stdout=subprocess.PIPE)
27512776
output = result.stdout.decode("utf-8").strip()
27522777
return output
2778+
2779+
2780+
def charm_logging_config(
2781+
endpoint_requirer: LokiPushApiConsumer, cert_path: Optional[Union[Path, str]]
2782+
) -> Tuple[Optional[List[str]], Optional[str]]:
2783+
"""Utility function to determine the charm_logging config you will likely want.
2784+
2785+
If no endpoint is provided:
2786+
disable charm logging.
2787+
If https endpoint is provided but cert_path is not found on disk:
2788+
disable charm logging.
2789+
If https endpoint is provided and cert_path is None:
2790+
ERROR
2791+
Else:
2792+
proceed with charm logging (with or without tls, as appropriate)
2793+
2794+
Args:
2795+
endpoint_requirer: an instance of LokiPushApiConsumer.
2796+
cert_path: a path where a cert is stored.
2797+
2798+
Returns:
2799+
A tuple with (optionally) the values of the endpoints and the certificate path.
2800+
2801+
Raises:
2802+
LokiPushApiError: if some endpoint are http and others https.
2803+
"""
2804+
endpoints = [ep["url"] for ep in endpoint_requirer.loki_endpoints]
2805+
if not endpoints:
2806+
return None, None
2807+
2808+
https = tuple(endpoint.startswith("https://") for endpoint in endpoints)
2809+
2810+
if all(https): # all endpoints are https
2811+
if cert_path is None:
2812+
raise LokiPushApiError("Cannot send logs to https endpoints without a certificate.")
2813+
if not Path(cert_path).exists():
2814+
# if endpoints is https BUT we don't have a server_cert yet:
2815+
# disable charm logging until we do to prevent tls errors
2816+
return None, None
2817+
return endpoints, str(cert_path)
2818+
2819+
if all(not x for x in https): # all endpoints are http
2820+
return endpoints, None
2821+
2822+
# if there's a disagreement, that's very weird:
2823+
raise LokiPushApiError("Some endpoints are http, some others are https. That's not good.")

lib/charms/tempo_k8s/v1/charm_tracing.py renamed to lib/charms/tempo_coordinator_k8s/v0/charm_tracing.py

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,15 @@
1212
# Quickstart
1313
Fetch the following charm libs (and ensure the minimum version/revision numbers are satisfied):
1414
15-
charmcraft fetch-lib charms.tempo_k8s.v2.tracing # >= 1.10
16-
charmcraft fetch-lib charms.tempo_k8s.v1.charm_tracing # >= 2.7
15+
charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.tracing # >= 1.10
16+
charmcraft fetch-lib charms.tempo_coordinator_k8s.v0.charm_tracing # >= 2.7
1717
1818
Then edit your charm code to include:
1919
2020
```python
2121
# import the necessary charm libs
22-
from charms.tempo_k8s.v2.tracing import TracingEndpointRequirer, charm_tracing_config
23-
from charms.tempo_k8s.v1.charm_tracing import charm_tracing
22+
from charms.tempo_coordinator_k8s.v0.tracing import TracingEndpointRequirer, charm_tracing_config
23+
from charms.tempo_coordinator_k8s.v0.charm_tracing import charm_tracing
2424
2525
# decorate your charm class with charm_tracing:
2626
@charm_tracing(
@@ -51,7 +51,7 @@ def __init__(self, ...):
5151
5252
2) add to your charm a "my_tracing_endpoint" (you can name this attribute whatever you like)
5353
**property**, **method** or **instance attribute** that returns an otlp http/https endpoint url.
54-
If you are using the ``charms.tempo_k8s.v2.tracing.TracingEndpointRequirer`` as
54+
If you are using the ``charms.tempo_coordinator_k8s.v0.tracing.TracingEndpointRequirer`` as
5555
``self.tracing = TracingEndpointRequirer(self)``, the implementation could be:
5656
5757
```
@@ -80,7 +80,7 @@ def my_tracing_endpoint(self) -> Optional[str]:
8080
8181
For example:
8282
```
83-
from charms.tempo_k8s.v1.charm_tracing import trace_charm
83+
from charms.tempo_coordinator_k8s.v0.charm_tracing import trace_charm
8484
@trace_charm(
8585
tracing_endpoint="my_tracing_endpoint",
8686
server_cert="_server_cert"
@@ -129,7 +129,7 @@ def get_tracer(self) -> opentelemetry.trace.Tracer:
129129
For example:
130130
131131
```
132-
from charms.tempo_k8s.v0.charm_tracing import trace_charm
132+
from charms.tempo_coordinator_k8s.v0.charm_tracing import trace_charm
133133
134134
@trace_charm(
135135
tracing_endpoint="my_tracing_endpoint",
@@ -150,7 +150,7 @@ def my_tracing_endpoint(self) -> Optional[str]:
150150
needs to be replaced with:
151151
152152
```
153-
from charms.tempo_k8s.v1.charm_tracing import trace_charm
153+
from charms.tempo_coordinator_k8s.v0.charm_tracing import trace_charm
154154
155155
@trace_charm(
156156
tracing_endpoint="my_tracing_endpoint",
@@ -249,28 +249,27 @@ def _remove_stale_otel_sdk_packages():
249249
from opentelemetry.sdk.resources import Resource
250250
from opentelemetry.sdk.trace import Span, TracerProvider
251251
from opentelemetry.sdk.trace.export import BatchSpanProcessor
252+
from opentelemetry.trace import INVALID_SPAN, Tracer
253+
from opentelemetry.trace import get_current_span as otlp_get_current_span
252254
from opentelemetry.trace import (
253-
INVALID_SPAN,
254-
Tracer,
255255
get_tracer,
256256
get_tracer_provider,
257257
set_span_in_context,
258258
set_tracer_provider,
259259
)
260-
from opentelemetry.trace import get_current_span as otlp_get_current_span
261260
from ops.charm import CharmBase
262261
from ops.framework import Framework
263262

264263
# The unique Charmhub library identifier, never change it
265-
LIBID = "cb1705dcd1a14ca09b2e60187d1215c7"
264+
LIBID = "01780f1e588c42c3976d26780fdf9b89"
266265

267266
# Increment this major API version when introducing breaking changes
268-
LIBAPI = 1
267+
LIBAPI = 0
269268

270269
# Increment this PATCH version before using `charmcraft publish-lib` or reset
271270
# to 0 if you are raising the major API version
272271

273-
LIBPATCH = 15
272+
LIBPATCH = 1
274273

275274
PYDEPS = ["opentelemetry-exporter-otlp-proto-http==1.21.0"]
276275

@@ -332,7 +331,7 @@ def _get_tracer() -> Optional[Tracer]:
332331
return tracer.get()
333332
except LookupError:
334333
# fallback: this course-corrects for a user error where charm_tracing symbols are imported
335-
# from different paths (typically charms.tempo_k8s... and lib.charms.tempo_k8s...)
334+
# from different paths (typically charms.tempo_coordinator_k8s... and lib.charms.tempo_coordinator_k8s...)
336335
try:
337336
ctx: Context = copy_context()
338337
if context_tracer := _get_tracer_from_context(ctx):
@@ -562,8 +561,8 @@ def trace_charm(
562561
method calls on instances of this class.
563562
564563
Usage:
565-
>>> from charms.tempo_k8s.v1.charm_tracing import trace_charm
566-
>>> from charms.tempo_k8s.v1.tracing import TracingEndpointRequirer
564+
>>> from charms.tempo_coordinator_k8s.v0.charm_tracing import trace_charm
565+
>>> from charms.tempo_coordinator_k8s.v0.tracing import TracingEndpointRequirer
567566
>>> from ops import CharmBase
568567
>>>
569568
>>> @trace_charm(
@@ -626,7 +625,7 @@ def _autoinstrument(
626625
627626
Usage:
628627
629-
>>> from charms.tempo_k8s.v1.charm_tracing import _autoinstrument
628+
>>> from charms.tempo_coordinator_k8s.v0.charm_tracing import _autoinstrument
630629
>>> from ops.main import main
631630
>>> _autoinstrument(
632631
>>> MyCharm,

0 commit comments

Comments
 (0)