
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
319 lines
10 KiB
Python
Executable File
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)
|