Files
sunbeam-charms/charms/cinder-k8s/src/charm.py
Lucian Petrut 58a4c980bb Enable audit middleware
We're enabling the audit middleware for the following API services:

* cinder
* nova
* neutron
* glance
* heat
* barbican
* octavia
* gnocchi
  * config.verbose was deprecated a long time ago and Gnocchi doesn't
    support a separate "logging.conf" file
     * as such, Gnocchi can't be configured to use "info" level logging
     * the audit logs will only be emitted in debug mode
  * cf001e8428/gnocchi/service.py (L72-L79)
* newly defined audit map (the other services had already existing
  definitions, which were updated):
  * aodh
  * designate
  * magnum
  * masakari
    * updated wsgi log configuration

The following services do not support the audit middleware,
the support for api paste was dropped:
* placement
* watcher
* keystone (has its own pycadf audit implementation)
  * emits notifications using oslo.notifications
  * configurable driver, possible options include "messagingv2" and "log"
  * we can't just use the log driver since the user may request amqp
    notifications using:
      "juju config keystone enable-telemetry-notifications=True"
  * we'll listen for amqp notifications using a separate service

Reference:
* audit middleware configuration: https://docs.openstack.org/keystonemiddleware/latest/audit.html
* api map samples: https://opendev.org/openstack/pycadf/src/tag/3.1.1/etc/pycadf

Change-Id: I28a261b85067704221d6ab3d949c5d2a27a4a9d7
2025-06-10 13:52:25 +00:00

319 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
#
# Copyright 2021 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.
"""Cinder Operator Charm.
This charm provide Cinder services as part of an OpenStack deployment
"""
import logging
from typing import (
Dict,
List,
Mapping,
)
import charms.cinder_k8s.v0.storage_backend as sunbeam_storage_backend # noqa
import ops
import ops.pebble
import ops_sunbeam.charm as sunbeam_charm
import ops_sunbeam.container_handlers as sunbeam_chandlers
import ops_sunbeam.core as sunbeam_core
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
import ops_sunbeam.tracing as sunbeam_tracing
logger = logging.getLogger(__name__)
CINDER_API_PORT = 8090
CINDER_API_CONTAINER = "cinder-api"
CINDER_SCHEDULER_CONTAINER = "cinder-scheduler"
@sunbeam_tracing.trace_type
class CinderWSGIPebbleHandler(sunbeam_chandlers.WSGIPebbleHandler):
"""Pebble handler for Cinder WSGI services."""
def start_service(self):
"""Start services in container."""
pass
def init_service(self, context) -> None:
"""Enable and start WSGI service."""
self.write_config(context)
try:
self.execute(["a2disconf", "cinder-wsgi"], exception_on_error=True)
self.execute(
["a2ensite", self.wsgi_service_name], exception_on_error=True
)
except ops.pebble.ExecError:
logger.exception(
f"Failed to enable {self.wsgi_service_name} site in apache"
)
# ignore for now - pebble is raising an exited too quickly, but it
# appears to work properly.
self.start_wsgi()
def get_healthcheck_layer(self) -> dict:
"""Health check pebble layer.
:returns: pebble health check layer configuration for cinder-api
service
:rtype: dict
"""
return {
"checks": {
"online": {
"override": "replace",
"level": "ready",
"exec": {"command": "service apache2 status"},
},
}
}
def default_container_configs(self) -> List[Dict]:
"""Generate default configuration files for container."""
return [
sunbeam_core.ContainerConfigFile(self.wsgi_conf, "root", "root"),
sunbeam_core.ContainerConfigFile(
"/etc/cinder/cinder.conf", "root", "cinder", 0o640
),
sunbeam_core.ContainerConfigFile(
"/etc/cinder/api_audit_map.conf", "root", "cinder", 0o640
),
sunbeam_core.ContainerConfigFile(
"/usr/local/share/ca-certificates/ca-bundle.pem",
"root",
"cinder",
0o640,
),
]
@sunbeam_tracing.trace_type
class CinderSchedulerPebbleHandler(sunbeam_chandlers.ServicePebbleHandler):
"""Pebble handler for Cinder Scheduler services."""
def get_layer(self) -> dict:
"""Cinder Scheduler service.
:returns: pebble layer configuration for wsgi services
:rtype: dict
"""
return {
"summary": "cinder layer",
"description": "pebble configuration for cinder services",
"services": {
"cinder-scheduler": {
"override": "replace",
"summary": "Cinder Scheduler",
"command": "cinder-scheduler --use-syslog",
"user": "cinder",
"group": "cinder",
}
},
}
def default_container_configs(self) -> List[Dict]:
"""Generate default configuration files for container."""
return [
sunbeam_core.ContainerConfigFile(
"/etc/cinder/cinder.conf", "root", "cinder", 0o640
),
sunbeam_core.ContainerConfigFile(
"/usr/local/share/ca-certificates/ca-bundle.pem",
"root",
"cinder",
0o640,
),
]
@sunbeam_tracing.trace_type
class StorageBackendRequiresHandler(sunbeam_rhandlers.RelationHandler):
"""Relation handler for cinder storage backends."""
def setup_event_handler(self):
"""Configure event handlers for an Identity service relation."""
logger.debug("Setting up Identity Service event handler")
sb_svc = sunbeam_tracing.trace_type(
sunbeam_storage_backend.StorageBackendRequires
)(
self.charm,
self.relation_name,
)
self.framework.observe(sb_svc.on.ready, self._on_ready)
return sb_svc
def _on_ready(self, event) -> None:
"""Handles AMQP change events."""
# Ready is only emitted when the interface considers
# that the relation is complete (indicated by a password)
self.callback_f(event)
def set_ready(self) -> None:
"""Flag that all services are running and ready for use."""
return self.interface.set_ready()
@property
def ready(self) -> bool:
"""Determine whether interface is ready for use."""
return True
@sunbeam_tracing.trace_sunbeam_charm
class CinderOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
"""Charm the service."""
_authed = False
service_name = "cinder-api"
wsgi_admin_script = "/usr/bin/cinder-wsgi"
wsgi_public_script = "/usr/bin/cinder-wsgi"
db_sync_cmds = [
[
"sudo",
"-u",
"cinder",
"cinder-manage",
"--config-dir",
"/etc/cinder",
"db",
"sync",
],
]
def get_relation_handlers(
self, handlers=None
) -> List[sunbeam_rhandlers.RelationHandler]:
"""Relation handlers for the service."""
handlers = handlers or []
if self.can_add_handler("storage-backend", handlers):
self.sb_svc = StorageBackendRequiresHandler(
self,
"storage-backend",
self.configure_charm,
"storage-backend" in self.mandatory_relations,
)
handlers.append(self.sb_svc)
handlers = super().get_relation_handlers(handlers)
return handlers
@property
def service_endpoints(self) -> List[Dict]:
"""Service endpoints for the Cinder API services."""
return [
{
"service_name": "cinderv3",
"type": "volumev3",
"description": "Cinder Volume Service v3",
"internal_url": f"{self.internal_url}/v3/$(tenant_id)s",
"public_url": f"{self.public_url}/v3/$(tenant_id)s",
"admin_url": f"{self.admin_url}/v3/$(tenant_id)s",
},
{
"service_name": "cinder",
"type": "block-storage",
"description": "Cinder Block Storage service",
"internal_url": f"{self.internal_url}/v3/$(tenant_id)s",
"public_url": f"{self.public_url}/v3/$(tenant_id)s",
"admin_url": f"{self.admin_url}/v3/$(tenant_id)s",
},
]
@property
def container_configs(self) -> List[sunbeam_core.ContainerConfigFile]:
"""Container configuration files for the service."""
_cconfigs = [
sunbeam_core.ContainerConfigFile(
"/etc/cinder/api-paste.ini",
"root",
self.service_group,
0o640,
)
]
return _cconfigs
@property
def databases(self) -> Mapping[str, str]:
"""Provide database name for cinder services."""
return {"database": "cinder"}
def get_pebble_handlers(self) -> List[sunbeam_chandlers.PebbleHandler]:
"""Pebble handlers for the charm."""
pebble_handlers = [
CinderWSGIPebbleHandler(
self,
CINDER_API_CONTAINER,
self.service_name,
self.container_configs,
self.template_dir,
self.configure_charm,
f"wsgi-{self.service_name}",
),
CinderSchedulerPebbleHandler(
self,
CINDER_SCHEDULER_CONTAINER,
"cinder-scheduler",
[],
self.template_dir,
self.configure_charm,
),
]
return pebble_handlers
@property
def default_public_ingress_port(self):
"""Public ingress port for service."""
return 8776
@property
def ingress_healthcheck_path(self):
"""Healthcheck path for ingress relation."""
return "/healthcheck"
@property
def service_conf(self) -> str:
"""Service default configuration file."""
return "/etc/cinder/cinder.conf"
@property
def service_user(self) -> str:
"""Service user file and directory ownership."""
return "cinder"
@property
def service_group(self) -> str:
"""Service group file and directory ownership."""
return "cinder"
@property
def db_sync_container_name(self) -> str:
"""Name of Containerto run db sync from."""
return CINDER_SCHEDULER_CONTAINER
def configure_charm(self, event) -> None:
"""Configure the charmed services."""
super().configure_charm(event)
if self.bootstrapped():
# Tell storage backends we are ready
self.sb_svc.set_ready()
if __name__ == "__main__": # pragma: nocover
ops.main(CinderOperatorCharm)