From d62dc917afe054b7b040c0e113b281d4ab9f9ce2 Mon Sep 17 00:00:00 2001 From: Paul Cuzner Date: Thu, 26 Nov 2020 12:52:47 +1300 Subject: [PATCH] cephadm: Handle exporter config in a dedicated class Several changes related to feedback during review - repositioned stdlib imports - added an exporterconfig class to handle config interactions - service prepare and create simplified due to new class Signed-off-by: Paul Cuzner --- .../mgr/cephadm/services/cephadmservice.py | 138 +++++++++++++----- 1 file changed, 103 insertions(+), 35 deletions(-) diff --git a/src/pybind/mgr/cephadm/services/cephadmservice.py b/src/pybind/mgr/cephadm/services/cephadmservice.py index 4007397743cbe..0bb1acfc61edc 100644 --- a/src/pybind/mgr/cephadm/services/cephadmservice.py +++ b/src/pybind/mgr/cephadm/services/cephadmservice.py @@ -1,7 +1,9 @@ import json import re import logging +import secrets import subprocess +import collections from abc import ABCMeta, abstractmethod from typing import TYPE_CHECKING, List, Callable, Any, TypeVar, Generic, \ Optional, Dict, Any, Tuple, NewType @@ -12,9 +14,7 @@ from ceph.deployment.service_spec import ServiceSpec, RGWSpec from ceph.deployment.utils import is_ipv6, unwrap_ipv6 from orchestrator import OrchestratorError, DaemonDescription from cephadm import utils -import secrets from mgr_util import create_self_signed_cert, ServerConfigException, verify_tls -import collections if TYPE_CHECKING: from cephadm.module import CephadmOrchestrator @@ -23,7 +23,6 @@ logger = logging.getLogger(__name__) ServiceSpecs = TypeVar('ServiceSpecs', bound=ServiceSpec) AuthEntity = NewType('AuthEntity', str) -ExporterConfig = collections.namedtuple('ExporterConfig', 'crt key token port') class CephadmDaemonSpec(Generic[ServiceSpecs]): @@ -775,35 +774,107 @@ class CrashService(CephService): return daemon_spec +class CephadmExporterConfig: + required_keys = ['crt', 'key', 'token', 'port'] + DEFAULT_PORT = '9443' + + def __init__(self, mgr, crt="", key="", token="", port=""): + # type: (CephadmOrchestrator, str, str, str, str) -> None + self.mgr = mgr + self.crt = crt + self.key = key + self.token = token + self.port = port + + @property + def ready(self) -> bool: + return all([self.crt, self.key, self.token, self.port]) + + def load_from_store(self) -> None: + cfg = self.mgr._get_exporter_config() + + assert isinstance(cfg, dict) + self.crt = cfg.get('crt', "") + self.key = cfg.get('key', "") + self.token = cfg.get('token', "") + self.port = cfg.get('port', "") + + def load_from_json(self, json_str: str) -> Tuple[int, str]: + try: + cfg = json.loads(json_str) + except ValueError: + return 1, "Invalid JSON provided - unable to load" + + if not all([k in cfg for k in CephadmExporterConfig.required_keys]): + return 1, "JSON file must contain crt, key, token and port" + + self.crt = cfg.get('crt') + self.key = cfg.get('key') + self.token = cfg.get('token') + self.port = cfg.get('port') + + return 0, "" + + def validate_config(self) -> Tuple[int, str]: + if not self.ready: + return 1, "Incomplete configuration. cephadm-exporter needs crt, key, token and port to be set" + + for check in [self._validate_tls, self._validate_token, self._validate_port]: + rc, reason = check() + if rc: + return 1, reason + + return 0, "" + + def _validate_tls(self) -> Tuple[int, str]: + + try: + verify_tls(self.crt, self.key) + except ServerConfigException as e: + return 1, str(e) + + return 0, "" + + def _validate_token(self) -> Tuple[int, str]: + if not isinstance(self.token, str): + return 1, "token must be a string" + if len(self.token) < 8: + return 1, "Token must be a string of at least 8 chars in length" + + return 0, "" + + def _validate_port(self) -> Tuple[int, str]: + try: + p = int(str(self.port)) + if p <= 1024: + raise ValueError + except ValueError: + return 1, "Port must be a integer (>1024)" + + return 0, "" + + class CephadmExporter(CephadmService): TYPE = 'cephadm-exporter' - def _fetch_config(self) -> ExporterConfig: - return ExporterConfig( - crt=self.mgr._get_exporter_option('crt'), - key=self.mgr._get_exporter_option('key'), - token=self.mgr._get_exporter_option('token'), - port=self.mgr._get_exporter_option('port')) - def prepare_create(self, daemon_spec: CephadmDaemonSpec) -> CephadmDaemonSpec: assert self.TYPE == daemon_spec.daemon_type - cfg = self._fetch_config() - if cfg.crt and cfg.key: - try: - verify_tls(cfg.crt, cfg.key) - except ServerConfigException: - raise OrchestratorError(f"Exporter's crt and key settings are invalid") - if not cfg.token: - raise OrchestratorError( - "Missing exporter token setting. Use 'cephadm generate-exporter-config' or 'cephadm set-exporter-token'") + cfg = CephadmExporterConfig(self.mgr) + cfg.load_from_store() + + if cfg.ready: + rc, reason = cfg.validate_config() + if rc: + raise OrchestratorError(reason) else: - logger.info("Using default TLS configuration for cephadm-exporter") - self.mgr._generate_exporter_config() - cfg = self._fetch_config() + logger.info( + "Incomplete/Missing configuration, applying defaults") + self.mgr._set_exporter_defaults() + cfg.load_from_store() if not daemon_spec.ports: - daemon_spec.ports = [cfg.port] + daemon_spec.ports = [int(cfg.port)] return daemon_spec @@ -812,20 +883,17 @@ class CephadmExporter(CephadmService): assert daemon_spec.spec deps: List[str] = [] - cfg = self._fetch_config() + cfg = CephadmExporterConfig(self.mgr) + cfg.load_from_store() - if cfg.crt and cfg.key: - try: - verify_tls(cfg.crt, cfg.key) - except ServerConfigException: - raise OrchestratorError(f"Exporter's crt and key settings are invalid") - if not cfg.token: - raise OrchestratorError( - "Missing exporter token setting. Use 'cephadm generate-exporter-config' or 'cephadm set-exporter-token'") + if cfg.ready: + rc, reason = cfg.validate_config() + if rc: + raise OrchestratorError(reason) else: - logger.info("Using default TLS configuration for cephadm-exporter") - self.mgr._generate_exporter_config() - cfg = self._fetch_config() + logger.info("Using default configuration for cephadm-exporter") + self.mgr._set_exporter_defaults() + cfg.load_from_store() config = { "crt": cfg.crt, -- 2.39.5