Skip to content
Merged
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
27 changes: 23 additions & 4 deletions lib/charms/data_platform_libs/v0/data_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 23
LIBPATCH = 24

PYDEPS = ["ops>=2.0.0"]

Expand Down Expand Up @@ -526,7 +526,16 @@ def get_content(self) -> Dict[str, str]:
"""Getting cached secret content."""
if not self._secret_content:
if self.meta:
self._secret_content = self.meta.get_content()
try:
self._secret_content = self.meta.get_content(refresh=True)
except (ValueError, ModelError) as err:
# https://bugs.launchpad.net/juju/+bug/2042596
# Only triggered when 'refresh' is set
msg = "ERROR either URI or label should be used for getting an owned secret but not both"
if isinstance(err, ModelError) and msg not in str(err):
raise
# Due to: ValueError: Secret owner cannot use refresh=True
self._secret_content = self.meta.get_content()
return self._secret_content

def set_content(self, content: Dict[str, str]) -> None:
Expand Down Expand Up @@ -1085,7 +1094,7 @@ def _delete_relation_secret(
secret = self._get_relation_secret(relation.id, group)

if not secret:
logging.error("Can't update secret for relation %s", str(relation.id))
logging.error("Can't delete secret for relation %s", str(relation.id))
return False

old_content = secret.get_content()
Expand Down Expand Up @@ -1827,7 +1836,8 @@ def _assign_relation_alias(self, relation_id: int) -> None:

# We need to set relation alias also on the application level so,
# it will be accessible in show-unit juju command, executed for a consumer application unit
self.update_relation_data(relation_id, {"alias": available_aliases[0]})
if self.local_unit.is_leader():
self.update_relation_data(relation_id, {"alias": available_aliases[0]})

def _emit_aliased_event(self, event: RelationChangedEvent, event_name: str) -> None:
"""Emit an aliased event to a particular relation if it has an alias.
Expand Down Expand Up @@ -1914,6 +1924,9 @@ def _on_relation_created_event(self, event: RelationCreatedEvent) -> None:

# Sets both database and extra user roles in the relation
# if the roles are provided. Otherwise, sets only the database.
if not self.local_unit.is_leader():
return

if self.extra_user_roles:
self.update_relation_data(
event.relation.id,
Expand Down Expand Up @@ -2173,6 +2186,9 @@ def _on_relation_created_event(self, event: RelationCreatedEvent) -> None:
"""Event emitted when the Kafka relation is created."""
super()._on_relation_created_event(event)

if not self.local_unit.is_leader():
return

# Sets topic, extra user roles, and "consumer-group-prefix" in the relation
relation_data = {
f: getattr(self, f.replace("-", "_"), "")
Expand Down Expand Up @@ -2345,6 +2361,9 @@ def _on_relation_created_event(self, event: RelationCreatedEvent) -> None:
"""Event emitted when the OpenSearch relation is created."""
super()._on_relation_created_event(event)

if not self.local_unit.is_leader():
return

# Sets both index and extra user roles in the relation if the roles are provided.
# Otherwise, sets only the index.
data = {"index": self.index}
Expand Down
35 changes: 6 additions & 29 deletions lib/charms/grafana_agent/v0/cos_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,17 +206,15 @@ def __init__(self, *args):
```
"""

import base64
import json
import logging
import lzma
from collections import namedtuple
from itertools import chain
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Optional, Set, Union

import pydantic
from cosl import JujuTopology
from cosl import GrafanaDashboard, JujuTopology
from cosl.rules import AlertRules
from ops.charm import RelationChangedEvent
from ops.framework import EventBase, EventSource, Object, ObjectEvents
Expand All @@ -236,7 +234,7 @@ class _MetricsEndpointDict(TypedDict):

LIBID = "dc15fa84cef84ce58155fb84f6c6213a"
LIBAPI = 0
LIBPATCH = 6
LIBPATCH = 7

PYDEPS = ["cosl", "pydantic < 2"]

Expand All @@ -251,31 +249,6 @@ class _MetricsEndpointDict(TypedDict):
SnapEndpoint = namedtuple("SnapEndpoint", "owner, name")


class GrafanaDashboard(str):
"""Grafana Dashboard encoded json; lzma-compressed."""

# TODO Replace this with a custom type when pydantic v2 released (end of 2023 Q1?)
# https://github.com/pydantic/pydantic/issues/4887
@staticmethod
def _serialize(raw_json: Union[str, bytes]) -> "GrafanaDashboard":
if not isinstance(raw_json, bytes):
raw_json = raw_json.encode("utf-8")
encoded = base64.b64encode(lzma.compress(raw_json)).decode("utf-8")
return GrafanaDashboard(encoded)

def _deserialize(self) -> Dict:
try:
raw = lzma.decompress(base64.b64decode(self.encode("utf-8"))).decode()
return json.loads(raw)
except json.decoder.JSONDecodeError as e:
logger.error("Invalid Dashboard format: %s", e)
return {}

def __repr__(self):
"""Return string representation of self."""
return "<GrafanaDashboard>"


class CosAgentProviderUnitData(pydantic.BaseModel):
"""Unit databag model for `cos-agent` relation."""

Expand Down Expand Up @@ -748,6 +721,10 @@ def metrics_jobs(self) -> List[Dict]:
"job_name": job["job_name"],
"metrics_path": job["path"],
"static_configs": [{"targets": [f"localhost:{job['port']}"]}],
# We include insecure_skip_verify because we are always scraping localhost.
# Even if we have the certs for the scrape targets, we'd rather specify the scrape
# jobs with localhost rather than the SAN DNS the cert was issued for.
"tls_config": {"insecure_skip_verify": True},
}

scrape_jobs.append(job)
Expand Down
8 changes: 5 additions & 3 deletions src/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@

PG_BASE_CONF_PATH = f"{POSTGRESQL_CONF_PATH}/postgresql.conf"

RUNNING_STATES = ["running", "streaming"]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Patroni 3.0.4 exposes the status of replicas using pg_stat_get_wal_receiver() so the replicas are usually in streaming state.



class NotReadyError(Exception):
"""Raised when not all cluster members healthy or finished initial sync."""
Expand Down Expand Up @@ -297,7 +299,7 @@ def are_all_members_ready(self) -> bool:
# because sometimes there may exist (for some period of time) only
# replicas after a failed switchover.
return all(
member["state"] == "running" for member in cluster_status.json()["members"]
member["state"] in RUNNING_STATES for member in cluster_status.json()["members"]
) and any(member["role"] == "leader" for member in cluster_status.json()["members"])

def get_patroni_health(self) -> Dict[str, str]:
Expand Down Expand Up @@ -366,7 +368,7 @@ def member_started(self) -> bool:
except RetryError:
return False

return response["state"] == "running"
return response["state"] in RUNNING_STATES

@property
def member_inactive(self) -> bool:
Expand All @@ -381,7 +383,7 @@ def member_inactive(self) -> bool:
except RetryError:
return True

return response["state"] not in ["running", "starting", "restarting"]
return response["state"] not in [*RUNNING_STATES, "starting", "restarting"]

@property
def member_replication_lag(self) -> str:
Expand Down
2 changes: 1 addition & 1 deletion src/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
# Snap constants.
PGBACKREST_EXECUTABLE = "charmed-postgresql.pgbackrest"
POSTGRESQL_SNAP_NAME = "charmed-postgresql"
SNAP_PACKAGES = [(POSTGRESQL_SNAP_NAME, {"revision": "87"})]
SNAP_PACKAGES = [(POSTGRESQL_SNAP_NAME, {"revision": "89"})]

SNAP_COMMON_PATH = "/var/snap/charmed-postgresql/common"
SNAP_CURRENT_PATH = "/var/snap/charmed-postgresql/current"
Expand Down