Merge pull request #36146 from rhcs-dashboard/wip-46577-master

mgr: add types to MODULE_OPTIONS

Reviewed-by: p-se <pseidensal@suse.com>
Reviewed-by: Tatjana Dehler <tdehler@suse.com>
Reviewed-by: sebastian-philipp <swagner@suse.com>
Reviewed-by: Kefu Chai <kchai@redhat.com>
Reviewed-by: Volker Theile <vtheile@suse.com>
This commit is contained in:
Ernesto Puerta 2020-12-14 17:35:52 +01:00 committed by GitHub
commit 0ef9ad55b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 303 additions and 280 deletions

View File

@ -29,7 +29,7 @@ from ceph.deployment.service_spec import \
from cephadm.serve import CephadmServe
from cephadm.services.cephadmservice import CephadmDaemonSpec
from mgr_module import MgrModule, HandleCommandResult
from mgr_module import MgrModule, HandleCommandResult, Option
from mgr_util import create_self_signed_cert, verify_tls, ServerConfigException
import secrets
import orchestrator
@ -137,161 +137,161 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule,
instance = None
NATIVE_OPTIONS = [] # type: List[Any]
MODULE_OPTIONS: List[dict] = [
{
'name': 'ssh_config_file',
'type': 'str',
'default': None,
'desc': 'customized SSH config file to connect to managed hosts',
},
{
'name': 'device_cache_timeout',
'type': 'secs',
'default': 30 * 60,
'desc': 'seconds to cache device inventory',
},
{
'name': 'daemon_cache_timeout',
'type': 'secs',
'default': 10 * 60,
'desc': 'seconds to cache service (daemon) inventory',
},
{
'name': 'facts_cache_timeout',
'type': 'secs',
'default': 1 * 60,
'desc': 'seconds to cache host facts data',
},
{
'name': 'host_check_interval',
'type': 'secs',
'default': 10 * 60,
'desc': 'how frequently to perform a host check',
},
{
'name': 'mode',
'type': 'str',
'enum_allowed': ['root', 'cephadm-package'],
'default': 'root',
'desc': 'mode for remote execution of cephadm',
},
{
'name': 'container_image_base',
'default': 'docker.io/ceph/ceph',
'desc': 'Container image name, without the tag',
'runtime': True,
},
{
'name': 'container_image_prometheus',
'default': 'docker.io/prom/prometheus:v2.18.1',
'desc': 'Prometheus container image',
},
{
'name': 'container_image_grafana',
'default': 'docker.io/ceph/ceph-grafana:6.6.2',
'desc': 'Prometheus container image',
},
{
'name': 'container_image_alertmanager',
'default': 'docker.io/prom/alertmanager:v0.20.0',
'desc': 'Prometheus container image',
},
{
'name': 'container_image_node_exporter',
'default': 'docker.io/prom/node-exporter:v0.18.1',
'desc': 'Prometheus container image',
},
{
'name': 'warn_on_stray_hosts',
'type': 'bool',
'default': True,
'desc': 'raise a health warning if daemons are detected on a host '
'that is not managed by cephadm',
},
{
'name': 'warn_on_stray_daemons',
'type': 'bool',
'default': True,
'desc': 'raise a health warning if daemons are detected '
'that are not managed by cephadm',
},
{
'name': 'warn_on_failed_host_check',
'type': 'bool',
'default': True,
'desc': 'raise a health warning if the host check fails',
},
{
'name': 'log_to_cluster',
'type': 'bool',
'default': True,
'desc': 'log to the "cephadm" cluster log channel"',
},
{
'name': 'allow_ptrace',
'type': 'bool',
'default': False,
'desc': 'allow SYS_PTRACE capability on ceph containers',
'long_desc': 'The SYS_PTRACE capability is needed to attach to a '
'process with gdb or strace. Enabling this options '
'can allow debugging daemons that encounter problems '
'at runtime.',
},
{
'name': 'container_init',
'type': 'bool',
'default': False,
'desc': 'Run podman/docker with `--init`',
},
{
'name': 'prometheus_alerts_path',
'type': 'str',
'default': '/etc/prometheus/ceph/ceph_default_alerts.yml',
'desc': 'location of alerts to include in prometheus deployments',
},
{
'name': 'migration_current',
'type': 'int',
'default': None,
'desc': 'internal - do not modify',
MODULE_OPTIONS = [
Option(
'ssh_config_file',
type='str',
default=None,
desc='customized SSH config file to connect to managed hosts',
),
Option(
'device_cache_timeout',
type='secs',
default=30 * 60,
desc='seconds to cache device inventory',
),
Option(
'daemon_cache_timeout',
type='secs',
default=10 * 60,
desc='seconds to cache service (daemon) inventory',
),
Option(
'facts_cache_timeout',
type='secs',
default=1 * 60,
desc='seconds to cache host facts data',
),
Option(
'host_check_interval',
type='secs',
default=10 * 60,
desc='how frequently to perform a host check',
),
Option(
'mode',
type='str',
enum_allowed=['root', 'cephadm-package'],
default='root',
desc='mode for remote execution of cephadm',
),
Option(
'container_image_base',
default='docker.io/ceph/ceph',
desc='Container image name, without the tag',
runtime=True,
),
Option(
'container_image_prometheus',
default='docker.io/prom/prometheus:v2.18.1',
desc='Prometheus container image',
),
Option(
'container_image_grafana',
default='docker.io/ceph/ceph-grafana:6.6.2',
desc='Prometheus container image',
),
Option(
'container_image_alertmanager',
default='docker.io/prom/alertmanager:v0.20.0',
desc='Prometheus container image',
),
Option(
'container_image_node_exporter',
default='docker.io/prom/node-exporter:v0.18.1',
desc='Prometheus container image',
),
Option(
'warn_on_stray_hosts',
type='bool',
default=True,
desc='raise a health warning if daemons are detected on a host '
'that is not managed by cephadm',
),
Option(
'warn_on_stray_daemons',
type='bool',
default=True,
desc='raise a health warning if daemons are detected '
'that are not managed by cephadm',
),
Option(
'warn_on_failed_host_check',
type='bool',
default=True,
desc='raise a health warning if the host check fails',
),
Option(
'log_to_cluster',
type='bool',
default=True,
desc='log to the "cephadm" cluster log channel"',
),
Option(
'allow_ptrace',
type='bool',
default=False,
desc='allow SYS_PTRACE capability on ceph containers',
long_desc='The SYS_PTRACE capability is needed to attach to a '
'process with gdb or strace. Enabling this options '
'can allow debugging daemons that encounter problems '
'at runtime.',
),
Option(
'container_init',
type='bool',
default=False,
desc='Run podman/docker with `--init`'
),
Option(
'prometheus_alerts_path',
type='str',
default='/etc/prometheus/ceph/ceph_default_alerts.yml',
desc='location of alerts to include in prometheus deployments',
),
Option(
'migration_current',
type='int',
default=None,
desc='internal - do not modify',
# used to track track spec and other data migrations.
},
{
'name': 'config_dashboard',
'type': 'bool',
'default': True,
'desc': 'manage configs like API endpoints in Dashboard.'
},
{
'name': 'manage_etc_ceph_ceph_conf',
'type': 'bool',
'default': False,
'desc': 'Manage and own /etc/ceph/ceph.conf on the hosts.',
},
{
'name': 'registry_url',
'type': 'str',
'default': None,
'desc': 'Custom repository url'
},
{
'name': 'registry_username',
'type': 'str',
'default': None,
'desc': 'Custom repository username'
},
{
'name': 'registry_password',
'type': 'str',
'default': None,
'desc': 'Custom repository password'
},
{
'name': 'use_repo_digest',
'type': 'bool',
'default': False,
'desc': 'Automatically convert image tags to image digest. Make sure all daemons use the same image',
}
),
Option(
'config_dashboard',
type='bool',
default=True,
desc='manage configs like API endpoints in Dashboard.'
),
Option(
'manage_etc_ceph_ceph_conf',
type='bool',
default=False,
desc='Manage and own /etc/ceph/ceph.conf on the hosts.',
),
Option(
'registry_url',
type='str',
default=None,
desc='Custom repository url'
),
Option(
'registry_username',
type='str',
default=None,
desc='Custom repository username'
),
Option(
'registry_password',
type='str',
default=None,
desc='Custom repository password'
),
Option(
'use_repo_digest',
type='bool',
default=False,
desc='Automatically convert image tags to image digest. Make sure all daemons use the same image',
),
]
def __init__(self, *args: Any, **kwargs: Any):

View File

@ -1,10 +1,15 @@
import ceph_module # noqa
try:
from typing import Set, Tuple, Iterator, Any, Dict, Optional, Callable, List
except ImportError:
# just for type checking
pass
from typing import Set, Tuple, Iterator, Any, Dict, Optional, Callable, List, \
Union, TYPE_CHECKING, NamedTuple
if TYPE_CHECKING:
import sys
if sys.version_info >= (3, 8):
from typing import Literal
else:
from typing_extensions import Literal
import logging
import errno
import json
@ -354,27 +359,37 @@ def CLIWriteCommand(prefix, args="", desc=""):
def _get_localized_key(prefix, key):
return '{}/{}'.format(prefix, key)
"""
MODULE_OPTIONS types and Option Class
"""
if TYPE_CHECKING:
OptionTypeLabel = Literal[
'uint', 'int', 'str', 'float', 'bool', 'addr', 'addrvec', 'uuid', 'size', 'secs']
class Option(dict):
# common/options.h: value_t
OptionValue = Optional[Union[bool, int, float, str]]
class Option(Dict):
"""
Helper class to declare options for MODULE_OPTIONS list.
Caveat: it uses argument names matching Python keywords (type, min, max),
so any further processing should happen in a separate method.
TODO: type validation.
TODO: Replace with typing.TypedDict when in python_version >= 3.8
"""
def __init__(
self, name,
default=None,
type='str',
desc=None, longdesc=None,
min=None, max=None,
enum_allowed=None,
see_also=None,
tags=None,
runtime=False,
self,
name: str,
default: OptionValue=None,
type: 'OptionTypeLabel'='str',
desc: Optional[str]=None,
long_desc: Optional[str]=None,
min: OptionValue=None,
max: OptionValue=None,
enum_allowed: Optional[List[str]]=None,
tags: Optional[List[str]]=None,
see_also: Optional[List[str]]=None,
runtime: bool=False,
):
super(Option, self).__init__(
(k, v) for k, v in vars().items()
@ -600,7 +615,7 @@ class MgrStandbyModule(ceph_module.BaseMgrStandbyModule, MgrModuleLoggingMixin):
from their active peer), and to configuration settings (read only).
"""
MODULE_OPTIONS = [] # type: List[Dict[str, Any]]
MODULE_OPTIONS: List[Option] = []
MODULE_OPTION_DEFAULTS = {} # type: Dict[str, Any]
def __init__(self, module_name, capsule):
@ -663,13 +678,11 @@ class MgrStandbyModule(ceph_module.BaseMgrStandbyModule, MgrModuleLoggingMixin):
def get_mgr_id(self):
return self._ceph_get_mgr_id()
def get_module_option(self, key, default=None):
def get_module_option(self, key: str, default: OptionValue=None) -> OptionValue:
"""
Retrieve the value of a persistent configuration setting
:param str key:
:param default: the default value of the config if it is not found
:return: str
"""
r = self._ceph_get_module_option(key)
if r is None:
@ -692,7 +705,7 @@ class MgrStandbyModule(ceph_module.BaseMgrStandbyModule, MgrModuleLoggingMixin):
def get_active_uri(self):
return self._ceph_get_active_uri()
def get_localized_module_option(self, key, default=None):
def get_localized_module_option(self, key: str, default: OptionValue=None) -> OptionValue:
r = self._ceph_get_module_option(key, self.get_mgr_id())
if r is None:
return self.MODULE_OPTION_DEFAULTS.get(key, default)
@ -702,7 +715,7 @@ class MgrStandbyModule(ceph_module.BaseMgrStandbyModule, MgrModuleLoggingMixin):
class MgrModule(ceph_module.BaseMgrModule, MgrModuleLoggingMixin):
COMMANDS = [] # type: List[Any]
MODULE_OPTIONS = [] # type: List[dict]
MODULE_OPTIONS: List[Option] = []
MODULE_OPTION_DEFAULTS = {} # type: Dict[str, Any]
# Priority definitions for perf counters
@ -1248,28 +1261,23 @@ class MgrModule(ceph_module.BaseMgrModule, MgrModuleLoggingMixin):
else:
return r
def get_module_option(self, key, default=None):
def get_module_option(self, key: str, default: OptionValue=None) -> OptionValue:
"""
Retrieve the value of a persistent configuration setting
:param str key:
:param str default:
:return: str
"""
self._validate_module_option(key)
return self._get_module_option(key, default)
def get_module_option_ex(self, module, key, default=None):
def get_module_option_ex(self, module: str, key: str, default: OptionValue=None) -> OptionValue:
"""
Retrieve the value of a persistent configuration setting
for the specified module.
:param str module: The name of the module, e.g. 'dashboard'
:param module: The name of the module, e.g. 'dashboard'
or 'telemetry'.
:param str key: The configuration key, e.g. 'server_addr'.
:param str,None default: The default value to use when the
:param key: The configuration key, e.g. 'server_addr'.
:param default: The default value to use when the
returned value is ``None``. Defaults to ``None``.
:return: str,int,bool,float,None
"""
if module == self.module_name:
self._validate_module_option(key)
@ -1289,12 +1297,9 @@ class MgrModule(ceph_module.BaseMgrModule, MgrModuleLoggingMixin):
def _set_localized(self, key, val, setter):
return setter(_get_localized_key(self.get_mgr_id(), key), val)
def get_localized_module_option(self, key, default=None):
def get_localized_module_option(self, key: str, default: OptionValue=None) -> OptionValue:
"""
Retrieve localized configuration for this ceph-mgr instance
:param str key:
:param str default:
:return: str
"""
self._validate_module_option(key)
return self._get_module_option(key, default, self.get_mgr_id())

View File

@ -13,7 +13,7 @@ from ceph.deployment.drive_group import DriveGroupSpec, DeviceSelection
from ceph.deployment.service_spec import PlacementSpec, ServiceSpec
from mgr_util import format_bytes, to_pretty_timedelta, format_dimless
from mgr_module import MgrModule, HandleCommandResult
from mgr_module import MgrModule, HandleCommandResult, Option
from ._interface import OrchestratorClientMixin, DeviceLightLoc, _cli_read_command, \
raise_if_exception, _cli_write_command, TrivialReadCompletion, OrchestratorError, \
@ -135,15 +135,14 @@ def preview_table_services(data):
class OrchestratorCli(OrchestratorClientMixin, MgrModule,
metaclass=CLICommandMeta):
MODULE_OPTIONS = [
{
'name': 'orchestrator',
'type': 'str',
'default': None,
'desc': 'Orchestrator backend',
'enum_allowed': ['cephadm', 'rook',
'test_orchestrator'],
'runtime': True,
},
Option(
'orchestrator',
type='str',
default=None,
desc='Orchestrator backend',
enum_allowed=['cephadm', 'rook', 'test_orchestrator'],
runtime=True,
)
]
NATIVE_OPTIONS = [] # type: List[dict]
@ -633,38 +632,38 @@ class OrchestratorCli(OrchestratorClientMixin, MgrModule,
usage:
ceph orch apply osd -i <json_file/yaml_file> [--dry-run]
ceph orch apply osd --all-available-devices [--dry-run] [--unmanaged]
Restrictions:
Mutexes:
* -i, --all-available-devices
* -i, --unmanaged (this would overwrite the osdspec loaded from a file)
Parameters:
* --unmanaged
Only works with --all-available-devices.
Description:
* -i
An inbuf object like a file or a json/yaml blob containing a valid OSDSpec
* --all-available-devices
The most simple OSDSpec there is. Takes all as 'available' marked devices
and creates standalone OSDs on them.
* --unmanaged
Set a the unmanaged flag for all--available-devices (default is False)
Examples:
# ceph orch apply osd -i <file.yml|json>
Applies one or more OSDSpecs found in <file>
# ceph orch osd apply --all-available-devices --unmanaged=true
Creates and applies simple OSDSpec with the unmanaged flag set to <true>
"""

View File

@ -4,7 +4,7 @@ try:
except ImportError:
TYPE_CHECKING = False
from mgr_module import MgrModule, OSDMap
from mgr_module import MgrModule, OSDMap, Option
from mgr_util import to_pretty_timedelta
from datetime import timedelta
import os
@ -54,7 +54,7 @@ class Event(object):
def refs(self):
# type: () -> List[str]
return self._refs
@property
def add_to_ceph_s(self):
# type: () -> bool
@ -427,27 +427,26 @@ class Module(MgrModule):
]
MODULE_OPTIONS = [
{
'name': 'max_completed_events',
'default': 50,
'type': 'int',
'desc': 'number of past completed events to remember',
'runtime': True,
},
{
'name': 'persist_interval',
'default': 5,
'type': 'secs',
'desc': 'how frequently to persist completed events',
'runtime': True,
},
{
'name': 'enabled',
'default': True,
'type': 'bool',
}
] # type: List[Dict[str, Any]]
Option(
'max_completed_events',
default=50,
type='int',
desc='number of past completed events to remember',
runtime=True
),
Option(
'persist_interval',
default=5,
type='secs',
desc='how frequently to persist completed events',
runtime=True
),
Option(
'enabled',
default=True,
type='bool',
)
]
def __init__(self, *args, **kwargs):
super(Module, self).__init__(*args, **kwargs)
@ -632,17 +631,17 @@ class Module(MgrModule):
))
self._osdmap_changed(old_osdmap, self._latest_osdmap)
elif notify_type == "pg_summary":
# if there are no events we will skip this here to avoid
# if there are no events we will skip this here to avoid
# expensive get calls
if len(self._events) == 0:
return
global_event = False
data = self.get("pg_stats")
ready = self.get("pg_ready")
for ev_id in list(self._events):
ev = self._events[ev_id]
# Check for types of events
# Check for types of events
# we have to update
if isinstance(ev, PgRecoveryEvent):
ev.pg_update(data, ready, self.log)
@ -653,7 +652,7 @@ class Module(MgrModule):
self.maybe_complete(ev)
if not global_event:
# If there is no global event
# If there is no global event
# we create one
self._pg_state_changed(data)
@ -848,7 +847,7 @@ class Module(MgrModule):
self.clear_all_progress_events()
def _handle_clear(self):
self.clear()
self.clear()
return 0, "", ""
def handle_command(self, _, cmd):

View File

@ -9,12 +9,12 @@ import re
import socket
import threading
import time
from mgr_module import MgrModule, MgrStandbyModule, PG_STATES
from mgr_module import MgrModule, MgrStandbyModule, PG_STATES, Option
from mgr_util import get_default_addr, profile_method
from rbd import RBD
from collections import namedtuple
try:
from typing import DefaultDict, Optional, Dict, Any, Set
from typing import DefaultDict, Optional, Dict, Any, Set, cast
except ImportError:
pass
@ -247,12 +247,31 @@ class Module(MgrModule):
]
MODULE_OPTIONS = [
{'name': 'server_addr'},
{'name': 'server_port'},
{'name': 'scrape_interval'},
{'name': 'stale_cache_strategy'},
{'name': 'rbd_stats_pools'},
{'name': 'rbd_stats_pools_refresh_interval', 'type': 'int', 'default': 300},
Option(
'server_addr'
),
Option(
'server_port',
type='int'
),
Option(
'scrape_interval',
type='float',
default=15.0
),
Option(
'stale_cache_strategy',
default='log'
),
Option(
'rbd_stats_pools',
default=''
),
Option(
name='rbd_stats_pools_refresh_interval',
type='int',
default=300
)
]
STALE_CACHE_FAIL = 'fail'
@ -264,8 +283,8 @@ class Module(MgrModule):
self.shutdown_event = threading.Event()
self.collect_lock = threading.Lock()
self.collect_time = 0.0
self.scrape_interval = 15.0
self.stale_cache_strategy = self.STALE_CACHE_FAIL
self.scrape_interval: float = 15.0
self.stale_cache_strategy: str = self.STALE_CACHE_FAIL
self.collect_cache = None
self.rbd_stats = {
'pools': {},
@ -825,7 +844,7 @@ class Module(MgrModule):
# list of pool[/namespace] entries. If no namespace is specifed the
# stats are collected for every namespace in the pool. The wildcard
# '*' can be used to indicate all pools or namespaces
pools_string = self.get_localized_module_option('rbd_stats_pools', '')
pools_string = cast(str, self.get_localized_module_option('rbd_stats_pools'))
pool_keys = []
for x in re.split('[\s,]+', pools_string):
if not x:
@ -1249,9 +1268,9 @@ class Module(MgrModule):
raise cherrypy.HTTPError(503, msg)
# Make the cache timeout for collecting configurable
self.scrape_interval = float(self.get_localized_module_option('scrape_interval', 15.0))
self.scrape_interval = cast(float, self.get_localized_module_option('scrape_interval'))
self.stale_cache_strategy = self.get_localized_module_option('stale_cache_strategy', 'log')
self.stale_cache_strategy = cast(str, self.get_localized_module_option('stale_cache_strategy'))
if self.stale_cache_strategy not in [self.STALE_CACHE_FAIL,
self.STALE_CACHE_RETURN]:
self.stale_cache_strategy = self.STALE_CACHE_FAIL
@ -1276,7 +1295,7 @@ class Module(MgrModule):
cherrypy.config.update({
'server.socket_host': server_addr,
'server.socket_port': int(server_port),
'server.socket_port': server_port,
'engine.autoreload.on': False
})
cherrypy.tree.mount(Root(), "/")
@ -1313,7 +1332,7 @@ class StandbyModule(MgrStandbyModule):
(server_addr, server_port))
cherrypy.config.update({
'server.socket_host': server_addr,
'server.socket_port': int(server_port),
'server.socket_port': server_port,
'engine.autoreload.on': False
})

View File

@ -30,7 +30,7 @@ except ImportError:
client = None
config = None
from mgr_module import MgrModule
from mgr_module import MgrModule, Option
import orchestrator
from .rook_cluster import RookCluster
@ -98,9 +98,9 @@ class RookOrchestrator(MgrModule, orchestrator.Orchestrator):
Right now, we are calling the k8s API synchronously.
"""
MODULE_OPTIONS = [
MODULE_OPTIONS: List[Option] = [
# TODO: configure k8s API addr instead of assuming local
] # type: List[Dict[str, Any]]
]
def process(self, completions):
# type: (List[RookCompletion]) -> None

View File

@ -7,20 +7,20 @@ import errno
import json
import sqlite3
from .fs.schedule_client import SnapSchedClient
from mgr_module import MgrModule, CLIReadCommand, CLIWriteCommand
from mgr_module import MgrModule, CLIReadCommand, CLIWriteCommand, Option
from mgr_util import CephfsConnectionException
from threading import Event
class Module(MgrModule):
MODULE_OPTIONS = [
{
'name': 'allow_m_granularity',
'type': 'bool',
'default': False,
'desc': 'allow minute scheduled snapshots',
'runtime': True,
},
Option(
'allow_m_granularity',
type='bool',
default=False,
desc='allow minute scheduled snapshots',
runtime=True,
),
]
def __init__(self, *args, **kwargs):

View File

@ -5,7 +5,7 @@ performance stats for ceph filesystem (for now...)
import json
from typing import List, Dict
from mgr_module import MgrModule
from mgr_module import MgrModule, Option
from .fs.perf_stats import FSPerfStats
@ -20,7 +20,7 @@ class Module(MgrModule):
"perm": "r"
},
]
MODULE_OPTIONS = [] # type: List[Dict]
MODULE_OPTIONS: List[Option] = []
def __init__(self, *args, **kwargs):
super(Module, self).__init__(*args, **kwargs)

View File

@ -4,7 +4,7 @@ import logging
import traceback
import threading
from mgr_module import MgrModule
from mgr_module import MgrModule, Option
import orchestrator
from .fs.volume import VolumeClient
@ -396,12 +396,12 @@ class Module(orchestrator.OrchestratorClientMixin, MgrModule):
]
MODULE_OPTIONS = [
{
'name': 'max_concurrent_clones',
'type': 'int',
'default': 4,
'desc': 'Number of asynchronous cloner threads',
}
Option(
'max_concurrent_clones',
type='int',
default=4,
desc='Number of asynchronous cloner threads',
)
]
def __init__(self, *args, **kwargs):

View File

@ -4,3 +4,4 @@ mypy==0.790; python_version >= '3'
pytest-mypy; python_version >= '3'
pytest >= 2.1.3; python_version >= '3'
pyyaml
typing-extensions; python_version < '3.8'