# pylint: disable=too-many-lines
import logging
from functools import partial
-from typing import Any, Dict, List, Optional
+from typing import Any, Dict, List, Optional, Tuple
import cherrypy
from orchestrator import OrchestratorError
)
return io_stats
+ @ReadPermission
+ @Endpoint('GET', '/thread_stats')
+ @NvmeofCLICommand(
+ "nvmeof gateway get_thread_stats", model.ThreadStatsInfo,
+ alias="nvmeof gw get_thread_stats")
+ @EndpointDoc(
+ "Get NVMeoF thread statistics for the gateway",
+ parameters={
+ "gw_group": Param(str, "NVMeoF gateway group", True, None),
+ "server_address": Param(str, "NVMeoF gateway address", True, None),
+ "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
+ }
+ )
+ @convert_to_model(model.ThreadStatsInfo)
+ @handle_nvmeof_error
+ def get_thread_stats(
+ self, gw_group: Optional[str] = None,
+ server_address: Optional[str] = None,
+ traddr: Optional[str] = None
+ ):
+ server_address = resolve_nvmeof_server_address(
+ server_address=server_address,
+ traddr=traddr
+ )
+ return NVMeoFClient(
+ gw_group=gw_group,
+ server_address=server_address
+ ).stub.get_thread_stats(
+ NVMeoFClient.pb2.get_thread_stats_req()
+ )
+
@APIRouter("/nvmeof/spdk", Scope.NVME_OF)
@APIDoc("NVMe-oF SPDK Management API", "NVMe-oF SPDK")
class NVMeoFSpdk(RESTController):
@EndpointDoc(
"List all NVMeoF subsystems",
parameters={
+ "nqn": Param(str, "NVMeoF subsystem NQN to filter by", True, None),
+ "serial_number": Param(str, "Serial number to filter by", True, None),
"gw_group": Param(str, "NVMeoF gateway group", True, None),
"server_address": Param(str, "NVMeoF gateway address", True, None),
"traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
)
@convert_to_model(model.SubsystemList)
@handle_nvmeof_error
- def list(self, gw_group: Optional[str] = None, server_address: Optional[str] = None,
+ def list(self, nqn: Optional[str] = None, serial_number: Optional[str] = None,
+ gw_group: Optional[str] = None, server_address: Optional[str] = None,
traddr: Optional[str] = None):
server_address = resolve_nvmeof_server_address(
server_address=server_address,
gw_group=gw_group,
server_address=server_address
).stub.list_subsystems(
- NVMeoFClient.pb2.list_subsystems_req()
+ NVMeoFClient.pb2.list_subsystems_req(subsystem_nqn=nqn,
+ serial_number=serial_number)
)
@pick(field="subsystems", first=True)
"gw_group": Param(str, "NVMeoF gateway group", True, None),
"server_address": Param(str, "NVMeoF gateway address", True, None),
"secure": Param(bool, "Use a secure channel", True, False),
+ "force": Param(
+ bool,
+ "Allow contradiction in security with existing listeners",
+ True,
+ False
+ ),
"verify_host_name": Param(bool,
"Fail if the host name doesn't match the "
"gateway's host name",
gw_group: Optional[str] = None,
server_address: Optional[str] = None,
secure: Optional[bool] = False,
+ force: Optional[bool] = False,
verify_host_name: Optional[bool] = False,
):
client = NVMeoFClient(
trsvcid=int(trsvcid) if trsvcid is not None else None,
adrfam=int(adrfam),
secure=str_to_bool(secure),
+ force=str_to_bool(force),
verify_host_name=str_to_bool(verify_host_name),
)
)
"traddr": Param(str, "NVMeoF transport address"),
"trsvcid": Param(int, "NVMeoF transport service port"),
"adrfam": Param(int, "NVMeoF address family (0 - IPv4, 1 - IPv6)", True, 0),
+ "force": Param(
+ bool,
+ (
+ "Delete listener even if there are active connections "
+ "or host name doesn't match"
+ ),
+ True,
+ False
+ ),
"gw_group": Param(str, "NVMeoF gateway group", True, None),
"server_address": Param(str, "NVMeoF gateway address", True, None),
},
parameters={
"nqn": Param(str, "NVMeoF subsystem NQN", True, None),
"nsid": Param(str, "NVMeoF Namespace ID to filter by", True, None),
+ "uuid": Param(str, "NVMeoF Namespace UUID to filter by", True, None),
"gw_group": Param(str, "NVMeoF gateway group", True, None),
"server_address": Param(str, "NVMeoF gateway address", True, None),
"traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
@convert_to_model(model.NamespaceList)
@handle_nvmeof_error
def list(self, nqn: Optional[str] = None, nsid: Optional[str] = None,
+ uuid: Optional[str] = None,
gw_group: Optional[str] = None, server_address: Optional[str] = None,
traddr: Optional[str] = None):
server_address = resolve_nvmeof_server_address(
server_address=server_address
).stub.list_namespaces(
NVMeoFClient.pb2.list_namespaces_req(subsystem=nqn,
- nsid=int(nsid) if nsid else None)
+ nsid=int(nsid) if nsid else None,
+ uuid=uuid)
)
@pick("namespaces", first=True)
"rados_namespace": Param(str, "RADOS namespace name", True, None),
"rbd_pool": Param(str, "RBD pool name"),
"rbd_data_pool": Param(str, "RBD data pool name", True, None),
- "nsid": Param(str, "Create RBD image", True, None),
+ "nsid": Param(str, "Namespace ID", True, None),
+ "uuid": Param(str, "UUID", True, None),
"create_image": Param(bool, "Create RBD image"),
"size": Param(int, "Deprecated. Use `rbd_image_size` instead"),
"rbd_image_size": Param(int, "RBD image size"),
rbd_pool: str = "rbd",
rbd_data_pool: Optional[str] = None,
nsid: Optional[str] = None,
+ uuid: Optional[str] = None,
create_image: Optional[bool] = False,
size: Optional[int] = None,
rbd_image_size: Optional[int] = None,
NVMeoFClient.pb2.namespace_add_req(
subsystem_nqn=nqn,
nsid=int(nsid) if nsid else None,
+ uuid=uuid,
rbd_image_name=rbd_image_name,
rados_namespace_name=rados_namespace,
rbd_pool_name=rbd_pool,
"rbd_data_pool": Param(str, "RBD data pool name", True, None),
"rados_namespace": Param(str, "RADOS namespace name", True, None),
"rbd_image_name": Param(str, "RBD image name"),
+ "nsid": Param(str, "Namespace ID", True, None),
+ "uuid": Param(str, "UUID", True, None),
"create_image": Param(bool, "Create RBD image"),
"size": Param(str, "Deprecated. Use `rbd_image_size` instead", True, None),
"rbd_image_size": Param(str, "RBD image size", True, None),
rbd_pool: str = "rbd",
rbd_data_pool: Optional[str] = None,
nsid: Optional[str] = None,
+ uuid: Optional[str] = None,
create_image: Optional[bool] = False,
size: Optional[str] = None,
rbd_image_size: Optional[str] = None,
NVMeoFClient.pb2.namespace_add_req(
subsystem_nqn=nqn,
nsid=int(nsid) if nsid else None,
+ uuid=uuid,
rbd_image_name=rbd_image_name,
rados_namespace_name=rados_namespace,
rbd_pool_name=rbd_pool,
)
)
+ @ReadPermission
+ @Endpoint('PUT', '{nsid}/change_location')
+ @NvmeofCLICommand(
+ "nvmeof namespace change_location",
+ model=model.RequestStatus,
+ alias="nvmeof ns change_location",
+ success_message_template=(
+ 'Setting location for namespace {nsid} in {nqn} to "{location}": Successful'
+ )
+ )
+ @EndpointDoc(
+ "Change the location of the specified NVMeoF namespace",
+ parameters={
+ "nqn": Param(str, "NVMeoF subsystem NQN"),
+ "nsid": Param(str, "NVMeoF Namespace ID"),
+ "location": Param(str, "Gateway location for namespace"),
+ "gw_group": Param(str, "NVMeoF gateway group", True, None),
+ "server_address": Param(str, "NVMeoF gateway address", True, None),
+ "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
+ },
+ )
+ @convert_to_model(model.RequestStatus)
+ @handle_nvmeof_error
+ def change_location(
+ self,
+ nqn: str,
+ nsid: str,
+ location: str,
+ gw_group: Optional[str] = None,
+ server_address: Optional[str] = None,
+ traddr: Optional[str] = None
+ ):
+ server_address = resolve_nvmeof_server_address(
+ server_address=server_address,
+ traddr=traddr
+ )
+ return NVMeoFClient(
+ gw_group=gw_group,
+ server_address=server_address
+ ).stub.namespace_change_location(
+ NVMeoFClient.pb2.namespace_change_location_req(
+ subsystem_nqn=nqn,
+ nsid=int(nsid),
+ location=location,
+ )
+ )
+
+ @ReadPermission
+ @Endpoint('GET', 'list_hosts')
+ @NvmeofCLICommand(
+ "nvmeof namespace list_hosts",
+ model=model.NamespaceHostsList,
+ alias="nvmeof ns list_hosts"
+ )
+ @EndpointDoc(
+ "List all NVMeoF namespaces with their allowed hosts",
+ parameters={
+ "nqn": Param(str, "NVMeoF subsystem NQN", True, None),
+ "nsid": Param(str, "NVMeoF Namespace ID to filter by", True, None),
+ "uuid": Param(str, "NVMeoF Namespace UUID to filter by", True, None),
+ "gw_group": Param(str, "NVMeoF gateway group", True, None),
+ "server_address": Param(str, "NVMeoF gateway address", True, None),
+ "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
+ },
+ )
+ @handle_nvmeof_error
+ def list_hosts(
+ self, nqn: Optional[str] = None, nsid: Optional[str] = None,
+ uuid: Optional[str] = None,
+ gw_group: Optional[str] = None, server_address: Optional[str] = None,
+ traddr: Optional[str] = None
+ ):
+ server_address = resolve_nvmeof_server_address(
+ server_address=server_address,
+ traddr=traddr
+ )
+ ns_list = NVMeoFClient(
+ gw_group=gw_group,
+ server_address=server_address
+ ).stub.list_namespaces(
+ NVMeoFClient.pb2.list_namespaces_req(subsystem=nqn,
+ nsid=int(nsid) if nsid else None,
+ uuid=uuid)
+ )
+
+ # Transform to NamedTuple with only NQN, NSID, and Hosts
+ host_infos = []
+ for ns in ns_list.namespaces:
+ host_infos.append(model.NamespaceHostInfo(
+ nqn=ns.ns_subsystem_nqn or nqn or "",
+ nsid=ns.nsid,
+ hosts=list(ns.hosts)
+ ))
+
+ return model.NamespaceHostsList(
+ status=ns_list.status,
+ error_message=ns_list.error_message,
+ namespaces=host_infos
+ )
+
+ @ReadPermission
+ @Endpoint('GET', 'list_locations')
+ @NvmeofCLICommand(
+ "nvmeof namespace list_locations",
+ model=model.NamespaceLocationsList,
+ alias="nvmeof ns list_locations"
+ )
+ @EndpointDoc(
+ "List namespace distribution per site locations",
+ parameters={
+ "nqn": Param(str, "NVMeoF subsystem NQN", True, None),
+ "nsid": Param(str, "NVMeoF Namespace ID to filter by", True, None),
+ "uuid": Param(str, "NVMeoF Namespace UUID to filter by", True, None),
+ "gw_group": Param(str, "NVMeoF gateway group", True, None),
+ "server_address": Param(str, "NVMeoF gateway address", True, None),
+ "traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
+ },
+ )
+ @handle_nvmeof_error
+ def list_locations(
+ self, nqn: Optional[str] = None, nsid: Optional[str] = None,
+ uuid: Optional[str] = None,
+ gw_group: Optional[str] = None, server_address: Optional[str] = None,
+ traddr: Optional[str] = None
+ ):
+ server_address = resolve_nvmeof_server_address(
+ server_address=server_address,
+ traddr=traddr
+ )
+ ns_list = NVMeoFClient(
+ gw_group=gw_group,
+ server_address=server_address
+ ).stub.list_namespaces(
+ NVMeoFClient.pb2.list_namespaces_req(subsystem=nqn,
+ nsid=int(nsid) if nsid else None,
+ uuid=uuid)
+ )
+
+ # Aggregate namespaces by subsystem, load balancing group, and location
+ location_counts: Dict[Tuple[str, int, str], int] = {}
+ for ns in ns_list.namespaces:
+ subsystem_nqn = ns.ns_subsystem_nqn or nqn or ""
+ lb_group = ns.load_balancing_group if ns.load_balancing_group else 0
+ location = ns.location if ns.location else "<default>"
+
+ key = (subsystem_nqn, lb_group, location)
+ location_counts[key] = location_counts.get(key, 0) + 1
+
+ # Convert to NamedTuple list
+ location_infos = []
+ for (subsystem_nqn, lb_group, location), count in sorted(location_counts.items()):
+ location_infos.append(model.NamespaceLocationInfo(
+ subsystem=subsystem_nqn,
+ load_balancing_group=lb_group,
+ location=location,
+ namespace_count=count
+ ))
+
+ return model.NamespaceLocationsList(
+ status=ns_list.status,
+ error_message=ns_list.error_message,
+ locations=location_infos
+ )
+
@ReadPermission
@Endpoint('PUT', '{nsid}/set_auto_resize')
@NvmeofCLICommand(
parameters={
"nqn": Param(str, "NVMeoF subsystem NQN"),
"host_nqn": Param(str, 'NVMeoF host NQN. Use "*" to disallow any host.'),
+ "force": Param(
+ bool,
+ "Delete the host even if it used in a namespace netmask",
+ True, False),
"gw_group": Param(str, "NVMeoF gateway group", True, None),
"server_address": Param(str, "NVMeoF gateway address", True, None),
"traddr": Param(str, "NVMeoF gateway address (deprecated)", True, None),
)
@convert_to_model(model.RequestStatus)
@handle_nvmeof_error
- def delete(self, nqn: str, host_nqn: str, gw_group: Optional[str] = None,
+ def delete(self, nqn: str, host_nqn: str, force: Optional[bool] = False,
+ gw_group: Optional[str] = None,
server_address: Optional[str] = None,
traddr: Optional[str] = None):
server_address = resolve_nvmeof_server_address(
gw_group=gw_group,
server_address=server_address
).stub.remove_host(
- NVMeoFClient.pb2.remove_host_req(subsystem_nqn=nqn, host_nqn=host_nqn)
+ NVMeoFClient.pb2.remove_host_req(subsystem_nqn=nqn, host_nqn=host_nqn, force=force)
)
@empty_response
summary: Get NVMeoF statistics for the gateway
tags:
- NVMe-oF Gateway
+ /api/nvmeof/gateway/thread_stats:
+ get:
+ parameters:
+ - allowEmptyValue: true
+ description: NVMeoF gateway group
+ in: query
+ name: gw_group
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF gateway address
+ in: query
+ name: server_address
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF gateway address (deprecated)
+ in: query
+ name: traddr
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ application/vnd.ceph.api.v1.0+json:
+ schema:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ summary: Get NVMeoF thread statistics for the gateway
+ tags:
+ - NVMe-oF Gateway
/api/nvmeof/gateway/version:
get:
parameters:
/api/nvmeof/subsystem:
get:
parameters:
+ - allowEmptyValue: true
+ description: NVMeoF subsystem NQN to filter by
+ in: query
+ name: nqn
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: Serial number to filter by
+ in: query
+ name: serial_number
+ schema:
+ type: string
- allowEmptyValue: true
description: NVMeoF gateway group
in: query
required: true
schema:
type: string
+ - default: false
+ description: Delete the host even if it used in a namespace netmask
+ in: query
+ name: force
+ schema:
+ type: boolean
- allowEmptyValue: true
description: NVMeoF gateway group
in: query
default: 0
description: NVMeoF address family (0 - IPv4, 1 - IPv6)
type: integer
+ force:
+ default: false
+ description: Allow contradiction in security with existing listeners
+ type: boolean
gw_group:
description: NVMeoF gateway group
type: string
schema:
type: integer
- default: false
+ description: Delete listener even if there are active connections or host
+ name doesn't match
in: query
name: force
schema:
name: nsid
schema:
type: string
+ - allowEmptyValue: true
+ description: NVMeoF Namespace UUID to filter by
+ in: query
+ name: uuid
+ schema:
+ type: string
- allowEmptyValue: true
description: NVMeoF gateway group
in: query
description: Namespace will be visible only for the allowed hosts
type: boolean
nsid:
- description: Create RBD image
+ description: Namespace ID
type: string
rados_namespace:
description: RADOS namespace name
default: false
description: Trash the RBD image when namespace is removed
type: boolean
+ uuid:
+ description: UUID
+ type: string
required:
- rbd_image_name
type: object
summary: Create a new NVMeoF namespace.
tags:
- NVMe-oF Subsystem Namespace
+ /api/nvmeof/subsystem/{nqn}/namespace/list_hosts:
+ get:
+ parameters:
+ - allowEmptyValue: true
+ description: NVMeoF subsystem NQN
+ in: path
+ name: nqn
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF Namespace ID to filter by
+ in: query
+ name: nsid
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF Namespace UUID to filter by
+ in: query
+ name: uuid
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF gateway group
+ in: query
+ name: gw_group
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF gateway address
+ in: query
+ name: server_address
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF gateway address (deprecated)
+ in: query
+ name: traddr
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ application/vnd.ceph.api.v1.0+json:
+ schema:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ summary: List all NVMeoF namespaces with their allowed hosts
+ tags:
+ - NVMe-oF Subsystem Namespace
+ /api/nvmeof/subsystem/{nqn}/namespace/list_locations:
+ get:
+ parameters:
+ - allowEmptyValue: true
+ description: NVMeoF subsystem NQN
+ in: path
+ name: nqn
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF Namespace ID to filter by
+ in: query
+ name: nsid
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF Namespace UUID to filter by
+ in: query
+ name: uuid
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF gateway group
+ in: query
+ name: gw_group
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF gateway address
+ in: query
+ name: server_address
+ schema:
+ type: string
+ - allowEmptyValue: true
+ description: NVMeoF gateway address (deprecated)
+ in: query
+ name: traddr
+ schema:
+ type: string
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ application/vnd.ceph.api.v1.0+json:
+ schema:
+ type: object
+ description: OK
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ summary: List namespace distribution per site locations
+ tags:
+ - NVMe-oF Subsystem Namespace
/api/nvmeof/subsystem/{nqn}/namespace/{nsid}:
delete:
parameters:
summary: set the load balancing group for specified NVMeoF namespace
tags:
- NVMe-oF Subsystem Namespace
+ /api/nvmeof/subsystem/{nqn}/namespace/{nsid}/change_location:
+ put:
+ parameters:
+ - description: NVMeoF subsystem NQN
+ in: path
+ name: nqn
+ required: true
+ schema:
+ type: string
+ - description: NVMeoF Namespace ID
+ in: path
+ name: nsid
+ required: true
+ schema:
+ type: string
+ requestBody:
+ content:
+ application/json:
+ schema:
+ properties:
+ gw_group:
+ description: NVMeoF gateway group
+ type: string
+ location:
+ description: Gateway location for namespace
+ type: string
+ server_address:
+ description: NVMeoF gateway address
+ type: string
+ traddr:
+ description: NVMeoF gateway address (deprecated)
+ type: string
+ required:
+ - location
+ type: object
+ responses:
+ '200':
+ content:
+ application/json:
+ schema:
+ type: object
+ application/vnd.ceph.api.v1.0+json:
+ schema:
+ type: object
+ description: Resource updated.
+ '202':
+ content:
+ application/json:
+ schema:
+ type: object
+ application/vnd.ceph.api.v1.0+json:
+ schema:
+ type: object
+ description: Operation is still executing. Please check the task queue.
+ '400':
+ description: Operation exception. Please check the response body for details.
+ '401':
+ description: Unauthenticated access. Please login first.
+ '403':
+ description: Unauthorized access. Please check your permissions.
+ '500':
+ description: Unexpected error. Please check the response body for the stack
+ trace.
+ security:
+ - jwt: []
+ summary: Change the location of the specified NVMeoF namespace
+ tags:
+ - NVMe-oF Subsystem Namespace
/api/nvmeof/subsystem/{nqn}/namespace/{nsid}/change_visibility:
put:
parameters: