General tidy of charm

tox4: Update allowlist_externals with full path to commands

Minor fix to remove use of stored state in pebble handler.

Remove flake8 lint.

Reformat using black.

Resync charm scaffolding with other K8S operators.

Change-Id: I347ad7a5ad39827f2f3bf5f97a82e2a80d49ba62
This commit is contained in:
James Page 2023-01-18 13:26:58 +00:00
parent 54b778750a
commit 35953b18de
6 changed files with 159 additions and 58 deletions

View File

@ -0,0 +1,39 @@
# Copyright 2022 Canonical Ltd.
# See LICENSE file for licensing details.
# Testing tools configuration
[tool.coverage.run]
branch = true
[tool.coverage.report]
show_missing = true
[tool.pytest.ini_options]
minversion = "6.0"
log_cli_level = "INFO"
# Formatting tools configuration
[tool.black]
line-length = 79
[tool.isort]
profile = "black"
multi_line_output = 3
force_grid_wrap = true
# Linting tools configuration
[tool.flake8]
max-line-length = 79
max-doc-length = 99
max-complexity = 10
exclude = [".git", "__pycache__", ".tox", "build", "dist", "*.egg_info", "venv"]
select = ["E", "W", "F", "C", "N", "R", "D", "H"]
# Ignore W503, E501 because using black creates errors with this
# Ignore D107 Missing docstring in __init__
ignore = ["W503", "E501", "D107", "E402"]
per-file-ignores = []
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,12 +1,20 @@
#!/usr/bin/env python3
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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 Ceph Operator Charm.
This charm provide Cinder <-> Ceph integration as part
@ -14,31 +22,38 @@ of an OpenStack deployment
"""
import logging
from ops.main import main
from ops.model import ActiveStatus
from typing import List, Mapping
import ops_sunbeam.core as core
import ops_sunbeam.charm as charm
import ops_sunbeam.relation_handlers as relation_handlers
import ops_sunbeam.config_contexts as config_contexts
import ops_sunbeam.container_handlers as container_handlers
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
from typing import (
List,
Mapping,
)
import charms.cinder_k8s.v0.storage_backend as sunbeam_storage_backend # noqa
import ops_sunbeam.charm as charm
import ops_sunbeam.config_contexts as config_contexts
import ops_sunbeam.container_handlers as container_handlers
import ops_sunbeam.core as core
import ops_sunbeam.relation_handlers as relation_handlers
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
from ops.main import (
main,
)
from ops.model import (
ActiveStatus,
)
logger = logging.getLogger(__name__)
class CephConfigurationContext(config_contexts.ConfigContext):
def context(self):
"""Configuration context to parse ceph parameters."""
def context(self) -> dict:
"""Generate configuration information for ceph config."""
config = self.charm.model.config.get
ctxt = {}
if (
config("pool-type") and
config("pool-type") == sunbeam_rhandlers.ERASURE_CODED
config("pool-type")
and config("pool-type") == sunbeam_rhandlers.ERASURE_CODED
):
base_pool_name = config("rbd-pool") or config("rbd-pool-name")
if not base_pool_name:
@ -48,7 +63,10 @@ class CephConfigurationContext(config_contexts.ConfigContext):
class CinderCephConfigurationContext(config_contexts.ConfigContext):
def context(self):
"""Configuration context for cinder parameters."""
def context(self) -> dict:
"""Generate context information for cinder config."""
config = self.charm.model.config.get
data_pool_name = config("rbd-pool-name") or self.charm.app.name
if config("pool-type") == sunbeam_rhandlers.ERASURE_CODED:
@ -71,8 +89,10 @@ class CinderCephConfigurationContext(config_contexts.ConfigContext):
class StorageBackendProvidesHandler(sunbeam_rhandlers.RelationHandler):
"""Relation handler for storage-backend interface type."""
def setup_event_handler(self):
"""Configure event handlers for an Identity service relation."""
"""Configure event handlers for an storage-backend relation."""
logger.debug("Setting up Identity Service event handler")
sb_svc = sunbeam_storage_backend.StorageBackendProvides(
self.charm,
@ -89,12 +109,15 @@ class StorageBackendProvidesHandler(sunbeam_rhandlers.RelationHandler):
@property
def ready(self) -> bool:
"""Check whether storage-backend interface is ready for use."""
return self.interface.remote_ready()
class CinderVolumePebbleHandler(container_handlers.PebbleHandler):
"""Pebble handler for cinder-volume service."""
def get_layer(self) -> dict:
"""cinder-volume service pebble layer
"""cinder-volume service pebble layer.
:returns: pebble layer configuration for cinder-volume service
:rtype: dict
@ -123,14 +146,13 @@ class CinderVolumePebbleHandler(container_handlers.PebbleHandler):
"online": {
"override": "replace",
"level": "ready",
"exec": {
"command": f"service {self.service_name} status"
}
"exec": {"command": f"service {self.service_name} status"},
},
}
}
def start_service(self):
def start_service(self) -> None:
"""Start all services in associated container."""
container = self.charm.unit.get_container(self.container_name)
if not container:
logger.debug(
@ -144,14 +166,14 @@ class CinderVolumePebbleHandler(container_handlers.PebbleHandler):
container.start(self.service_name)
def init_service(self, context):
def init_service(self, context) -> None:
"""Write configs and start services."""
self.write_config(context)
self.start_service()
self._state.service_ready = True
class CinderCephOperatorCharm(charm.OSBaseOperatorCharm):
"""Cinder/Ceph Operator charm"""
"""Cinder/Ceph Operator charm."""
# NOTE: service_name == container_name
service_name = "cinder-volume"
@ -207,6 +229,7 @@ class CinderCephOperatorCharm(charm.OSBaseOperatorCharm):
]
def api_ready(self, event) -> None:
"""Event handler for bootstrap of service when api services are ready."""
self._state.api_ready = True
self.configure_charm(event)
if self._state.bootstrapped:
@ -243,15 +266,13 @@ class CinderCephOperatorCharm(charm.OSBaseOperatorCharm):
@property
def databases(self) -> Mapping[str, str]:
"""Provide database name for cinder services"""
return {
'database': 'cinder'
}
"""Provide database name for cinder services."""
return {"database": "cinder"}
def configure_charm(self, event) -> None:
"""Catchall handler to cconfigure charm services."""
if not self.relation_handlers_ready():
logging.debug("Defering configuration, charm relations not ready")
logging.debug("Deferring configuration, charm relations not ready")
return
for ph in self.pebble_handlers:
@ -264,13 +285,13 @@ class CinderCephOperatorCharm(charm.OSBaseOperatorCharm):
f"--name=client.{self.app.name}",
f"--add-key={self.ceph.key}",
],
exception_on_error=True
exception_on_error=True,
)
ph.init_service(self.contexts())
for ph in self.pebble_handlers:
if not ph.service_ready:
logging.debug("Defering, container service not ready")
logging.debug("Deferring, container service not ready")
return
# Add healthchecks to the plan

View File

@ -28,7 +28,7 @@ target_deploy_status:
workload-status-message-regex: '^$'
mysql:
workload-status: active
workload-status-message-regex: '^$'
workload-status-message-regex: '^.*$'
cinder:
workload-status: active
workload-status-message-regex: '^$'

View File

@ -1,4 +1,11 @@
# http://www.apache.org/licenses/LICENSE-2.0
#
# 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,
@ -6,5 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unit testing module for Cinder Ceph K8S operator."""
import ops.testing
ops.testing.SIMULATE_CAN_CONNECT = True

View File

@ -14,17 +14,20 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unit tests for Cinder Ceph operator charm class."""
import json
import charm
import ops_sunbeam.test_utils as test_utils
from ops.testing import (
Harness,
)
import charm
class _CinderCephOperatorCharm(charm.CinderCephOperatorCharm):
"""Charm wrapper for test usage."""
openstack_release = "wallaby"
@ -61,33 +64,28 @@ class _CinderCephOperatorCharm(charm.CinderCephOperatorCharm):
def add_complete_storage_backend_relation(harness: Harness) -> None:
"""Add complete storage-backend relation."""
storage_backend_rel = harness.add_relation(
"storage-backend", "cinder"
)
harness.add_relation_unit(
storage_backend_rel, "cinder/0"
)
storage_backend_rel = harness.add_relation("storage-backend", "cinder")
harness.add_relation_unit(storage_backend_rel, "cinder/0")
harness.update_relation_data(
storage_backend_rel,
"cinder",
{
"ready": json.dumps("true")
}
storage_backend_rel, "cinder", {"ready": json.dumps("true")}
)
class TestCinderCephOperatorCharm(test_utils.CharmTestCase):
"""Test cases for CinderCephOperatorCharm class."""
PATCHES = []
def setUp(self):
"""Setup fixtures ready for testing."""
super().setUp(charm, self.PATCHES)
self.harness = test_utils.get_harness(
_CinderCephOperatorCharm,
container_calls=self.container_calls)
_CinderCephOperatorCharm, container_calls=self.container_calls
)
self.addCleanup(self.harness.cleanup)
def test_all_relations(self):
"""Test charm in context of full set of relations."""
self.harness.begin_with_initial_hooks()
test_utils.add_complete_ceph_relation(self.harness)
test_utils.add_complete_amqp_relation(self.harness)

View File

@ -15,6 +15,8 @@ minversion = 3.18.0
src_path = {toxinidir}/src/
tst_path = {toxinidir}/tests/
lib_path = {toxinidir}/lib/
pyproject_toml = {toxinidir}/pyproject.toml
all_path = {[vars]src_path} {[vars]tst_path}
[testenv]
basepython = python3
@ -28,11 +30,20 @@ commands = stestr run --slowest {posargs}
allowlist_externals =
git
charmcraft
fetch-libs.sh
rename.sh
{toxinidir}/fetch-libs.sh
{toxinidir}/rename.sh
deps =
-r{toxinidir}/test-requirements.txt
[testenv:fmt]
description = Apply coding style standards to code
deps =
black
isort
commands =
isort {[vars]all_path} --skip-glob {[vars]lib_path} --skip {toxinidir}/.tox
black --config {[vars]pyproject_toml} {[vars]all_path} --exclude {[vars]lib_path}
[testenv:build]
basepython = python3
deps =
@ -64,11 +75,6 @@ deps = {[testenv:py3]deps}
basepython = python3.10
deps = {[testenv:py3]deps}
[testenv:pep8]
basepython = python3
deps = {[testenv]deps}
commands = flake8 {posargs} {[vars]src_path} {[vars]tst_path}
[testenv:cover]
basepython = python3
deps = {[testenv:py3]deps}
@ -83,6 +89,30 @@ commands =
coverage xml -o cover/coverage.xml
coverage report
[testenv:pep8]
description = Alias for lint
deps = {[testenv:lint]deps}
commands = {[testenv:lint]commands}
[testenv:lint]
description = Check code against coding style standards
deps =
black
flake8<6
flake8-docstrings
flake8-copyright
flake8-builtins
pyproject-flake8
pep8-naming
isort
codespell
commands =
codespell {[vars]all_path}
# pflake8 wrapper supports config from pyproject.toml
pflake8 --exclude {[vars]lib_path} --config {toxinidir}/pyproject.toml {[vars]all_path}
isort --check-only --diff {[vars]all_path} --skip-glob {[vars]lib_path}
black --config {[vars]pyproject_toml} --check --diff {[vars]all_path} --exclude {[vars]lib_path}
[testenv:func-noop]
basepython = python3
commands =
@ -95,6 +125,9 @@ commands =
[testenv:func-smoke]
basepython = python3
setenv =
TEST_MODEL_SETTINGS = automatically-retry-hooks=true
TEST_MAX_RESOLVE_COUNT = 5
commands =
functest-run-suite --keep-model --smoke