From 61a6ec0399ac33843d39c9b4bd9ca23ad72e4154 Mon Sep 17 00:00:00 2001 From: Adam King Date: Mon, 27 Jun 2022 14:26:37 -0400 Subject: [PATCH] src/python_common: Add CustomConfig class Signed-off-by: Adam King --- .../ceph/deployment/drive_group.py | 6 +- .../ceph/deployment/service_spec.py | 92 +++++++++++++++++-- 2 files changed, 87 insertions(+), 11 deletions(-) diff --git a/src/python-common/ceph/deployment/drive_group.py b/src/python-common/ceph/deployment/drive_group.py index b451d664ec4..9be03df108d 100644 --- a/src/python-common/ceph/deployment/drive_group.py +++ b/src/python-common/ceph/deployment/drive_group.py @@ -2,7 +2,7 @@ import enum import yaml from ceph.deployment.inventory import Device -from ceph.deployment.service_spec import ServiceSpec, PlacementSpec +from ceph.deployment.service_spec import ServiceSpec, PlacementSpec, CustomConfig from ceph.deployment.hostspec import SpecValidationError try: @@ -179,6 +179,7 @@ class DriveGroupSpec(ServiceSpec): method=None, # type: Optional[OSDMethod] crush_device_class=None, # type: Optional[str] config=None, # type: Optional[Dict[str, str]] + custom_configs=None, # type: Optional[List[CustomConfig]] ): assert service_type is None or service_type == 'osd' super(DriveGroupSpec, self).__init__('osd', service_id=service_id, @@ -186,7 +187,8 @@ class DriveGroupSpec(ServiceSpec): config=config, unmanaged=unmanaged, preview_only=preview_only, - extra_container_args=extra_container_args) + extra_container_args=extra_container_args, + custom_configs=custom_configs) #: A :class:`ceph.deployment.drive_group.DeviceSelection` self.data_devices = data_devices diff --git a/src/python-common/ceph/deployment/service_spec.py b/src/python-common/ceph/deployment/service_spec.py index 02807418507..13b75728f53 100644 --- a/src/python-common/ceph/deployment/service_spec.py +++ b/src/python-common/ceph/deployment/service_spec.py @@ -1,4 +1,5 @@ import fnmatch +import os import re import enum from collections import OrderedDict @@ -420,6 +421,58 @@ tPlacementSpec(hostname='host2', network='', name='')]) _service_spec_from_json_validate = True +class CustomConfig: + """ + Class to specify custom config files to be mounted in daemon's container + """ + + _fields = ['content', 'mount_path'] + + def __init__(self, content: str, mount_path: str) -> None: + self.content: str = content + self.mount_path: str = mount_path + self.validate() + + def to_json(self) -> Dict[str, Any]: + return { + 'content': self.content, + 'mount_path': self.mount_path, + } + + @classmethod + def from_json(cls, data: Dict[str, Any]) -> "CustomConfig": + for k in cls._fields: + if k not in data: + raise SpecValidationError(f'CustomConfig must have "{k}" field') + for k in data.keys(): + if k not in cls._fields: + raise SpecValidationError(f'CustomConfig got unknown field "{k}"') + return cls(**data) + + @property + def filename(self) -> str: + return os.path.basename(self.mount_path) + + def __eq__(self, other: Any) -> bool: + if isinstance(other, CustomConfig): + return ( + self.content == other.content + and self.mount_path == other.mount_path + ) + return NotImplemented + + def __repr__(self) -> str: + return f'CustomConfig({self.mount_path})' + + def validate(self) -> None: + if not isinstance(self.content, str): + raise SpecValidationError( + f'CustomConfig content must be a string. Got {type(self.content)}') + if not isinstance(self.mount_path, str): + raise SpecValidationError( + f'CustomConfig content must be a string. Got {type(self.mount_path)}') + + @contextmanager def service_spec_allow_invalid_from_json() -> Iterator[None]: """ @@ -506,6 +559,7 @@ class ServiceSpec(object): preview_only: bool = False, networks: Optional[List[str]] = None, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): #: See :ref:`orchestrator-cli-placement-spec`. @@ -545,6 +599,7 @@ class ServiceSpec(object): self.config = {k.replace(' ', '_'): v for k, v in config.items()} self.extra_container_args: Optional[List[str]] = extra_container_args + self.custom_configs: Optional[List[CustomConfig]] = custom_configs @classmethod @handle_type_error @@ -632,6 +687,8 @@ class ServiceSpec(object): for k, v in json_spec.items(): if k == 'placement': v = PlacementSpec.from_json(v) + if k == 'custom_configs': + v = [CustomConfig.from_json(c) for c in v] if k == 'spec': args.update(v) continue @@ -670,6 +727,8 @@ class ServiceSpec(object): ret['networks'] = self.networks if self.extra_container_args: ret['extra_container_args'] = self.extra_container_args + if self.custom_configs: + ret['custom_configs'] = [c.to_json() for c in self.custom_configs] c = {} for key, val in sorted(self.__dict__.items(), key=lambda tpl: tpl[0]): @@ -746,12 +805,14 @@ class NFSServiceSpec(ServiceSpec): networks: Optional[List[str]] = None, port: Optional[int] = None, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type == 'nfs' super(NFSServiceSpec, self).__init__( 'nfs', service_id=service_id, placement=placement, unmanaged=unmanaged, preview_only=preview_only, - config=config, networks=networks, extra_container_args=extra_container_args) + config=config, networks=networks, extra_container_args=extra_container_args, + custom_configs=custom_configs) self.port = port @@ -809,6 +870,7 @@ class RGWSpec(ServiceSpec): networks: Optional[List[str]] = None, subcluster: Optional[str] = None, # legacy, only for from_json on upgrade extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type == 'rgw', service_type @@ -820,7 +882,7 @@ class RGWSpec(ServiceSpec): 'rgw', service_id=service_id, placement=placement, unmanaged=unmanaged, preview_only=preview_only, config=config, networks=networks, - extra_container_args=extra_container_args) + extra_container_args=extra_container_args, custom_configs=custom_configs) #: The RGW realm associated with this service. Needs to be manually created self.rgw_realm: Optional[str] = rgw_realm @@ -878,13 +940,15 @@ class IscsiServiceSpec(ServiceSpec): config: Optional[Dict[str, str]] = None, networks: Optional[List[str]] = None, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type == 'iscsi' super(IscsiServiceSpec, self).__init__('iscsi', service_id=service_id, placement=placement, unmanaged=unmanaged, preview_only=preview_only, config=config, networks=networks, - extra_container_args=extra_container_args) + extra_container_args=extra_container_args, + custom_configs=custom_configs) #: RADOS pool where ceph-iscsi config data is stored. self.pool = pool @@ -947,13 +1011,15 @@ class IngressSpec(ServiceSpec): unmanaged: bool = False, ssl: bool = False, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type == 'ingress' super(IngressSpec, self).__init__( 'ingress', service_id=service_id, placement=placement, config=config, networks=networks, - extra_container_args=extra_container_args + extra_container_args=extra_container_args, + custom_configs=custom_configs ) self.backend_service = backend_service self.frontend_port = frontend_port @@ -1076,6 +1142,7 @@ class MonitoringSpec(ServiceSpec): preview_only: bool = False, port: Optional[int] = None, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type in ['grafana', 'node-exporter', 'prometheus', 'alertmanager', 'loki', 'promtail'] @@ -1084,7 +1151,8 @@ class MonitoringSpec(ServiceSpec): service_type, service_id, placement=placement, unmanaged=unmanaged, preview_only=preview_only, config=config, - networks=networks, extra_container_args=extra_container_args) + networks=networks, extra_container_args=extra_container_args, + custom_configs=custom_configs) self.service_type = service_type self.port = port @@ -1120,13 +1188,14 @@ class AlertManagerSpec(MonitoringSpec): port: Optional[int] = None, secure: bool = False, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type == 'alertmanager' super(AlertManagerSpec, self).__init__( 'alertmanager', service_id=service_id, placement=placement, unmanaged=unmanaged, preview_only=preview_only, config=config, networks=networks, port=port, - extra_container_args=extra_container_args) + extra_container_args=extra_container_args, custom_configs=custom_configs) # Custom configuration. # @@ -1171,13 +1240,14 @@ class GrafanaSpec(MonitoringSpec): port: Optional[int] = None, initial_admin_password: Optional[str] = None, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type == 'grafana' super(GrafanaSpec, self).__init__( 'grafana', service_id=service_id, placement=placement, unmanaged=unmanaged, preview_only=preview_only, config=config, networks=networks, port=port, - extra_container_args=extra_container_args) + extra_container_args=extra_container_args, custom_configs=custom_configs) self.initial_admin_password = initial_admin_password @@ -1225,6 +1295,7 @@ class SNMPGatewaySpec(ServiceSpec): preview_only: bool = False, port: Optional[int] = None, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type == 'snmp-gateway' @@ -1233,7 +1304,8 @@ class SNMPGatewaySpec(ServiceSpec): placement=placement, unmanaged=unmanaged, preview_only=preview_only, - extra_container_args=extra_container_args) + extra_container_args=extra_container_args, + custom_configs=custom_configs) self.service_type = service_type self.snmp_version = snmp_version @@ -1344,6 +1416,7 @@ class MDSSpec(ServiceSpec): unmanaged: bool = False, preview_only: bool = False, extra_container_args: Optional[List[str]] = None, + custom_configs: Optional[List[CustomConfig]] = None, ): assert service_type == 'mds' super(MDSSpec, self).__init__('mds', service_id=service_id, @@ -1351,7 +1424,8 @@ class MDSSpec(ServiceSpec): config=config, unmanaged=unmanaged, preview_only=preview_only, - extra_container_args=extra_container_args) + extra_container_args=extra_container_args, + custom_configs=custom_configs) def validate(self) -> None: super(MDSSpec, self).validate() -- 2.39.5