Skip to content

Commit 19b6eaf

Browse files
authored
Merge pull request ceph#50423 from rkachach/fix_issue_nmveof
mgr/cephadm: adding support for nvmeof Reviewed-by: Adam King <[email protected]>
2 parents 6217a62 + 050c0f9 commit 19b6eaf

File tree

10 files changed

+572
-19
lines changed

10 files changed

+572
-19
lines changed

src/cephadm/cephadm.py

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
DEFAULT_GRAFANA_IMAGE = 'quay.io/ceph/ceph-grafana:9.4.7'
5656
DEFAULT_HAPROXY_IMAGE = 'quay.io/ceph/haproxy:2.3'
5757
DEFAULT_KEEPALIVED_IMAGE = 'quay.io/ceph/keepalived:2.2.4'
58+
DEFAULT_NVMEOF_IMAGE = 'quay.io/ceph/nvmeof:0.0.1'
5859
DEFAULT_SNMP_GATEWAY_IMAGE = 'docker.io/maxwo/snmp-notifier:v1.2.1'
5960
DEFAULT_ELASTICSEARCH_IMAGE = 'quay.io/omrizeneva/elasticsearch:6.8.23'
6061
DEFAULT_JAEGER_COLLECTOR_IMAGE = 'quay.io/jaegertracing/jaeger-collector:1.29'
@@ -393,7 +394,7 @@ class UnauthorizedRegistryError(Error):
393394
class Ceph(object):
394395
daemons = ('mon', 'mgr', 'osd', 'mds', 'rgw', 'rbd-mirror',
395396
'crash', 'cephfs-mirror', 'ceph-exporter')
396-
gateways = ('iscsi', 'nfs')
397+
gateways = ('iscsi', 'nfs', 'nvmeof')
397398

398399
##################################
399400

@@ -916,7 +917,8 @@ def get_version(ctx, container_id):
916917
version = None
917918
out, err, code = call(ctx,
918919
[ctx.container_engine.path, 'exec', container_id,
919-
'/usr/bin/python3', '-c', "import pkg_resources; print(pkg_resources.require('ceph_iscsi')[0].version)"],
920+
'/usr/bin/python3', '-c',
921+
"import pkg_resources; print(pkg_resources.require('ceph_iscsi')[0].version)"],
920922
verbosity=CallVerbosity.QUIET)
921923
if code == 0:
922924
version = out.strip()
@@ -983,6 +985,133 @@ def get_tcmu_runner_container(self):
983985
tcmu_container.cname = self.get_container_name(desc='tcmu')
984986
return tcmu_container
985987

988+
989+
##################################
990+
991+
992+
class CephNvmeof(object):
993+
"""Defines a Ceph-Nvmeof container"""
994+
995+
daemon_type = 'nvmeof'
996+
required_files = ['ceph-nvmeof.conf']
997+
default_image = DEFAULT_NVMEOF_IMAGE
998+
999+
def __init__(self,
1000+
ctx,
1001+
fsid,
1002+
daemon_id,
1003+
config_json,
1004+
image=DEFAULT_NVMEOF_IMAGE):
1005+
# type: (CephadmContext, str, Union[int, str], Dict, str) -> None
1006+
self.ctx = ctx
1007+
self.fsid = fsid
1008+
self.daemon_id = daemon_id
1009+
self.image = image
1010+
1011+
# config-json options
1012+
self.files = dict_get(config_json, 'files', {})
1013+
1014+
# validate the supplied args
1015+
self.validate()
1016+
1017+
@classmethod
1018+
def init(cls, ctx, fsid, daemon_id):
1019+
# type: (CephadmContext, str, Union[int, str]) -> CephNvmeof
1020+
return cls(ctx, fsid, daemon_id,
1021+
fetch_configs(ctx), ctx.image)
1022+
1023+
@staticmethod
1024+
def get_container_mounts(data_dir: str) -> Dict[str, str]:
1025+
mounts = dict()
1026+
mounts[os.path.join(data_dir, 'config')] = '/etc/ceph/ceph.conf:z'
1027+
# mounts[os.path.join(data_dir, 'keyring')] = '/etc/ceph/keyring:z'
1028+
mounts['/etc/ceph/ceph.client.admin.keyring'] = '/etc/ceph/keyring:z' # TODO: FIXME
1029+
mounts[os.path.join(data_dir, 'ceph-nvmeof.conf')] = '/src/ceph-nvmeof.conf:z'
1030+
mounts[os.path.join(data_dir, 'configfs')] = '/sys/kernel/config'
1031+
mounts['/dev/hugepages'] = '/dev/hugepages'
1032+
mounts['/dev/vfio/vfio'] = '/dev/vfio/vfio'
1033+
return mounts
1034+
1035+
@staticmethod
1036+
def get_container_binds():
1037+
# type: () -> List[List[str]]
1038+
binds = []
1039+
lib_modules = ['type=bind',
1040+
'source=/lib/modules',
1041+
'destination=/lib/modules',
1042+
'ro=true']
1043+
binds.append(lib_modules)
1044+
return binds
1045+
1046+
@staticmethod
1047+
def get_version(ctx: CephadmContext, container_id: str) -> Optional[str]:
1048+
out, err, ret = call(ctx,
1049+
[ctx.container_engine.path, 'inspect',
1050+
'--format', '{{index .Config.Labels "io.ceph.version"}}',
1051+
ctx.image])
1052+
version = None
1053+
if ret == 0:
1054+
version = out.strip()
1055+
return version
1056+
1057+
def validate(self):
1058+
# type: () -> None
1059+
if not is_fsid(self.fsid):
1060+
raise Error('not an fsid: %s' % self.fsid)
1061+
if not self.daemon_id:
1062+
raise Error('invalid daemon_id: %s' % self.daemon_id)
1063+
if not self.image:
1064+
raise Error('invalid image: %s' % self.image)
1065+
1066+
# check for the required files
1067+
if self.required_files:
1068+
for fname in self.required_files:
1069+
if fname not in self.files:
1070+
raise Error('required file missing from config-json: %s' % fname)
1071+
1072+
def get_daemon_name(self):
1073+
# type: () -> str
1074+
return '%s.%s' % (self.daemon_type, self.daemon_id)
1075+
1076+
def get_container_name(self, desc=None):
1077+
# type: (Optional[str]) -> str
1078+
cname = '%s-%s' % (self.fsid, self.get_daemon_name())
1079+
if desc:
1080+
cname = '%s-%s' % (cname, desc)
1081+
return cname
1082+
1083+
def create_daemon_dirs(self, data_dir, uid, gid):
1084+
# type: (str, int, int) -> None
1085+
"""Create files under the container data dir"""
1086+
if not os.path.isdir(data_dir):
1087+
raise OSError('data_dir is not a directory: %s' % (data_dir))
1088+
1089+
logger.info('Creating ceph-nvmeof config...')
1090+
configfs_dir = os.path.join(data_dir, 'configfs')
1091+
makedirs(configfs_dir, uid, gid, 0o755)
1092+
1093+
# populate files from the config-json
1094+
populate_files(data_dir, self.files, uid, gid)
1095+
1096+
@staticmethod
1097+
def configfs_mount_umount(data_dir, mount=True):
1098+
# type: (str, bool) -> List[str]
1099+
mount_path = os.path.join(data_dir, 'configfs')
1100+
if mount:
1101+
cmd = 'if ! grep -qs {0} /proc/mounts; then ' \
1102+
'mount -t configfs none {0}; fi'.format(mount_path)
1103+
else:
1104+
cmd = 'if grep -qs {0} /proc/mounts; then ' \
1105+
'umount {0}; fi'.format(mount_path)
1106+
return cmd.split()
1107+
1108+
@staticmethod
1109+
def get_sysctl_settings() -> List[str]:
1110+
return [
1111+
'vm.nr_hugepages = 4096',
1112+
]
1113+
1114+
9861115
##################################
9871116

9881117

@@ -1435,6 +1564,7 @@ def get_supported_daemons():
14351564
supported_daemons.extend(Monitoring.components)
14361565
supported_daemons.append(NFSGanesha.daemon_type)
14371566
supported_daemons.append(CephIscsi.daemon_type)
1567+
supported_daemons.append(CephNvmeof.daemon_type)
14381568
supported_daemons.append(CustomContainer.daemon_type)
14391569
supported_daemons.append(HAproxy.daemon_type)
14401570
supported_daemons.append(Keepalived.daemon_type)
@@ -2327,6 +2457,8 @@ def update_default_image(ctx: CephadmContext) -> None:
23272457
ctx.image = Keepalived.default_image
23282458
if type_ == SNMPGateway.daemon_type:
23292459
ctx.image = SNMPGateway.default_image
2460+
if type_ == CephNvmeof.daemon_type:
2461+
ctx.image = CephNvmeof.default_image
23302462
if type_ in Tracing.components:
23312463
ctx.image = Tracing.components[type_]['image']
23322464
if not ctx.image:
@@ -2966,6 +3098,10 @@ def create_daemon_dirs(ctx, fsid, daemon_type, daemon_id, uid, gid,
29663098
ceph_iscsi = CephIscsi.init(ctx, fsid, daemon_id)
29673099
ceph_iscsi.create_daemon_dirs(data_dir, uid, gid)
29683100

3101+
elif daemon_type == CephNvmeof.daemon_type:
3102+
ceph_nvmeof = CephNvmeof.init(ctx, fsid, daemon_id)
3103+
ceph_nvmeof.create_daemon_dirs(data_dir, uid, gid)
3104+
29693105
elif daemon_type == HAproxy.daemon_type:
29703106
haproxy = HAproxy.init(ctx, fsid, daemon_id)
29713107
haproxy.create_daemon_dirs(data_dir, uid, gid)
@@ -3144,6 +3280,8 @@ def get_container_binds(ctx, fsid, daemon_type, daemon_id):
31443280

31453281
if daemon_type == CephIscsi.daemon_type:
31463282
binds.extend(CephIscsi.get_container_binds())
3283+
if daemon_type == CephNvmeof.daemon_type:
3284+
binds.extend(CephNvmeof.get_container_binds())
31473285
elif daemon_type == CustomContainer.daemon_type:
31483286
assert daemon_id
31493287
cc = CustomContainer.init(ctx, fsid, daemon_id)
@@ -3260,6 +3398,11 @@ def get_container_mounts(ctx, fsid, daemon_type, daemon_id,
32603398
data_dir = get_data_dir(fsid, ctx.data_dir, daemon_type, daemon_id)
32613399
mounts.update(HAproxy.get_container_mounts(data_dir))
32623400

3401+
if daemon_type == CephNvmeof.daemon_type:
3402+
assert daemon_id
3403+
data_dir = get_data_dir(fsid, ctx.data_dir, daemon_type, daemon_id)
3404+
mounts.update(CephNvmeof.get_container_mounts(data_dir))
3405+
32633406
if daemon_type == CephIscsi.daemon_type:
32643407
assert daemon_id
32653408
data_dir = get_data_dir(fsid, ctx.data_dir, daemon_type, daemon_id)
@@ -3394,6 +3537,11 @@ def get_container(ctx: CephadmContext,
33943537
name = '%s.%s' % (daemon_type, daemon_id)
33953538
envs.extend(Keepalived.get_container_envs())
33963539
container_args.extend(['--cap-add=NET_ADMIN', '--cap-add=NET_RAW'])
3540+
elif daemon_type == CephNvmeof.daemon_type:
3541+
name = '%s.%s' % (daemon_type, daemon_id)
3542+
container_args.extend(['--ulimit', 'memlock=-1:-1'])
3543+
container_args.extend(['--ulimit', 'nofile=10240'])
3544+
container_args.extend(['--cap-add=SYS_ADMIN', '--cap-add=CAP_SYS_NICE'])
33973545
elif daemon_type == CephIscsi.daemon_type:
33983546
entrypoint = CephIscsi.entrypoint
33993547
name = '%s.%s' % (daemon_type, daemon_id)
@@ -3971,6 +4119,8 @@ def _write(conf: Path, lines: List[str]) -> None:
39714119
lines = HAproxy.get_sysctl_settings()
39724120
elif daemon_type == 'keepalived':
39734121
lines = Keepalived.get_sysctl_settings()
4122+
elif daemon_type == CephNvmeof.daemon_type:
4123+
lines = CephNvmeof.get_sysctl_settings()
39744124
lines = filter_sysctl_settings(ctx, lines)
39754125

39764126
# apply the sysctl settings
@@ -6445,6 +6595,14 @@ def _dispatch_deploy(
64456595
config=config, keyring=keyring,
64466596
deployment_type=deployment_type,
64476597
ports=daemon_ports)
6598+
elif daemon_type == CephNvmeof.daemon_type:
6599+
config, keyring = get_config_and_keyring(ctx)
6600+
uid, gid = 167, 167 # TODO: need to get properly the uid/gid
6601+
c = get_deployment_container(ctx, ctx.fsid, daemon_type, daemon_id)
6602+
deploy_daemon(ctx, ctx.fsid, daemon_type, daemon_id, c, uid, gid,
6603+
config=config, keyring=keyring,
6604+
deployment_type=deployment_type,
6605+
ports=daemon_ports)
64486606
elif daemon_type in Tracing.components:
64496607
uid, gid = 65534, 65534
64506608
c = get_container(ctx, ctx.fsid, daemon_type, daemon_id)
@@ -6986,6 +7144,8 @@ def list_daemons(ctx, detail=True, legacy_dir=None):
69867144
version = NFSGanesha.get_version(ctx, container_id)
69877145
if daemon_type == CephIscsi.daemon_type:
69887146
version = CephIscsi.get_version(ctx, container_id)
7147+
if daemon_type == CephNvmeof.daemon_type:
7148+
version = CephNvmeof.get_version(ctx, container_id)
69897149
elif not version:
69907150
if daemon_type in Ceph.daemons:
69917151
out, err, code = call(ctx,

src/pybind/mgr/cephadm/module.py

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
from .services.ingress import IngressService
5959
from .services.container import CustomContainerService
6060
from .services.iscsi import IscsiService
61+
from .services.nvmeof import NvmeofService
6162
from .services.nfs import NFSService
6263
from .services.osd import OSDRemovalQueue, OSDService, OSD, NotFoundError
6364
from .services.monitoring import GrafanaService, AlertmanagerService, PrometheusService, \
@@ -106,6 +107,7 @@ def os_exit_noop(status: int) -> None:
106107
DEFAULT_IMAGE = 'quay.io/ceph/ceph'
107108
DEFAULT_PROMETHEUS_IMAGE = 'quay.io/prometheus/prometheus:v2.43.0'
108109
DEFAULT_NODE_EXPORTER_IMAGE = 'quay.io/prometheus/node-exporter:v1.5.0'
110+
DEFAULT_NVMEOF_IMAGE = 'quay.io/ceph/nvmeof:0.0.1'
109111
DEFAULT_LOKI_IMAGE = 'docker.io/grafana/loki:2.4.0'
110112
DEFAULT_PROMTAIL_IMAGE = 'docker.io/grafana/promtail:2.4.0'
111113
DEFAULT_ALERT_MANAGER_IMAGE = 'quay.io/prometheus/alertmanager:v0.25.0'
@@ -201,6 +203,11 @@ class CephadmOrchestrator(orchestrator.Orchestrator, MgrModule,
201203
default=DEFAULT_PROMETHEUS_IMAGE,
202204
desc='Prometheus container image',
203205
),
206+
Option(
207+
'container_image_nvmeof',
208+
default=DEFAULT_NVMEOF_IMAGE,
209+
desc='Nvme-of container image',
210+
),
204211
Option(
205212
'container_image_grafana',
206213
default=DEFAULT_GRAFANA_IMAGE,
@@ -511,6 +518,7 @@ def __init__(self, *args: Any, **kwargs: Any):
511518
self.mode = ''
512519
self.container_image_base = ''
513520
self.container_image_prometheus = ''
521+
self.container_image_nvmeof = ''
514522
self.container_image_grafana = ''
515523
self.container_image_alertmanager = ''
516524
self.container_image_node_exporter = ''
@@ -630,7 +638,7 @@ def __init__(self, *args: Any, **kwargs: Any):
630638
OSDService, NFSService, MonService, MgrService, MdsService,
631639
RgwService, RbdMirrorService, GrafanaService, AlertmanagerService,
632640
PrometheusService, NodeExporterService, LokiService, PromtailService, CrashService, IscsiService,
633-
IngressService, CustomContainerService, CephfsMirrorService,
641+
IngressService, CustomContainerService, CephfsMirrorService, NvmeofService,
634642
CephadmAgent, CephExporterService, SNMPGatewayService, ElasticSearchService,
635643
JaegerQueryService, JaegerAgentService, JaegerCollectorService
636644
]
@@ -642,6 +650,7 @@ def __init__(self, *args: Any, **kwargs: Any):
642650
self.mgr_service: MgrService = cast(MgrService, self.cephadm_services['mgr'])
643651
self.osd_service: OSDService = cast(OSDService, self.cephadm_services['osd'])
644652
self.iscsi_service: IscsiService = cast(IscsiService, self.cephadm_services['iscsi'])
653+
self.nvmeof_service: NvmeofService = cast(NvmeofService, self.cephadm_services['nvmeof'])
645654

646655
self.scheduled_async_actions: List[Callable] = []
647656

@@ -1198,7 +1207,8 @@ def check_host(self, host: str, addr: Optional[str] = None) -> Tuple[int, str, s
11981207
if code:
11991208
return 1, '', ('check-host failed:\n' + '\n'.join(err))
12001209
except ssh.HostConnectionError as e:
1201-
self.log.exception(f"check-host failed for '{host}' at addr ({e.addr}) due to connection failure: {str(e)}")
1210+
self.log.exception(
1211+
f"check-host failed for '{host}' at addr ({e.addr}) due to connection failure: {str(e)}")
12021212
return 1, '', ('check-host failed:\n'
12031213
+ f"Failed to connect to {host} at address ({e.addr}): {str(e)}")
12041214
except OrchestratorError:
@@ -1479,6 +1489,8 @@ def _get_container_image(self, daemon_name: str) -> Optional[str]:
14791489
)).strip()
14801490
elif daemon_type == 'prometheus':
14811491
image = self.container_image_prometheus
1492+
elif daemon_type == 'nvmeof':
1493+
image = self.container_image_nvmeof
14821494
elif daemon_type == 'grafana':
14831495
image = self.container_image_grafana
14841496
elif daemon_type == 'alertmanager':
@@ -1669,7 +1681,8 @@ def run_cmd(cmd_args: dict) -> None:
16691681

16701682
if d.daemon_type != 'osd':
16711683
self.cephadm_services[daemon_type_to_service(str(d.daemon_type))].pre_remove(d)
1672-
self.cephadm_services[daemon_type_to_service(str(d.daemon_type))].post_remove(d, is_failed_deploy=False)
1684+
self.cephadm_services[daemon_type_to_service(
1685+
str(d.daemon_type))].post_remove(d, is_failed_deploy=False)
16731686
else:
16741687
cmd_args = {
16751688
'prefix': 'osd purge-actual',
@@ -1687,7 +1700,8 @@ def run_cmd(cmd_args: dict) -> None:
16871700
self.inventory.rm_host(host)
16881701
self.cache.rm_host(host)
16891702
self.ssh.reset_con(host)
1690-
self.offline_hosts_remove(host) # if host was in offline host list, we should remove it now.
1703+
# if host was in offline host list, we should remove it now.
1704+
self.offline_hosts_remove(host)
16911705
self.event.set() # refresh stray health check
16921706
self.log.info('Removed host %s' % host)
16931707
return "Removed {} host '{}'".format('offline' if offline else '', host)
@@ -2662,7 +2676,8 @@ def get_daemon_names(daemons: List[str]) -> List[str]:
26622676
# an explicit dependency is added for each service-type to force a reconfig
26632677
# whenever the number of daemons for those service-type changes from 0 to greater
26642678
# than zero and vice versa.
2665-
deps += [s for s in ['node-exporter', 'alertmanager'] if self.cache.get_daemons_by_service(s)]
2679+
deps += [s for s in ['node-exporter', 'alertmanager']
2680+
if self.cache.get_daemons_by_service(s)]
26662681
if len(self.cache.get_daemons_by_type('ingress')) > 0:
26672682
deps.append('ingress')
26682683
# add dependency on ceph-exporter daemons
@@ -2671,7 +2686,8 @@ def get_daemon_names(daemons: List[str]) -> List[str]:
26712686
if self.prometheus_web_user and self.prometheus_web_password:
26722687
deps.append(f'{hash(self.prometheus_web_user + self.prometheus_web_password)}')
26732688
if self.alertmanager_web_user and self.alertmanager_web_password:
2674-
deps.append(f'{hash(self.alertmanager_web_user + self.alertmanager_web_password)}')
2689+
deps.append(
2690+
f'{hash(self.alertmanager_web_user + self.alertmanager_web_password)}')
26752691
elif daemon_type == 'grafana':
26762692
deps += get_daemon_names(['prometheus', 'loki'])
26772693
if self.secure_monitoring_stack and self.prometheus_web_user and self.prometheus_web_password:
@@ -2976,6 +2992,7 @@ def _apply_service_spec(self, spec: ServiceSpec) -> str:
29762992
'rgw': PlacementSpec(count=2),
29772993
'ingress': PlacementSpec(count=2),
29782994
'iscsi': PlacementSpec(count=1),
2995+
'nvmeof': PlacementSpec(count=1),
29792996
'rbd-mirror': PlacementSpec(count=2),
29802997
'cephfs-mirror': PlacementSpec(count=1),
29812998
'nfs': PlacementSpec(count=1),
@@ -3199,7 +3216,8 @@ def upgrade_start(self, image: str, version: str, daemon_types: Optional[List[st
31993216
if self.inventory.get_host_with_state("maintenance"):
32003217
raise OrchestratorError("Upgrade aborted - you have host(s) in maintenance state")
32013218
if self.offline_hosts:
3202-
raise OrchestratorError(f"Upgrade aborted - Some host(s) are currently offline: {self.offline_hosts}")
3219+
raise OrchestratorError(
3220+
f"Upgrade aborted - Some host(s) are currently offline: {self.offline_hosts}")
32033221
if daemon_types is not None and services is not None:
32043222
raise OrchestratorError('--daemon-types and --services are mutually exclusive')
32053223
if daemon_types is not None:

src/pybind/mgr/cephadm/services/cephadmservice.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def get_auth_entity(daemon_type: str, daemon_id: str, host: str = "") -> AuthEnt
3939
"""
4040
# despite this mapping entity names to daemons, self.TYPE within
4141
# the CephService class refers to service types, not daemon types
42-
if daemon_type in ['rgw', 'rbd-mirror', 'cephfs-mirror', 'nfs', "iscsi", 'ingress', 'ceph-exporter']:
42+
if daemon_type in ['rgw', 'rbd-mirror', 'cephfs-mirror', 'nfs', "iscsi", 'nvmeof', 'ingress', 'ceph-exporter']:
4343
return AuthEntity(f'client.{daemon_type}.{daemon_id}')
4444
elif daemon_type in ['crash', 'agent']:
4545
if host == "":

0 commit comments

Comments
 (0)