Fix configuration issues

* Add pipeline.yaml
* Add event_pipeline.yaml
* Add service_credentials to ceilometer.conf
* Add action ceilometer-upgrade
* Run tox -e fmt for formatting code

Change-Id: I8c1edc0bdaa71ee48a272f8c4d4aa6fae6642f4e
This commit is contained in:
Hemanth Nakkina 2023-08-25 16:52:16 +05:30
parent 6a227b9bcb
commit e3d77b77be
9 changed files with 282 additions and 34 deletions

View File

@ -1,2 +1,7 @@
# NOTE: no actions yet! # Copyright 2023 Canonical Ltd.
{ } # See LICENSE file for licensing details.
ceilometer-upgrade:
description: |
Perform ceilometer-upgrade. This action will update Ceilometer data store
configuration.

View File

@ -1,3 +1,6 @@
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.
# Testing tools configuration # Testing tools configuration
[tool.coverage.run] [tool.coverage.run]
branch = true branch = true
@ -11,23 +14,26 @@ log_cli_level = "INFO"
# Formatting tools configuration # Formatting tools configuration
[tool.black] [tool.black]
line-length = 99 line-length = 79
target-version = ["py38"]
[tool.isort] [tool.isort]
line_length = 99
profile = "black" profile = "black"
multi_line_output = 3
force_grid_wrap = true
# Linting tools configuration # Linting tools configuration
[tool.flake8] [tool.flake8]
max-line-length = 99 max-line-length = 79
max-doc-length = 99 max-doc-length = 99
max-complexity = 10 max-complexity = 10
exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"] exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"]
select = ["E", "W", "F", "C", "N", "R", "D", "H"] select = ["E", "W", "F", "C", "N", "R", "D", "H"]
# Ignore W503, E501 because using black creates errors with this # Ignore W503, E501 because using black creates errors with this
# Ignore D107 Missing docstring in __init__ # Ignore D107 Missing docstring in __init__
ignore = ["W503", "E501", "D107"] ignore = ["W503", "E501", "D107", "E402"]
# D100, D101, D102, D103: Ignore missing docstrings in tests per-file-ignores = []
per-file-ignores = ["tests/*:D100,D101,D102,D103,D104"]
docstring-convention = "google" docstring-convention = "google"
# Check for properly formatted copyright header in each file
copyright-check = "True"
copyright-author = "Canonical Ltd."
copyright-regexp = "Copyright\\s\\d{4}([-,]\\d{4})*\\s+%(author)s"

View File

@ -1,4 +1,18 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Copyright 2023 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Ceilometer Operator Charm. """Ceilometer Operator Charm.
This charm provide Ceilometer services as part of an OpenStack deployment This charm provide Ceilometer services as part of an OpenStack deployment
@ -6,13 +20,20 @@ This charm provide Ceilometer services as part of an OpenStack deployment
import logging import logging
import uuid import uuid
from typing import List from typing import (
List,
)
import ops.framework import ops.framework
import ops_sunbeam.charm as sunbeam_charm import ops_sunbeam.charm as sunbeam_charm
import ops_sunbeam.container_handlers as container_handlers import ops_sunbeam.container_handlers as container_handlers
import ops_sunbeam.core as core import ops_sunbeam.core as sunbeam_core
from ops.main import main from ops.charm import (
ActionEvent,
)
from ops.main import (
main,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -44,8 +65,20 @@ class CeilometerCentralPebbleHandler(container_handlers.ServicePebbleHandler):
}, },
} }
def default_container_configs(
self,
) -> List[sunbeam_core.ContainerConfigFile]:
"""Container configurations for handler.
class CeilometerNotificationPebbleHandler(container_handlers.ServicePebbleHandler): :returns: Container configuration files
:rtype: List[ContainerConfigFile]
"""
return self.charm.container_configs
class CeilometerNotificationPebbleHandler(
container_handlers.ServicePebbleHandler
):
"""Pebble handler for ceilometer-notification service.""" """Pebble handler for ceilometer-notification service."""
def get_layer(self) -> dict: def get_layer(self) -> dict:
@ -69,6 +102,33 @@ class CeilometerNotificationPebbleHandler(container_handlers.ServicePebbleHandle
}, },
} }
def default_container_configs(
self,
) -> List[sunbeam_core.ContainerConfigFile]:
"""Container configurations for handler.
:returns: Container configuration files
:rtype: List[ContainerConfigFile]
"""
_cconfigs = self.charm.container_configs
_cconfigs.extend(
[
sunbeam_core.ContainerConfigFile(
"/etc/ceilometer/pipeline.yaml",
self.charm.service_user,
self.charm.service_group,
0o640,
),
sunbeam_core.ContainerConfigFile(
"/etc/ceilometer/event_pipeline.yaml",
self.charm.service_user,
self.charm.service_group,
0o640,
),
]
)
return _cconfigs
class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S): class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
"""Charm the service.""" """Charm the service."""
@ -78,6 +138,12 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
mandatory_relations = {"amqp", "identity-credentials"} mandatory_relations = {"amqp", "identity-credentials"}
def __init__(self, framework: ops.framework):
super().__init__(framework)
self.framework.observe(
self.on.ceilometer_upgrade_action, self._ceilometer_upgrade_action
)
def get_shared_meteringsecret(self): def get_shared_meteringsecret(self):
"""Return the shared metering secret.""" """Return the shared metering secret."""
return self.leader_get(self.shared_metering_secret_key) return self.leader_get(self.shared_metering_secret_key)
@ -103,13 +169,23 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
super().configure_charm(event) super().configure_charm(event)
@property @property
def container_configs(self) -> List[core.ContainerConfigFile]: def service_user(self) -> str:
"""Service user file and directory ownership."""
return "ceilometer"
@property
def service_group(self) -> str:
"""Service group file and directory ownership."""
return "ceilometer"
@property
def container_configs(self) -> List[sunbeam_core.ContainerConfigFile]:
"""Container configurations for the operator.""" """Container configurations for the operator."""
_cconfigs = [ _cconfigs = [
core.ContainerConfigFile( sunbeam_core.ContainerConfigFile(
"/etc/ceilometer/ceilometer.conf", "/etc/ceilometer/ceilometer.conf",
"root", self.service_user,
"ceilometer", self.service_group,
0o640, 0o640,
), ),
] ]
@ -122,7 +198,7 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
self, self,
CEILOMETER_CENTRAL_CONTAINER, CEILOMETER_CENTRAL_CONTAINER,
"ceilometer-central", "ceilometer-central",
self.container_configs, [],
self.template_dir, self.template_dir,
self.configure_charm, self.configure_charm,
), ),
@ -130,12 +206,36 @@ class CeilometerOperatorCharm(sunbeam_charm.OSBaseOperatorCharmK8S):
self, self,
CEILOMETER_NOTIFICATION_CONTAINER, CEILOMETER_NOTIFICATION_CONTAINER,
"ceilometer-notification", "ceilometer-notification",
self.container_configs, [],
self.template_dir, self.template_dir,
self.configure_charm, self.configure_charm,
), ),
] ]
def _ceilometer_upgrade_action(self, event: ActionEvent) -> None:
"""Run ceilometer-upgrade.
This action will upgrade the data store configuration in gnocchi.
"""
try:
logger.info("Syncing database...")
cmd = ["ceilometer-upgrade"]
container = self.unit.get_container(
CEILOMETER_NOTIFICATION_CONTAINER
)
process = container.exec(cmd, timeout=5 * 60)
out, warnings = process.wait_output()
logging.debug("Output from database sync: \n%s", out)
if warnings:
for line in warnings.splitlines():
logger.warning("DB Sync Out: %s", line.strip())
event.fail(f"Error in running ceilometer-upgrade: {warnings}")
else:
event.set_results({"message": "ceilometer-upgrade successful"})
except Exception as e:
logger.exception(e)
event.fail(f"Error in running ceilometer-updgrade: {e}")
if __name__ == "__main__": if __name__ == "__main__":
main(CeilometerOperatorCharm) main(CeilometerOperatorCharm)

View File

@ -2,18 +2,10 @@
debug = {{ options.debug }} debug = {{ options.debug }}
# event_pipeline_cfg_file = /etc/ceilometer/event_pipeline.yaml # event_pipeline_cfg_file = /etc/ceilometer/event_pipeline.yaml
meter_dispatchers = gnocchi
event_dispatchers = gnocchi
{% if amqp.transport_url -%} {% if amqp.transport_url -%}
transport_url = {{ amqp.transport_url }} transport_url = {{ amqp.transport_url }}
{%- endif %} {%- endif %}
[notification]
{% if amqp.transport_url -%}
messaging_urls = {{ amqp.transport_url }}
{% endif %}
[polling] [polling]
batch_size = 50 batch_size = 50
@ -29,3 +21,5 @@ archive_policy = low
{% include "parts/identity-data-id-creds" %} {% include "parts/identity-data-id-creds" %}
{% include "parts/section-service-user-id-creds" %} {% include "parts/section-service-user-id-creds" %}
{% include "parts/section-service-credentials" %}

View File

@ -0,0 +1,12 @@
---
sources:
- name: event_source
events:
- "*"
sinks:
- event_sink
sinks:
- name: event_sink
transformers:
publishers:
- notifier://?topic=alarm.all

View File

@ -0,0 +1,14 @@
{% if identity_credentials.project_domain_id -%}
[service_credentials]
{% if identity_credentials.internal_auth_url -%}
auth_url = {{ identity_credentials.internal_auth_url }}
{% elif identity_credentials.internal_host -%}
auth_url = {{ identity_credentials.internal_protocol }}://{{ identity_credentials.internal_host }}:{{ identity_credentials.internal_port }}
{% endif -%}
auth_type = password
project_domain_id = {{ identity_credentials.project_domain_id }}
user_domain_id = {{ identity_credentials.user_domain_id }}
project_name = {{ identity_credentials.project_name }}
username = {{ identity_credentials.username }}
password = {{ identity_credentials.password }}
{% endif -%}

View File

@ -0,0 +1,89 @@
---
sources:
- name: meter_source
meters:
- "*"
sinks:
- meter_sink
- name: cpu_source
meters:
- "cpu"
sinks:
- cpu_sink
- cpu_delta_sink
- name: disk_source
meters:
- "disk.read.bytes"
- "disk.read.requests"
- "disk.write.bytes"
- "disk.write.requests"
- "disk.device.read.bytes"
- "disk.device.read.requests"
- "disk.device.write.bytes"
- "disk.device.write.requests"
sinks:
- disk_sink
- name: network_source
meters:
- "network.incoming.bytes"
- "network.incoming.packets"
- "network.outgoing.bytes"
- "network.outgoing.packets"
sinks:
- network_sink
sinks:
- name: meter_sink
transformers:
publishers:
- gnocchi://
- name: cpu_sink
transformers:
- name: "rate_of_change"
parameters:
target:
name: "cpu_util"
unit: "%"
type: "gauge"
max: 100
scale: "100.0 / (10**9 * (resource_metadata.cpu_number or 1))"
publishers:
- gnocchi://
- name: cpu_delta_sink
transformers:
- name: "delta"
parameters:
target:
name: "cpu.delta"
growth_only: True
publishers:
- gnocchi://
- name: disk_sink
transformers:
- name: "rate_of_change"
parameters:
source:
map_from:
name: "(disk\\.device|disk)\\.(read|write)\\.(bytes|requests)"
unit: "(B|request)"
target:
map_to:
name: "\\1.\\2.\\3.rate"
unit: "\\1/s"
type: "gauge"
publishers:
- gnocchi://
- name: network_sink
transformers:
- name: "rate_of_change"
parameters:
source:
map_from:
name: "network\\.(incoming|outgoing)\\.(bytes|packets)"
unit: "(B|packet)"
target:
map_to:
name: "network.\\1.\\2.rate"
unit: "\\1/s"
type: "gauge"
publishers:
- gnocchi://

View File

@ -0,0 +1 @@
../actions.yaml

View File

@ -1,14 +1,32 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# Copyright 2023 liam
# See LICENSE file for licensing details. # Copyright 2023 Canonical Ltd.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Tests for ceilometer charm."""
import asyncio import asyncio
import logging import logging
from pathlib import Path from pathlib import (
Path,
)
import pytest import pytest
import yaml import yaml
from pytest_operator.plugin import OpsTest from pytest_operator.plugin import (
OpsTest,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -24,12 +42,21 @@ async def test_build_and_deploy(ops_test: OpsTest):
""" """
# Build and deploy charm from local source folder # Build and deploy charm from local source folder
charm = await ops_test.build_charm(".") charm = await ops_test.build_charm(".")
resources = {"httpbin-image": METADATA["resources"]["httpbin-image"]["upstream-source"]} resources = {
"httpbin-image": METADATA["resources"]["httpbin-image"][
"upstream-source"
]
}
# Deploy the charm and wait for active/idle status # Deploy the charm and wait for active/idle status
await asyncio.gather( await asyncio.gather(
ops_test.model.deploy(await charm, resources=resources, application_name=APP_NAME), ops_test.model.deploy(
await charm, resources=resources, application_name=APP_NAME
),
ops_test.model.wait_for_idle( ops_test.model.wait_for_idle(
apps=[APP_NAME], status="active", raise_on_blocked=True, timeout=1000 apps=[APP_NAME],
status="active",
raise_on_blocked=True,
timeout=1000,
), ),
) )