[glance-k8s] add image-cap-size option

Add image-cap-size option to glance charm. If no ceph relation, check
limit is smaller than persistent volume's capacity.

Change-Id: I772cc9b4a75fbbe7216e07088b14bdf57b195908
This commit is contained in:
Guillaume Boutry 2024-05-10 20:23:45 +02:00
parent 44c4bf300f
commit af1d400828
No known key found for this signature in database
GPG Key ID: E95E3326872E55DE
7 changed files with 151 additions and 3 deletions

View File

@ -259,3 +259,14 @@ options:
type: boolean
default: False
description: Enable notifications to send to telemetry.
image-size-cap:
type: string
default: 5GB
description: |
Maximum size of image a user can upload. Defaults to 5GB
(5368709120 bytes). Example values: 500M, 500MB, 5G, 5TB.
Valid units: K, KB, M, MB, G, GB, T, TB, P, PB. If no units provided,
bytes are assumed.
.
WARNING: this value should only be increased after careful consideration
and must be set to a value under 8EB (9223372036854775808 bytes).

View File

@ -42,6 +42,7 @@ resources:
storage:
local-repository:
type: filesystem
minimum-size: 10GiB
description: |
A local filesystem storage repository for glance images to be saved to.
Note, this must be shared storage in order to support a highly

View File

@ -22,6 +22,7 @@ This charm provide Glance services as part of an OpenStack deployment
"""
import logging
import re
from typing import (
Callable,
List,
@ -32,7 +33,18 @@ import ops_sunbeam.compound_status as compound_status
import ops_sunbeam.config_contexts as sunbeam_ctxts
import ops_sunbeam.container_handlers as sunbeam_chandlers
import ops_sunbeam.core as sunbeam_core
import ops_sunbeam.guard as sunbeam_guard
import ops_sunbeam.relation_handlers as sunbeam_rhandlers
from lightkube.core.client import (
Client,
)
from lightkube.core.exceptions import (
ApiError,
)
from lightkube.resources.core_v1 import (
PersistentVolumeClaim,
Pod,
)
from ops.charm import (
CharmBase,
)
@ -51,6 +63,7 @@ from ops.model import (
logger = logging.getLogger(__name__)
IMAGES_DIR = "/var/lib/glance/images"
STORAGE_NAME = "local-repository"
# Use Apache to translate /<model-name> to /. This should be possible
# adding rules to the api-paste.ini but this does not seem to work
@ -186,6 +199,58 @@ class GlanceStorageRelationHandler(sunbeam_rhandlers.CephClientHandler):
return {}
class GlanceConfigContext(sunbeam_ctxts.ConfigContext):
"""Glance configuration context."""
def __init__(
self,
charm: sunbeam_charm.OSBaseOperatorAPICharm,
namespace: str,
) -> None:
"""Initialise the context."""
super().__init__(charm, namespace)
def context(self) -> dict:
"""Context used when rendering templates."""
return {
"image_size_cap": bytes_from_string(
self.charm.config["image-size-cap"]
),
}
def bytes_from_string(value: str) -> int:
"""Interpret human readable string value as bytes.
Returns int
"""
byte_power = {
"K": 1,
"KB": 1,
"Ki": 1,
"M": 2,
"MB": 2,
"Mi": 2,
"G": 3,
"GB": 3,
"Gi": 3,
"T": 4,
"TB": 4,
"Ti": 4,
"P": 5,
"PB": 5,
"Pi": 5,
}
matches = re.match(r"([0-9]+)\s*([a-zA-Z]+)", value)
if matches:
return int(matches.group(1)) * (1024 ** byte_power[matches.group(2)])
try:
return int(value)
except ValueError:
msg = f"Unable to interpret string value {value!r} as bytes"
raise ValueError(msg)
class GlanceOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
"""Charm the service."""
@ -243,6 +308,7 @@ class GlanceOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
self, "cinder_ceph"
)
)
contexts.append(GlanceConfigContext(self, "glance_config"))
return contexts
@property
@ -447,6 +513,75 @@ class GlanceOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm):
logger.info("CONFIG changed and ceph ready: calling request pools")
self.ceph.request_pools(event)
def configure_unit(self, event: EventBase) -> None:
"""Run configuration on this unit."""
self.check_configuration(event)
return super().configure_unit(event)
def check_configuration(self, event: EventBase):
"""Check a configuration key is correct."""
try:
self._validate_image_size_cap()
except ValueError as e:
raise sunbeam_guard.BlockedExceptionError(str(e)) from e
def _validate_image_size_cap(self):
"""Check image size is valid."""
try:
image_cap_size = bytes_from_string(self.config["image-size-cap"])
except ValueError as e:
raise ValueError(
"image-size-cap must be a number or a number followed by "
"KG, MG, GB, TB, or PB"
) from e
if self.has_ceph_relation():
logger.debug("ceph relation exists, skipping PVC size check")
return
pvc_size = self._fetch_volume_size()
if pvc_size < image_cap_size:
raise ValueError(
"image-size-cap must be less than the size"
" of the local-repository volume"
)
def _fetch_volume_size(
self,
):
"""Fetch the size of the local-repository volume."""
client = Client() # type: ignore
try:
pod = client.get(
Pod,
name="-".join(self.unit.name.rsplit("/", 1)),
namespace=self.model.name,
)
except ApiError as e:
if e.status.code == 404:
raise Exception("Failed to find associated pod")
raise sunbeam_guard.BlockedExceptionError(e.status.message) from e
lr_volume = None
for volume in pod.spec.volumes:
if volume.name.startswith(self.app.name + "-" + STORAGE_NAME):
lr_volume = volume
break
if lr_volume is None:
raise sunbeam_guard.BlockedExceptionError(
"Failed to find local-repository volume in pod spec"
)
claim_name = lr_volume.persistentVolumeClaim.claimName
try:
pvc = client.get(
PersistentVolumeClaim,
name=claim_name,
namespace=self.model.name,
)
except ApiError as e:
if e.status.code == 404:
raise Exception("Failed to find associated PVC")
raise sunbeam_guard.BlockedExceptionError(e.status.message) from e
return bytes_from_string(pvc.status.capacity["storage"])
if __name__ == "__main__":
main(GlanceOperatorCharm)

View File

@ -10,6 +10,7 @@ transport_url = {{ amqp.transport_url }}
{% endif %}
bind_port = 9282
workers = 4
image_size_cap = {{ glance_config.image_size_cap }}
{% if ceph.auth %}
enabled_backends = filestore:file, ceph:rbd

View File

@ -80,7 +80,7 @@ applications:
scale: 1
trust: true
storage:
local-repository: 5G
local-repository: 10G
resources:
glance-api-image: ghcr.io/canonical/glance-api:2024.1
heat:

View File

@ -85,7 +85,7 @@ applications:
scale: 1
trust: true
storage:
local-repository: 5G
local-repository: 10G
resources:
glance-api-image: ghcr.io/canonical/glance-api:2024.1
nova:

View File

@ -85,7 +85,7 @@ applications:
scale: 1
trust: true
storage:
local-repository: 5G
local-repository: 10G
resources:
glance-api-image: ghcr.io/canonical/glance-api:2024.1
nova: