mirror of
https://github.com/home-assistant/core
synced 2024-08-02 23:40:32 +02:00
Fix duplicate metrics in prometheus (#61355)
* Fix duplicate metrics in prometheus * Fix duplicate prometheus metrics for entities with multiple labelsets - Move friendly_name detection to state_changed event - Add additional test case * Add review suggestions for prometheus friendly name update * Remove commented out code in prometheus * Update prometheus tests for deleted metrics * Add review suggestions for prometheus - Remove unnecessary firendly_name check in handle_entity_registry_updated - Add assert in test
This commit is contained in:
parent
51a04585e7
commit
3e0e9e54bf
@ -40,6 +40,7 @@ from homeassistant.const import (
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entityfilter, state as state_helper
|
from homeassistant.helpers import entityfilter, state as state_helper
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
|
||||||
from homeassistant.helpers.entity_values import EntityValues
|
from homeassistant.helpers.entity_values import EntityValues
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.util.temperature import fahrenheit_to_celsius
|
from homeassistant.util.temperature import fahrenheit_to_celsius
|
||||||
@ -112,7 +113,10 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|||||||
default_metric,
|
default_metric,
|
||||||
)
|
)
|
||||||
|
|
||||||
hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_event)
|
hass.bus.listen(EVENT_STATE_CHANGED, metrics.handle_state_changed)
|
||||||
|
hass.bus.listen(
|
||||||
|
EVENT_ENTITY_REGISTRY_UPDATED, metrics.handle_entity_registry_updated
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
@ -150,7 +154,7 @@ class PrometheusMetrics:
|
|||||||
self._metrics = {}
|
self._metrics = {}
|
||||||
self._climate_units = climate_units
|
self._climate_units = climate_units
|
||||||
|
|
||||||
def handle_event(self, event):
|
def handle_state_changed(self, event):
|
||||||
"""Listen for new messages on the bus, and add them to Prometheus."""
|
"""Listen for new messages on the bus, and add them to Prometheus."""
|
||||||
if (state := event.data.get("new_state")) is None:
|
if (state := event.data.get("new_state")) is None:
|
||||||
return
|
return
|
||||||
@ -162,6 +166,11 @@ class PrometheusMetrics:
|
|||||||
if not self._filter(state.entity_id):
|
if not self._filter(state.entity_id):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if (old_state := event.data.get("old_state")) is not None and (
|
||||||
|
old_friendly_name := old_state.attributes.get(ATTR_FRIENDLY_NAME)
|
||||||
|
) != state.attributes.get(ATTR_FRIENDLY_NAME):
|
||||||
|
self._remove_labelsets(old_state.entity_id, old_friendly_name)
|
||||||
|
|
||||||
ignored_states = (STATE_UNAVAILABLE, STATE_UNKNOWN)
|
ignored_states = (STATE_UNAVAILABLE, STATE_UNKNOWN)
|
||||||
|
|
||||||
handler = f"_handle_{domain}"
|
handler = f"_handle_{domain}"
|
||||||
@ -189,6 +198,46 @@ class PrometheusMetrics:
|
|||||||
)
|
)
|
||||||
last_updated_time_seconds.labels(**labels).set(state.last_updated.timestamp())
|
last_updated_time_seconds.labels(**labels).set(state.last_updated.timestamp())
|
||||||
|
|
||||||
|
def handle_entity_registry_updated(self, event):
|
||||||
|
"""Listen for deleted, disabled or renamed entities and remove them from the Prometheus Registry."""
|
||||||
|
if (action := event.data.get("action")) in (None, "create"):
|
||||||
|
return
|
||||||
|
|
||||||
|
entity_id = event.data.get("entity_id")
|
||||||
|
_LOGGER.debug("Handling entity update for %s", entity_id)
|
||||||
|
|
||||||
|
metrics_entity_id = None
|
||||||
|
|
||||||
|
if action == "remove":
|
||||||
|
metrics_entity_id = entity_id
|
||||||
|
elif action == "update":
|
||||||
|
changes = event.data.get("changes")
|
||||||
|
|
||||||
|
if "entity_id" in changes:
|
||||||
|
metrics_entity_id = changes["entity_id"]
|
||||||
|
elif "disabled_by" in changes:
|
||||||
|
metrics_entity_id = entity_id
|
||||||
|
|
||||||
|
if metrics_entity_id:
|
||||||
|
self._remove_labelsets(metrics_entity_id)
|
||||||
|
|
||||||
|
def _remove_labelsets(self, entity_id, friendly_name=None):
|
||||||
|
"""Remove labelsets matching the given entity id from all metrics."""
|
||||||
|
for _, metric in self._metrics.items():
|
||||||
|
for sample in metric.collect()[0].samples:
|
||||||
|
if sample.labels["entity"] == entity_id and (
|
||||||
|
not friendly_name or sample.labels["friendly_name"] == friendly_name
|
||||||
|
):
|
||||||
|
_LOGGER.debug(
|
||||||
|
"Removing labelset from %s for entity_id: %s",
|
||||||
|
sample.name,
|
||||||
|
entity_id,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
metric.remove(*sample.labels.values())
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
def _handle_attributes(self, state):
|
def _handle_attributes(self, state):
|
||||||
for key, value in state.attributes.items():
|
for key, value in state.attributes.items():
|
||||||
metric = self._metric(
|
metric = self._metric(
|
||||||
|
@ -24,6 +24,7 @@ from homeassistant.const import (
|
|||||||
TEMP_FAHRENHEIT,
|
TEMP_FAHRENHEIT,
|
||||||
)
|
)
|
||||||
from homeassistant.core import split_entity_id
|
from homeassistant.core import split_entity_id
|
||||||
|
from homeassistant.helpers import entity_registry
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
|
|
||||||
@ -726,6 +727,392 @@ async def test_counter(hass, hass_client):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_renaming_entity_name(hass, hass_client):
|
||||||
|
"""Test renaming entity name."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"conversation",
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
client = await setup_prometheus_client(hass, hass_client, "")
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
body = await generate_latest_metrics(client)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'sensor_temperature_celsius{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"} 15.6' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'sensor_humidity_percent{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 54.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'climate_action{action="heating",'
|
||||||
|
'domain="climate",'
|
||||||
|
'entity="climate.heatpump",'
|
||||||
|
'friendly_name="HeatPump"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'climate_action{action="cooling",'
|
||||||
|
'domain="climate",'
|
||||||
|
'entity="climate.heatpump",'
|
||||||
|
'friendly_name="HeatPump"} 0.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
registry = entity_registry.async_get(hass)
|
||||||
|
assert "sensor.outside_temperature" in registry.entities
|
||||||
|
assert "climate.heatpump" in registry.entities
|
||||||
|
registry.async_update_entity(
|
||||||
|
entity_id="sensor.outside_temperature",
|
||||||
|
name="Outside Temperature Renamed",
|
||||||
|
)
|
||||||
|
registry.async_update_entity(
|
||||||
|
entity_id="climate.heatpump",
|
||||||
|
name="HeatPump Renamed",
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
body = await generate_latest_metrics(client)
|
||||||
|
|
||||||
|
# Check if old metrics deleted
|
||||||
|
body_line = "\n".join(body)
|
||||||
|
assert 'friendly_name="Outside Temperature"' not in body_line
|
||||||
|
assert 'friendly_name="HeatPump"' not in body_line
|
||||||
|
|
||||||
|
# Check if new metrics created
|
||||||
|
assert (
|
||||||
|
'sensor_temperature_celsius{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature Renamed"} 15.6' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature Renamed"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'climate_action{action="heating",'
|
||||||
|
'domain="climate",'
|
||||||
|
'entity="climate.heatpump",'
|
||||||
|
'friendly_name="HeatPump Renamed"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'climate_action{action="cooling",'
|
||||||
|
'domain="climate",'
|
||||||
|
'entity="climate.heatpump",'
|
||||||
|
'friendly_name="HeatPump Renamed"} 0.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep other sensors
|
||||||
|
assert (
|
||||||
|
'sensor_humidity_percent{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 54.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_renaming_entity_id(hass, hass_client):
|
||||||
|
"""Test renaming entity id."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"conversation",
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
client = await setup_prometheus_client(hass, hass_client, "")
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
body = await generate_latest_metrics(client)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'sensor_temperature_celsius{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"} 15.6' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'sensor_humidity_percent{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 54.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
registry = entity_registry.async_get(hass)
|
||||||
|
assert "sensor.outside_temperature" in registry.entities
|
||||||
|
registry.async_update_entity(
|
||||||
|
entity_id="sensor.outside_temperature",
|
||||||
|
new_entity_id="sensor.outside_temperature_renamed",
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
body = await generate_latest_metrics(client)
|
||||||
|
|
||||||
|
# Check if old metrics deleted
|
||||||
|
body_line = "\n".join(body)
|
||||||
|
assert 'entity="sensor.outside_temperature"' not in body_line
|
||||||
|
|
||||||
|
# Check if new metrics created
|
||||||
|
assert (
|
||||||
|
'sensor_temperature_celsius{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature_renamed",'
|
||||||
|
'friendly_name="Outside Temperature"} 15.6' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature_renamed",'
|
||||||
|
'friendly_name="Outside Temperature"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
# Keep other sensors
|
||||||
|
assert (
|
||||||
|
'sensor_humidity_percent{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 54.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_deleting_entity(hass, hass_client):
|
||||||
|
"""Test deleting a entity."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"conversation",
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
client = await setup_prometheus_client(hass, hass_client, "")
|
||||||
|
|
||||||
|
await async_setup_component(
|
||||||
|
hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
body = await generate_latest_metrics(client)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'sensor_temperature_celsius{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"} 15.6' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'sensor_humidity_percent{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 54.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'climate_action{action="heating",'
|
||||||
|
'domain="climate",'
|
||||||
|
'entity="climate.heatpump",'
|
||||||
|
'friendly_name="HeatPump"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'climate_action{action="cooling",'
|
||||||
|
'domain="climate",'
|
||||||
|
'entity="climate.heatpump",'
|
||||||
|
'friendly_name="HeatPump"} 0.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
registry = entity_registry.async_get(hass)
|
||||||
|
assert "sensor.outside_temperature" in registry.entities
|
||||||
|
assert "climate.heatpump" in registry.entities
|
||||||
|
registry.async_remove("sensor.outside_temperature")
|
||||||
|
registry.async_remove("climate.heatpump")
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
body = await generate_latest_metrics(client)
|
||||||
|
|
||||||
|
# Check if old metrics deleted
|
||||||
|
body_line = "\n".join(body)
|
||||||
|
assert 'entity="sensor.outside_temperature"' not in body_line
|
||||||
|
assert 'friendly_name="Outside Temperature"' not in body_line
|
||||||
|
assert 'entity="climate.heatpump"' not in body_line
|
||||||
|
assert 'friendly_name="HeatPump"' not in body_line
|
||||||
|
|
||||||
|
# Keep other sensors
|
||||||
|
assert (
|
||||||
|
'sensor_humidity_percent{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 54.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_disabling_entity(hass, hass_client):
|
||||||
|
"""Test disabling a entity."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
"conversation",
|
||||||
|
{},
|
||||||
|
)
|
||||||
|
client = await setup_prometheus_client(hass, hass_client, "")
|
||||||
|
|
||||||
|
await async_setup_component(
|
||||||
|
hass, climate.DOMAIN, {"climate": [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass, sensor.DOMAIN, {"sensor": [{"platform": "demo"}]}
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
body = await generate_latest_metrics(client)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'sensor_temperature_celsius{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"} 15.6' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'state_change_total{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert any(
|
||||||
|
'state_change_created{domain="sensor",'
|
||||||
|
'entity="sensor.outside_temperature",'
|
||||||
|
'friendly_name="Outside Temperature"}' in metric
|
||||||
|
for metric in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'sensor_humidity_percent{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 54.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'climate_action{action="heating",'
|
||||||
|
'domain="climate",'
|
||||||
|
'entity="climate.heatpump",'
|
||||||
|
'friendly_name="HeatPump"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'climate_action{action="cooling",'
|
||||||
|
'domain="climate",'
|
||||||
|
'entity="climate.heatpump",'
|
||||||
|
'friendly_name="HeatPump"} 0.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
registry = entity_registry.async_get(hass)
|
||||||
|
assert "sensor.outside_temperature" in registry.entities
|
||||||
|
assert "climate.heatpump" in registry.entities
|
||||||
|
registry.async_update_entity(
|
||||||
|
entity_id="sensor.outside_temperature",
|
||||||
|
disabled_by="user",
|
||||||
|
)
|
||||||
|
registry.async_update_entity(entity_id="climate.heatpump", disabled_by="user")
|
||||||
|
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
body = await generate_latest_metrics(client)
|
||||||
|
|
||||||
|
# Check if old metrics deleted
|
||||||
|
body_line = "\n".join(body)
|
||||||
|
assert 'entity="sensor.outside_temperature"' not in body_line
|
||||||
|
assert 'friendly_name="Outside Temperature"' not in body_line
|
||||||
|
assert 'entity="climate.heatpump"' not in body_line
|
||||||
|
assert 'friendly_name="HeatPump"' not in body_line
|
||||||
|
|
||||||
|
# Keep other sensors
|
||||||
|
assert (
|
||||||
|
'sensor_humidity_percent{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 54.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
assert (
|
||||||
|
'entity_available{domain="sensor",'
|
||||||
|
'entity="sensor.outside_humidity",'
|
||||||
|
'friendly_name="Outside Humidity"} 1.0' in body
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(name="mock_client")
|
@pytest.fixture(name="mock_client")
|
||||||
def mock_client_fixture():
|
def mock_client_fixture():
|
||||||
"""Mock the prometheus client."""
|
"""Mock the prometheus client."""
|
||||||
|
Loading…
Reference in New Issue
Block a user