Skip to content

Commit c8337c6

Browse files
authored
Merge pull request ceph#54781 from rhcs-dashboard/nvmeof-integration
mgr/dashboard: implement APIs for nvmeof management Reviewed-by: Avan Thakkar <[email protected]> Reviewed-by: Ernesto Puerta <[email protected]>
2 parents 9545549 + 538f94c commit c8337c6

File tree

18 files changed

+5523
-5
lines changed

18 files changed

+5523
-5
lines changed

ceph.spec.in

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,12 +625,20 @@ Requires: ceph-mgr = %{_epoch_prefix}%{version}-%{release}
625625
Requires: ceph-grafana-dashboards = %{_epoch_prefix}%{version}-%{release}
626626
Requires: ceph-prometheus-alerts = %{_epoch_prefix}%{version}-%{release}
627627
Requires: python%{python3_pkgversion}-setuptools
628+
%if 0%{?fedora} || 0%{?rhel} >= 9
629+
Requires: python%{python3_pkgversion}-grpcio
630+
Requires: python%{python3_pkgversion}-grpcio-tools
631+
%endif
628632
%if 0%{?fedora} || 0%{?rhel} || 0%{?openEuler}
629633
Requires: python%{python3_pkgversion}-cherrypy
630634
Requires: python%{python3_pkgversion}-routes
631635
Requires: python%{python3_pkgversion}-werkzeug
632636
%if 0%{?weak_deps}
633637
Recommends: python%{python3_pkgversion}-saml
638+
%if 0%{?fedora} || 0%{?rhel} <= 8
639+
Recommends: python%{python3_pkgversion}-grpcio
640+
Recommends: python%{python3_pkgversion}-grpcio-tools
641+
%endif
634642
%endif
635643
%endif
636644
%if 0%{?suse_version}

debian/control

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ Build-Depends: automake,
9696
tox <pkg.ceph.check>,
9797
python3-coverage <pkg.ceph.check>,
9898
python3-dateutil <pkg.ceph.check>,
99+
python3-grpcio <pkg.ceph.check>,
99100
python3-openssl <pkg.ceph.check>,
100101
python3-prettytable <pkg.ceph.check>,
101102
python3-requests <pkg.ceph.check>,

src/pybind/mgr/cephadm/serve.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
logger = logging.getLogger(__name__)
4242

43-
REQUIRES_POST_ACTIONS = ['grafana', 'iscsi', 'prometheus', 'alertmanager', 'rgw']
43+
REQUIRES_POST_ACTIONS = ['grafana', 'iscsi', 'prometheus', 'alertmanager', 'rgw', 'nvmeof']
4444

4545

4646
class CephadmServe:

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

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import json
44
from typing import List, cast, Optional
5+
from ipaddress import ip_address, IPv6Address
56

67
from mgr_module import HandleCommandResult
78
from ceph.deployment.service_spec import NvmeofServiceSpec
@@ -55,8 +56,39 @@ def prepare_create(self, daemon_spec: CephadmDaemonDeploySpec) -> CephadmDaemonD
5556
return daemon_spec
5657

5758
def config_dashboard(self, daemon_descrs: List[DaemonDescription]) -> None:
58-
# TODO: what integration do we need with the dashboard?
59-
pass
59+
def get_set_cmd_dicts(out: str) -> List[dict]:
60+
gateways = json.loads(out)['gateways']
61+
cmd_dicts = []
62+
63+
spec = cast(NvmeofServiceSpec,
64+
self.mgr.spec_store.all_specs.get(daemon_descrs[0].service_name(), None))
65+
66+
for dd in daemon_descrs:
67+
assert dd.hostname is not None
68+
69+
if not spec:
70+
logger.warning(f'No ServiceSpec found for {dd.service_name()}')
71+
continue
72+
73+
ip = utils.resolve_ip(self.mgr.inventory.get_addr(dd.hostname))
74+
if type(ip_address(ip)) is IPv6Address:
75+
ip = f'[{ip}]'
76+
service_url = '{}:{}'.format(ip, spec.port or '5500')
77+
gw = gateways.get(dd.hostname)
78+
if not gw or gw['service_url'] != service_url:
79+
logger.info(f'Adding NVMeoF gateway {service_url} to Dashboard')
80+
cmd_dicts.append({
81+
'prefix': 'dashboard nvmeof-gateway-add',
82+
'inbuf': service_url,
83+
'name': dd.hostname
84+
})
85+
return cmd_dicts
86+
87+
self._check_and_set_dashboard(
88+
service_name='nvmeof',
89+
get_cmd='dashboard nvmeof-gateway-list',
90+
get_set_cmd_dicts=get_set_cmd_dicts
91+
)
6092

6193
def ok_to_stop(self,
6294
daemon_ids: List[str],
@@ -83,7 +115,14 @@ def post_remove(self, daemon: DaemonDescription, is_failed_deploy: bool) -> None
83115
Called after the daemon is removed.
84116
"""
85117
logger.debug(f'Post remove daemon {self.TYPE}.{daemon.daemon_id}')
86-
# TODO: remove config for dashboard nvmeof gateways if any
118+
# remove config for dashboard nvmeof gateways if any
119+
ret, out, err = self.mgr.mon_command({
120+
'prefix': 'dashboard nvmeof-gateway-rm',
121+
'name': daemon.hostname,
122+
})
123+
if not ret:
124+
logger.info(f'{daemon.hostname} removed from nvmeof gateways dashboard config')
125+
87126
# and any certificates being used for mTLS
88127

89128
def purge(self, service_name: str) -> None:
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
# -*- coding: utf-8 -*-
2+
import json
3+
from typing import Optional
4+
5+
from ..security import Scope
6+
from . import APIDoc, APIRouter, CreatePermission, DeletePermission, Endpoint, \
7+
EndpointDoc, ReadPermission, RESTController
8+
9+
try:
10+
from google.protobuf.json_format import MessageToJson
11+
12+
from ..services.nvmeof_client import NVMeoFClient
13+
except ImportError:
14+
MessageToJson = None
15+
else:
16+
@APIRouter('/nvmeof/namespace', Scope.NVME_OF)
17+
@APIDoc('NVMe-oF Namespace Management API', 'NVMe-oF')
18+
class NvmeofNamespace(RESTController):
19+
@ReadPermission
20+
def list(self, subsystem_nqn: str):
21+
"""
22+
List all NVMeoF namespaces
23+
"""
24+
response = MessageToJson(NVMeoFClient().list_namespaces(subsystem_nqn))
25+
return json.loads(response)
26+
27+
@CreatePermission
28+
def create(self, rbd_pool: str, rbd_image: str, subsystem_nqn: str,
29+
create_image: Optional[bool] = True, image_size: Optional[int] = 1024,
30+
block_size: int = 512, nsid: Optional[int] = 1,
31+
uuid: Optional[str] = None, anagrpid: Optional[int] = 1):
32+
"""
33+
Create a new NVMeoF namespace
34+
:param rbd_pool: RBD pool name
35+
:param rbd_image: RBD image name
36+
:param subsystem_nqn: NVMeoF subsystem NQN
37+
:param create_image: Create RBD image
38+
:param image_size: RBD image size
39+
:param block_size: NVMeoF namespace block size
40+
:param nsid: NVMeoF namespace ID
41+
:param uuid: NVMeoF namespace UUID
42+
:param anagrpid: NVMeoF namespace ANA group ID
43+
"""
44+
response = NVMeoFClient().create_namespace(rbd_pool, rbd_image,
45+
subsystem_nqn, block_size,
46+
nsid, uuid, anagrpid,
47+
create_image, image_size)
48+
return json.loads(MessageToJson(response))
49+
50+
@Endpoint('DELETE', path='{subsystem_nqn}')
51+
def delete(self, subsystem_nqn: str, nsid: int):
52+
"""
53+
Delete an existing NVMeoF namespace
54+
:param subsystem_nqn: NVMeoF subsystem NQN
55+
:param nsid: NVMeoF namespace ID
56+
"""
57+
response = NVMeoFClient().delete_namespace(subsystem_nqn, nsid)
58+
return json.loads(MessageToJson(response))
59+
60+
@APIRouter('/nvmeof/subsystem', Scope.NVME_OF)
61+
@APIDoc('NVMe-oF Subsystem Management API', 'NVMe-oF')
62+
class NvmeofSubsystem(RESTController):
63+
@ReadPermission
64+
@EndpointDoc("List all NVMeoF gateways",
65+
parameters={
66+
'subsystem_nqn': (str, 'NVMeoF subsystem NQN'),
67+
'serial_number': (str, 'NVMeoF subsystem serial number')
68+
})
69+
def list(self, subsystem_nqn: Optional[str] = None, serial_number: Optional[str] = None):
70+
response = MessageToJson(NVMeoFClient().list_subsystems(
71+
subsystem_nqn=subsystem_nqn, serial_number=serial_number))
72+
73+
return json.loads(response)
74+
75+
@CreatePermission
76+
def create(self, subsystem_nqn: str, serial_number: Optional[str] = None,
77+
max_namespaces: Optional[int] = 256, ana_reporting: Optional[bool] = False,
78+
enable_ha: Optional[bool] = False):
79+
"""
80+
Create a new NVMeoF subsystem
81+
82+
:param subsystem_nqn: NVMeoF subsystem NQN
83+
:param serial_number: NVMeoF subsystem serial number
84+
:param max_namespaces: NVMeoF subsystem maximum namespaces
85+
:param ana_reporting: NVMeoF subsystem ANA reporting
86+
:param enable_ha: NVMeoF subsystem enable HA
87+
"""
88+
response = NVMeoFClient().create_subsystem(subsystem_nqn, serial_number, max_namespaces,
89+
ana_reporting, enable_ha)
90+
return json.loads(MessageToJson(response))
91+
92+
@DeletePermission
93+
@Endpoint('DELETE', path='{subsystem_nqn}')
94+
def delete(self, subsystem_nqn: str):
95+
"""
96+
Delete an existing NVMeoF subsystem
97+
:param subsystem_nqn: NVMeoF subsystem NQN
98+
"""
99+
response = NVMeoFClient().delete_subsystem(subsystem_nqn)
100+
return json.loads(MessageToJson(response))
101+
102+
@APIRouter('/nvmeof/hosts', Scope.NVME_OF)
103+
@APIDoc('NVMe-oF Host Management API', 'NVMe-oF')
104+
class NvmeofHost(RESTController):
105+
@ReadPermission
106+
def list(self, subsystem_nqn: str):
107+
"""
108+
List all NVMeoF hosts
109+
:param subsystem_nqn: NVMeoF subsystem NQN
110+
"""
111+
response = MessageToJson(NVMeoFClient().list_hosts(subsystem_nqn))
112+
return json.loads(response)
113+
114+
@CreatePermission
115+
def create(self, subsystem_nqn: str, host_nqn: str):
116+
"""
117+
Create a new NVMeoF host
118+
:param subsystem_nqn: NVMeoF subsystem NQN
119+
:param host_nqn: NVMeoF host NQN
120+
"""
121+
response = NVMeoFClient().add_host(subsystem_nqn, host_nqn)
122+
return json.loads(MessageToJson(response))
123+
124+
@DeletePermission
125+
def delete(self, subsystem_nqn: str, host_nqn: str):
126+
"""
127+
Delete an existing NVMeoF host
128+
:param subsystem_nqn: NVMeoF subsystem NQN
129+
:param host_nqn: NVMeoF host NQN
130+
"""
131+
response = NVMeoFClient().remove_host(subsystem_nqn, host_nqn)
132+
return json.loads(MessageToJson(response))
133+
134+
@APIRouter('/nvmeof/listener', Scope.NVME_OF)
135+
@APIDoc('NVMe-oF Listener Management API', 'NVMe-oF')
136+
class NvmeofListener(RESTController):
137+
@ReadPermission
138+
def list(self, subsystem_nqn: str):
139+
"""
140+
List all NVMeoF listeners
141+
:param nqn: NVMeoF subsystem NQN
142+
"""
143+
response = MessageToJson(NVMeoFClient().list_listeners(subsystem_nqn))
144+
return json.loads(response)
145+
146+
@CreatePermission
147+
def create(self, nqn: str, gateway: str, traddr: Optional[str] = None,
148+
trtype: Optional[str] = 'TCP', adrfam: Optional[str] = 'IPV4',
149+
trsvcid: Optional[int] = 4420,
150+
auto_ha_state: Optional[str] = 'AUTO_HA_UNSET'):
151+
"""
152+
Create a new NVMeoF listener
153+
:param nqn: NVMeoF subsystem NQN
154+
:param gateway: NVMeoF gateway
155+
:param traddr: NVMeoF transport address
156+
:param trtype: NVMeoF transport type
157+
:param adrfam: NVMeoF address family
158+
:param trsvcid: NVMeoF transport service ID
159+
:param auto_ha_state: NVMeoF auto HA state
160+
"""
161+
response = NVMeoFClient().create_listener(nqn, gateway, traddr,
162+
trtype, adrfam, trsvcid, auto_ha_state)
163+
return json.loads(MessageToJson(response))
164+
165+
@DeletePermission
166+
def delete(self, nqn: str, gateway: str, traddr: Optional[str] = None,
167+
transport_type: Optional[str] = 'TCP', addr_family: Optional[str] = 'IPV4',
168+
transport_svc_id: Optional[int] = 4420):
169+
"""
170+
Delete an existing NVMeoF listener
171+
:param nqn: NVMeoF subsystem NQN
172+
:param gateway: NVMeoF gateway
173+
:param traddr: NVMeoF transport address
174+
:param transport_type: NVMeoF transport type
175+
:param addr_family: NVMeoF address family
176+
:param transport_svc_id: NVMeoF transport service ID
177+
"""
178+
response = NVMeoFClient().delete_listener(nqn, gateway, traddr, transport_type,
179+
addr_family, transport_svc_id)
180+
return json.loads(MessageToJson(response))
181+
182+
@APIRouter('/nvmeof/gateway', Scope.NVME_OF)
183+
@APIDoc('NVMe-oF Gateway Management API', 'NVMe-oF')
184+
class NvmeofGateway(RESTController):
185+
@ReadPermission
186+
@Endpoint()
187+
def info(self):
188+
"""
189+
Get NVMeoF gateway information
190+
"""
191+
response = MessageToJson(NVMeoFClient().gateway_info())
192+
return json.loads(response)

src/pybind/mgr/dashboard/module.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from . import mgr
3030
from .controllers import Router, json_error_page
3131
from .grafana import push_local_dashboards
32+
from .services import nvmeof_cli # noqa # pylint: disable=unused-import
3233
from .services.auth import AuthManager, AuthManagerTool, JwtManager
3334
from .services.exception import dashboard_exception_handler
3435
from .services.rgw_client import configure_rgw_credentials

0 commit comments

Comments
 (0)