summaryrefslogtreecommitdiffstats
path: root/src/cephadm
diff options
context:
space:
mode:
Diffstat (limited to 'src/cephadm')
-rwxr-xr-xsrc/cephadm/cephadm.py42
-rw-r--r--src/cephadm/cephadmlib/container_types.py16
-rw-r--r--src/cephadm/cephadmlib/daemons/ingress.py9
-rw-r--r--src/cephadm/cephadmlib/daemons/mgmt_gateway.py6
-rw-r--r--src/cephadm/cephadmlib/daemons/monitoring.py49
-rw-r--r--src/cephadm/cephadmlib/daemons/nvmeof.py7
-rw-r--r--src/cephadm/cephadmlib/daemons/oauth2_proxy.py6
-rw-r--r--src/cephadm/cephadmlib/daemons/smb.py4
-rw-r--r--src/cephadm/cephadmlib/daemons/snmp.py4
-rw-r--r--src/cephadm/cephadmlib/daemons/tracing.py15
-rw-r--r--src/cephadm/cephadmlib/file_utils.py24
-rw-r--r--src/cephadm/cephadmlib/net_utils.py14
-rw-r--r--src/cephadm/tests/test_deploy.py2
13 files changed, 129 insertions, 69 deletions
diff --git a/src/cephadm/cephadm.py b/src/cephadm/cephadm.py
index d2ddf564116..a8616980e4d 100755
--- a/src/cephadm/cephadm.py
+++ b/src/cephadm/cephadm.py
@@ -111,6 +111,7 @@ from cephadmlib.file_utils import (
unlink_file,
write_new,
write_tmp,
+ update_meta_file,
)
from cephadmlib.net_utils import (
build_addrv_params,
@@ -3453,6 +3454,7 @@ def list_daemons(
detail: bool = True,
legacy_dir: Optional[str] = None,
daemon_name: Optional[str] = None,
+ type_of_daemon: Optional[str] = None,
) -> List[Dict[str, str]]:
host_version: Optional[str] = None
ls = []
@@ -3489,6 +3491,8 @@ def list_daemons(
if os.path.exists(data_dir):
for i in os.listdir(data_dir):
if i in ['mon', 'osd', 'mds', 'mgr', 'rgw']:
+ if type_of_daemon and type_of_daemon != i:
+ continue
daemon_type = i
for j in os.listdir(os.path.join(data_dir, i)):
if '-' not in j:
@@ -3525,6 +3529,8 @@ def list_daemons(
if daemon_name and name != daemon_name:
continue
(daemon_type, daemon_id) = j.split('.', 1)
+ if type_of_daemon and type_of_daemon != daemon_type:
+ continue
unit_name = get_unit_name(fsid,
daemon_type,
daemon_id)
@@ -4705,6 +4711,34 @@ def command_list_images(ctx: CephadmContext) -> None:
# print default images
cp_obj.write(sys.stdout)
+
+def update_service_for_daemon(ctx: CephadmContext,
+ available_daemons: list,
+ update_daemons: list) -> None:
+ """ Update the unit.meta file of daemon with required service name for valid daemons"""
+
+ data = {'service_name': ctx.service_name}
+ # check if all the daemon names are valid
+ if not set(update_daemons).issubset(set(available_daemons)):
+ raise Error(f'Error EINVAL: one or more daemons of {update_daemons} does not exist on this host')
+ for name in update_daemons:
+ path = os.path.join(ctx.data_dir, ctx.fsid, name, 'unit.meta')
+ update_meta_file(path, data)
+ print(f'Successfully updated daemon {name} with service {ctx.service_name}')
+
+
+@infer_fsid
+def command_update_osd_service(ctx: CephadmContext) -> int:
+ """update service for provided daemon"""
+ update_daemons = [f'osd.{osd_id}' for osd_id in ctx.osd_ids.split(',')]
+ daemons = list_daemons(ctx, detail=False, type_of_daemon='osd')
+ if not daemons:
+ raise Error(f'Daemon {ctx.osd_ids} does not exists on this host')
+ available_daemons = [d['name'] for d in daemons]
+ update_service_for_daemon(ctx, available_daemons, update_daemons)
+ return 0
+
+
##################################
@@ -5571,6 +5605,14 @@ def _get_parser():
parser_list_images = subparsers.add_parser(
'list-images', help='list all the default images')
parser_list_images.set_defaults(func=command_list_images)
+
+ parser_update_service = subparsers.add_parser(
+ 'update-osd-service', help='update service for provided daemon')
+ parser_update_service.set_defaults(func=command_update_osd_service)
+ parser_update_service.add_argument('--fsid', help='cluster FSID')
+ parser_update_service.add_argument('--osd-ids', required=True, help='Comma-separated OSD IDs')
+ parser_update_service.add_argument('--service-name', required=True, help='OSD service name')
+
return parser
diff --git a/src/cephadm/cephadmlib/container_types.py b/src/cephadm/cephadmlib/container_types.py
index f1e829cbdf7..52a68888e78 100644
--- a/src/cephadm/cephadmlib/container_types.py
+++ b/src/cephadm/cephadmlib/container_types.py
@@ -9,7 +9,7 @@ from typing import Dict, List, Optional, Any, Union, Tuple, Iterable, cast
from .call_wrappers import call, call_throws, CallVerbosity
from .constants import DEFAULT_TIMEOUT
-import ceph.cephadm.images as default_images
+from ceph.cephadm.images import DefaultImages
from .container_engines import Docker, Podman
from .context import CephadmContext
from .daemon_identity import DaemonIdentity, DaemonSubIdentity
@@ -665,14 +665,8 @@ def enable_shared_namespaces(
def get_mgr_images() -> dict:
"""Return dict of default mgr images"""
- mgr_prefix = 'mgr/cephadm/container_image_'
- mgr_images = {}
- images = vars(default_images)
- for key, value in images.items():
- if key.startswith('DEFAULT_') and key.endswith('_IMAGE'):
- # flake8 and black disagree about spaces around ":" hence the noqa comment
- suffix = key[
- len('DEFAULT_') : -len('_IMAGE') # noqa: E203
- ].lower()
- mgr_images[mgr_prefix + suffix] = value
+ mgr_prefix = 'mgr/cephadm/'
+ mgr_images = {
+ f'{mgr_prefix}{image.key}': image.image_ref for image in DefaultImages
+ }
return mgr_images
diff --git a/src/cephadm/cephadmlib/daemons/ingress.py b/src/cephadm/cephadmlib/daemons/ingress.py
index c88e39ac025..645654b59c8 100644
--- a/src/cephadm/cephadmlib/daemons/ingress.py
+++ b/src/cephadm/cephadmlib/daemons/ingress.py
@@ -2,10 +2,7 @@ import os
from typing import Dict, List, Optional, Tuple, Union
-from ceph.cephadm.images import (
- DEFAULT_HAPROXY_IMAGE,
- DEFAULT_KEEPALIVED_IMAGE,
-)
+from ceph.cephadm.images import DefaultImages
from ..constants import (
DATA_DIR_MODE,
)
@@ -27,7 +24,7 @@ class HAproxy(ContainerDaemonForm):
daemon_type = 'haproxy'
required_files = ['haproxy.cfg']
- default_image = DEFAULT_HAPROXY_IMAGE
+ default_image = DefaultImages.HAPROXY.image_ref
@classmethod
def for_daemon_type(cls, daemon_type: str) -> bool:
@@ -155,7 +152,7 @@ class Keepalived(ContainerDaemonForm):
daemon_type = 'keepalived'
required_files = ['keepalived.conf']
- default_image = DEFAULT_KEEPALIVED_IMAGE
+ default_image = DefaultImages.KEEPALIVED.image_ref
@classmethod
def for_daemon_type(cls, daemon_type: str) -> bool:
diff --git a/src/cephadm/cephadmlib/daemons/mgmt_gateway.py b/src/cephadm/cephadmlib/daemons/mgmt_gateway.py
index 85f72495909..2be18809aa3 100644
--- a/src/cephadm/cephadmlib/daemons/mgmt_gateway.py
+++ b/src/cephadm/cephadmlib/daemons/mgmt_gateway.py
@@ -11,7 +11,7 @@ from ..context_getters import fetch_configs
from ..daemon_form import register as register_daemon_form
from ..daemon_identity import DaemonIdentity
from ..deployment_utils import to_deployment_container
-from ceph.cephadm.images import DEFAULT_NGINX_IMAGE
+from ceph.cephadm.images import DefaultImages
from ..data_utils import dict_get, is_fsid
from ..file_utils import populate_files, makedirs, recursive_chown
from ..exceptions import Error
@@ -32,7 +32,7 @@ class MgmtGateway(ContainerDaemonForm):
'nginx_internal.key',
]
- default_image = DEFAULT_NGINX_IMAGE
+ default_image = DefaultImages.NGINX.image_ref
@classmethod
def for_daemon_type(cls, daemon_type: str) -> bool:
@@ -44,7 +44,7 @@ class MgmtGateway(ContainerDaemonForm):
fsid: str,
daemon_id: str,
config_json: Dict,
- image: str = DEFAULT_NGINX_IMAGE,
+ image: str = DefaultImages.NGINX.image_ref,
):
self.ctx = ctx
self.fsid = fsid
diff --git a/src/cephadm/cephadmlib/daemons/monitoring.py b/src/cephadm/cephadmlib/daemons/monitoring.py
index 710093f0f46..4ba00daaefb 100644
--- a/src/cephadm/cephadmlib/daemons/monitoring.py
+++ b/src/cephadm/cephadmlib/daemons/monitoring.py
@@ -3,14 +3,7 @@ import os
from typing import Dict, List, Tuple
from ..call_wrappers import call, CallVerbosity
-from ceph.cephadm.images import (
- DEFAULT_ALERTMANAGER_IMAGE,
- DEFAULT_GRAFANA_IMAGE,
- DEFAULT_LOKI_IMAGE,
- DEFAULT_NODE_EXPORTER_IMAGE,
- DEFAULT_PROMETHEUS_IMAGE,
- DEFAULT_PROMTAIL_IMAGE,
-)
+from ceph.cephadm.images import DefaultImages
from ..constants import (
UID_NOBODY,
GID_NOGROUP,
@@ -23,7 +16,13 @@ from ..daemon_form import register as register_daemon_form
from ..daemon_identity import DaemonIdentity
from ..deployment_utils import to_deployment_container
from ..exceptions import Error
-from ..net_utils import get_fqdn, get_hostname, get_ip_addresses, wrap_ipv6
+from ..net_utils import (
+ get_fqdn,
+ get_hostname,
+ get_ip_addresses,
+ wrap_ipv6,
+ EndPoint,
+)
@register_daemon_form
@@ -43,7 +42,7 @@ class Monitoring(ContainerDaemonForm):
components = {
'prometheus': {
- 'image': DEFAULT_PROMETHEUS_IMAGE,
+ 'image': DefaultImages.PROMETHEUS.image_ref,
'cpus': '2',
'memory': '4GB',
'args': [
@@ -55,7 +54,7 @@ class Monitoring(ContainerDaemonForm):
],
},
'loki': {
- 'image': DEFAULT_LOKI_IMAGE,
+ 'image': DefaultImages.LOKI.image_ref,
'cpus': '1',
'memory': '1GB',
'args': [
@@ -64,7 +63,7 @@ class Monitoring(ContainerDaemonForm):
'config-json-files': ['loki.yml'],
},
'promtail': {
- 'image': DEFAULT_PROMTAIL_IMAGE,
+ 'image': DefaultImages.PROMTAIL.image_ref,
'cpus': '1',
'memory': '1GB',
'args': [
@@ -75,13 +74,13 @@ class Monitoring(ContainerDaemonForm):
],
},
'node-exporter': {
- 'image': DEFAULT_NODE_EXPORTER_IMAGE,
+ 'image': DefaultImages.NODE_EXPORTER.image_ref,
'cpus': '1',
'memory': '1GB',
'args': ['--no-collector.timex'],
},
'grafana': {
- 'image': DEFAULT_GRAFANA_IMAGE,
+ 'image': DefaultImages.GRAFANA.image_ref,
'cpus': '2',
'memory': '4GB',
'args': [],
@@ -93,14 +92,9 @@ class Monitoring(ContainerDaemonForm):
],
},
'alertmanager': {
- 'image': DEFAULT_ALERTMANAGER_IMAGE,
+ 'image': DefaultImages.ALERTMANAGER.image_ref,
'cpus': '2',
'memory': '2GB',
- 'args': [
- '--cluster.listen-address=:{}'.format(
- port_map['alertmanager'][1]
- ),
- ],
'config-json-files': [
'alertmanager.yml',
],
@@ -255,11 +249,14 @@ class Monitoring(ContainerDaemonForm):
ip = meta['ip']
if 'ports' in meta and meta['ports']:
port = meta['ports'][0]
- if daemon_type == 'prometheus':
- config = fetch_configs(ctx)
+ config = fetch_configs(ctx)
+ if daemon_type in ['prometheus', 'alertmanager']:
ip_to_bind_to = config.get('ip_to_bind_to', '')
if ip_to_bind_to:
ip = ip_to_bind_to
+ web_listen_addr = str(EndPoint(ip, port))
+ r += [f'--web.listen-address={web_listen_addr}']
+ if daemon_type == 'prometheus':
retention_time = config.get('retention_time', '15d')
retention_size = config.get(
'retention_size', '0'
@@ -283,9 +280,11 @@ class Monitoring(ContainerDaemonForm):
r += ['--web.route-prefix=/prometheus/']
else:
r += [f'--web.external-url={scheme}://{host}:{port}']
- r += [f'--web.listen-address={ip}:{port}']
if daemon_type == 'alertmanager':
- config = fetch_configs(ctx)
+ clus_listen_addr = str(
+ EndPoint(ip, self.port_map[daemon_type][1])
+ )
+ r += [f'--cluster.listen-address={clus_listen_addr}']
use_url_prefix = config.get('use_url_prefix', False)
peers = config.get('peers', list()) # type: ignore
for peer in peers:
@@ -301,13 +300,11 @@ class Monitoring(ContainerDaemonForm):
if daemon_type == 'promtail':
r += ['--config.expand-env']
if daemon_type == 'prometheus':
- config = fetch_configs(ctx)
try:
r += [f'--web.config.file={config["web_config"]}']
except KeyError:
pass
if daemon_type == 'node-exporter':
- config = fetch_configs(ctx)
try:
r += [f'--web.config.file={config["web_config"]}']
except KeyError:
diff --git a/src/cephadm/cephadmlib/daemons/nvmeof.py b/src/cephadm/cephadmlib/daemons/nvmeof.py
index d916c7e6391..51b085df2a7 100644
--- a/src/cephadm/cephadmlib/daemons/nvmeof.py
+++ b/src/cephadm/cephadmlib/daemons/nvmeof.py
@@ -8,7 +8,7 @@ from ..container_types import CephContainer
from ..context_getters import fetch_configs, get_config_and_keyring
from ..daemon_form import register as register_daemon_form
from ..daemon_identity import DaemonIdentity
-from ceph.cephadm.images import DEFAULT_NVMEOF_IMAGE
+from ceph.cephadm.images import DefaultImages
from ..context import CephadmContext
from ..data_utils import dict_get, is_fsid
from ..deployment_utils import to_deployment_container
@@ -26,7 +26,7 @@ class CephNvmeof(ContainerDaemonForm):
daemon_type = 'nvmeof'
required_files = ['ceph-nvmeof.conf']
- default_image = DEFAULT_NVMEOF_IMAGE
+ default_image = DefaultImages.NVMEOF.image_ref
@classmethod
def for_daemon_type(cls, daemon_type: str) -> bool:
@@ -38,7 +38,7 @@ class CephNvmeof(ContainerDaemonForm):
fsid: str,
daemon_id: Union[int, str],
config_json: Dict,
- image: str = DEFAULT_NVMEOF_IMAGE,
+ image: str = DefaultImages.NVMEOF.image_ref,
) -> None:
self.ctx = ctx
self.fsid = fsid
@@ -100,6 +100,7 @@ class CephNvmeof(ContainerDaemonForm):
'client_cert',
'client_key',
'root_ca_cert',
+ 'encryption_key',
]:
if fn in files:
mounts[
diff --git a/src/cephadm/cephadmlib/daemons/oauth2_proxy.py b/src/cephadm/cephadmlib/daemons/oauth2_proxy.py
index 14202111c14..c4f4ec5562f 100644
--- a/src/cephadm/cephadmlib/daemons/oauth2_proxy.py
+++ b/src/cephadm/cephadmlib/daemons/oauth2_proxy.py
@@ -11,7 +11,7 @@ from ..context_getters import fetch_configs
from ..daemon_form import register as register_daemon_form
from ..daemon_identity import DaemonIdentity
from ..deployment_utils import to_deployment_container
-from ceph.cephadm.images import DEFAULT_OAUTH2_PROXY_IMAGE
+from ceph.cephadm.images import DefaultImages
from ..constants import UID_NOBODY, GID_NOGROUP
from ..data_utils import dict_get, is_fsid
from ..file_utils import populate_files, makedirs, recursive_chown
@@ -25,7 +25,7 @@ logger = logging.getLogger()
class OAuth2Proxy(ContainerDaemonForm):
"""Define the configs for the jaeger tracing containers"""
- default_image = DEFAULT_OAUTH2_PROXY_IMAGE
+ default_image = DefaultImages.OAUTH2_PROXY.image_ref
daemon_type = 'oauth2-proxy'
required_files = [
'oauth2-proxy.conf',
@@ -43,7 +43,7 @@ class OAuth2Proxy(ContainerDaemonForm):
fsid: str,
daemon_id: str,
config_json: Dict,
- image: str = DEFAULT_OAUTH2_PROXY_IMAGE,
+ image: str = DefaultImages.OAUTH2_PROXY.image_ref,
):
self.ctx = ctx
self.fsid = fsid
diff --git a/src/cephadm/cephadmlib/daemons/smb.py b/src/cephadm/cephadmlib/daemons/smb.py
index 33d43cbe6ce..0efde198812 100644
--- a/src/cephadm/cephadmlib/daemons/smb.py
+++ b/src/cephadm/cephadmlib/daemons/smb.py
@@ -14,7 +14,7 @@ from .. import data_utils
from .. import deployment_utils
from .. import file_utils
from ..call_wrappers import call, CallVerbosity
-from ceph.cephadm.images import DEFAULT_SAMBA_IMAGE
+from ceph.cephadm.images import DefaultImages
from ..container_daemon_form import ContainerDaemonForm, daemon_to_container
from ..container_engines import Podman
from ..container_types import (
@@ -368,7 +368,7 @@ class SMB(ContainerDaemonForm):
daemon_type = 'smb'
daemon_base = '/usr/sbin/smbd'
- default_image = DEFAULT_SAMBA_IMAGE
+ default_image = DefaultImages.SAMBA.image_ref
@classmethod
def for_daemon_type(cls, daemon_type: str) -> bool:
diff --git a/src/cephadm/cephadmlib/daemons/snmp.py b/src/cephadm/cephadmlib/daemons/snmp.py
index ab84a302f2c..0557a2ef972 100644
--- a/src/cephadm/cephadmlib/daemons/snmp.py
+++ b/src/cephadm/cephadmlib/daemons/snmp.py
@@ -5,7 +5,7 @@ from typing import Any, Dict, List, Optional, Tuple, Union
from urllib.error import HTTPError, URLError
from urllib.request import urlopen
-from ceph.cephadm.images import DEFAULT_SNMP_GATEWAY_IMAGE
+from ceph.cephadm.images import DefaultImages
from ..container_daemon_form import ContainerDaemonForm, daemon_to_container
from ..container_types import CephContainer
from ..context import CephadmContext
@@ -24,7 +24,7 @@ class SNMPGateway(ContainerDaemonForm):
daemon_type = 'snmp-gateway'
SUPPORTED_VERSIONS = ['V2c', 'V3']
- default_image = DEFAULT_SNMP_GATEWAY_IMAGE
+ default_image = DefaultImages.SNMP_GATEWAY.image_ref
DEFAULT_PORT = 9464
env_filename = 'snmp-gateway.conf'
diff --git a/src/cephadm/cephadmlib/daemons/tracing.py b/src/cephadm/cephadmlib/daemons/tracing.py
index 4cf74339455..44548a61d14 100644
--- a/src/cephadm/cephadmlib/daemons/tracing.py
+++ b/src/cephadm/cephadmlib/daemons/tracing.py
@@ -2,12 +2,7 @@ import logging
from typing import Any, Dict, List, Tuple
-from ceph.cephadm.images import (
- DEFAULT_ELASTICSEARCH_IMAGE,
- DEFAULT_JAEGER_AGENT_IMAGE,
- DEFAULT_JAEGER_COLLECTOR_IMAGE,
- DEFAULT_JAEGER_QUERY_IMAGE,
-)
+from ceph.cephadm.images import DefaultImages
from ..container_daemon_form import ContainerDaemonForm, daemon_to_container
from ..container_types import CephContainer
from ..context import CephadmContext
@@ -27,17 +22,17 @@ class Tracing(ContainerDaemonForm):
components: Dict[str, Dict[str, Any]] = {
'elasticsearch': {
- 'image': DEFAULT_ELASTICSEARCH_IMAGE,
+ 'image': DefaultImages.ELASTICSEARCH.image_ref,
'envs': ['discovery.type=single-node'],
},
'jaeger-agent': {
- 'image': DEFAULT_JAEGER_AGENT_IMAGE,
+ 'image': DefaultImages.JAEGER_AGENT.image_ref,
},
'jaeger-collector': {
- 'image': DEFAULT_JAEGER_COLLECTOR_IMAGE,
+ 'image': DefaultImages.JAEGER_COLLECTOR.image_ref,
},
'jaeger-query': {
- 'image': DEFAULT_JAEGER_QUERY_IMAGE,
+ 'image': DefaultImages.JAEGER_QUERY.image_ref,
},
} # type: ignore
diff --git a/src/cephadm/cephadmlib/file_utils.py b/src/cephadm/cephadmlib/file_utils.py
index 27e70e31756..4dd88cc3671 100644
--- a/src/cephadm/cephadmlib/file_utils.py
+++ b/src/cephadm/cephadmlib/file_utils.py
@@ -5,6 +5,7 @@ import datetime
import logging
import os
import tempfile
+import json
from contextlib import contextmanager
from pathlib import Path
@@ -157,3 +158,26 @@ def unlink_file(
except Exception:
if not ignore_errors:
raise
+
+
+def update_meta_file(file_path: str, update_key_val: dict) -> None:
+ """Update key in the file with provided value"""
+ try:
+ with open(file_path, 'r') as fh:
+ data = json.load(fh)
+ file_stat = os.stat(file_path)
+ except FileNotFoundError:
+ raise
+ except Exception:
+ logger.exception(f'Failed to update {file_path}')
+ raise
+ data.update(
+ {key: value for key, value in update_key_val.items() if key in data}
+ )
+
+ with write_new(
+ file_path,
+ owner=(file_stat.st_uid, file_stat.st_gid),
+ perms=(file_stat.st_mode & 0o777),
+ ) as fh:
+ fh.write(json.dumps(data, indent=4) + '\n')
diff --git a/src/cephadm/cephadmlib/net_utils.py b/src/cephadm/cephadmlib/net_utils.py
index 9a7f138b1c6..bfa61d933ef 100644
--- a/src/cephadm/cephadmlib/net_utils.py
+++ b/src/cephadm/cephadmlib/net_utils.py
@@ -24,12 +24,22 @@ class EndPoint:
def __init__(self, ip: str, port: int) -> None:
self.ip = ip
self.port = port
+ self.is_ipv4 = True
+ try:
+ if ip and ipaddress.ip_network(ip).version == 6:
+ self.is_ipv4 = False
+ except Exception:
+ logger.exception('Failed to check ip address version')
def __str__(self) -> str:
- return f'{self.ip}:{self.port}'
+ if self.is_ipv4:
+ return f'{self.ip}:{self.port}'
+ return f'[{self.ip}]:{self.port}'
def __repr__(self) -> str:
- return f'{self.ip}:{self.port}'
+ if self.is_ipv4:
+ return f'{self.ip}:{self.port}'
+ return f'[{self.ip}]:{self.port}'
def attempt_bind(ctx, s, address, port):
diff --git a/src/cephadm/tests/test_deploy.py b/src/cephadm/tests/test_deploy.py
index c5094db335f..1736639ed55 100644
--- a/src/cephadm/tests/test_deploy.py
+++ b/src/cephadm/tests/test_deploy.py
@@ -316,7 +316,7 @@ def test_deploy_a_monitoring_container(cephadm_fs, funkypatch):
runfile_lines = f.read().splitlines()
assert 'podman' in runfile_lines[-1]
assert runfile_lines[-1].endswith(
- 'quay.io/titans/prometheus:latest --config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/prometheus --storage.tsdb.retention.time=15d --storage.tsdb.retention.size=0 --web.external-url=http://10.10.10.10:9095 --web.listen-address=1.2.3.4:9095'
+ 'quay.io/titans/prometheus:latest --config.file=/etc/prometheus/prometheus.yml --storage.tsdb.path=/prometheus --web.listen-address=1.2.3.4:9095 --storage.tsdb.retention.time=15d --storage.tsdb.retention.size=0 --web.external-url=http://10.10.10.10:9095'
)
assert '--user 8765' in runfile_lines[-1]
assert f'-v /var/lib/ceph/{fsid}/prometheus.fire/etc/prometheus:/etc/prometheus:Z' in runfile_lines[-1]