From a8aefb4b13e0c751b356f78d92d77196ae3a9296 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 (cherry picked from commit 61a6ec0399ac33843d39c9b4bd9ca23ad72e4154) Conflicts: src/python-common/ceph/deployment/drive_group.py --- .../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 6f96b2f6de872..fcd2e1f114bdd 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: @@ -178,13 +178,15 @@ class DriveGroupSpec(ServiceSpec): data_allocate_fraction=None, # type: Optional[float] method=None, # type: Optional[OSDMethod] crush_device_class=None, # type: Optional[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, placement=placement, 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 c23783c5da0e8..a80bbc9ee2969 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]: """ @@ -500,6 +553,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`. @@ -539,6 +593,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 @@ -626,6 +681,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 @@ -664,6 +721,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]): @@ -740,12 +799,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 @@ -803,6 +864,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 @@ -814,7 +876,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 @@ -872,13 +934,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 @@ -941,13 +1005,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 @@ -1070,6 +1136,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'] @@ -1078,7 +1145,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 @@ -1114,13 +1182,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. # @@ -1165,13 +1234,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 @@ -1219,6 +1289,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' @@ -1227,7 +1298,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 @@ -1338,6 +1410,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, @@ -1345,7 +1418,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