Skip to content

Commit 792b9c0

Browse files
authored
Merge pull request #298 from gbregman/devel
Handle IPv6 parameters in NVMe CLI
2 parents 9b0239b + 95af3bb commit 792b9c0

File tree

10 files changed

+76
-10
lines changed

10 files changed

+76
-10
lines changed

.env

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ NVMEOF_DESCRIPTION="Service to provide block storage on top of Ceph for platform
2727
NVMEOF_URL="https://github.com/ceph/ceph-nvmeof"
2828
NVMEOF_TAGS="ceph,nvme-of,nvme-of gateway,rbd,block storage"
2929
NVMEOF_WANTS="ceph,rbd"
30-
NVMEOF_IP_ADDRESS="192.168.13.3"
30+
NVMEOF_IP_ADDRESS=192.168.13.3
31+
NVMEOF_IPV6_ADDRESS=2001:db8::3
3132
NVMEOF_IO_PORT=4420
3233
NVMEOF_GW_PORT=5500
3334
NVMEOF_DISC_PORT=8009
@@ -57,10 +58,10 @@ CEPH_CLUSTER_VERSION="${CEPH_VERSION}"
5758
CEPH_VSTART_ARGS="--without-dashboard --memstore"
5859

5960
# Demo settings
60-
RBD_POOL="rbd"
61-
RBD_IMAGE_NAME="demo_image"
62-
RBD_IMAGE_SIZE="10M"
63-
BDEV_NAME="demo_bdev"
61+
RBD_POOL=rbd
62+
RBD_IMAGE_NAME=demo_image
63+
RBD_IMAGE_SIZE=10M
64+
BDEV_NAME=demo_bdev
6465
NQN="nqn.2016-06.io.spdk:cnode1"
6566
SERIAL="SPDK00000000000001"
6667

.github/workflows/build-container.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ jobs:
226226
shopt -s expand_aliases
227227
eval $(make alias)
228228
nvmeof-cli get_subsystems
229+
nvmeof-cli-ipv6 get_subsystems
229230
230231
- name: Run bdevperf
231232
run: |

control/cli.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
from .proto import gateway_pb2_grpc as pb2_grpc
1919
from .proto import gateway_pb2 as pb2
20-
20+
from .config import GatewayConfig
2121

2222
def argument(*name_or_flags, **kwargs):
2323
"""Helper function to format arguments for argparse command decorator."""
@@ -124,7 +124,9 @@ def stub(self):
124124

125125
def connect(self, host, port, client_key, client_cert, server_cert):
126126
"""Connects to server and sets stub."""
127-
server = "{}:{}".format(host, port)
127+
# We need to enclose IPv6 addresses in brackets before concatenating a colon and port number to it
128+
host = GatewayConfig.escape_address_if_ipv6(host)
129+
server = f"{host}:{port}"
128130

129131
if client_key and client_cert:
130132
# Create credentials for mutual TLS and a secure channel

control/config.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,3 +64,10 @@ def dump_config_file(self, logger):
6464
self.conffile_logged = True
6565
except Exception:
6666
pass
67+
68+
# We need to enclose IPv6 addresses in brackets before concatenating a colon and port number to it
69+
def escape_address_if_ipv6(addr) -> str:
70+
ret_addr = addr
71+
if ":" in addr and not addr.strip().startswith("["):
72+
ret_addr = f"[{addr}]"
73+
return ret_addr

control/grpc.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
from google.protobuf import json_format
2424
from .proto import gateway_pb2 as pb2
2525
from .proto import gateway_pb2_grpc as pb2_grpc
26+
from .config import GatewayConfig
2627

2728
MAX_ANA_GROUPS = 4
2829

@@ -535,9 +536,10 @@ def matching_listener_exists(self, context, nqn, gw_name, trtype, traddr, trsvci
535536
def create_listener_safe(self, request, context=None):
536537
"""Creates a listener for a subsystem at a given IP/Port."""
537538
ret = True
539+
traddr = GatewayConfig.escape_address_if_ipv6(request.traddr)
538540
self.logger.info(f"Received request to create {request.gateway_name}"
539541
f" {request.trtype} listener for {request.nqn} at"
540-
f" {request.traddr}:{request.trsvcid}.")
542+
f" {traddr}:{request.trsvcid}.")
541543
try:
542544
if request.gateway_name == self.gateway_name:
543545
listener_already_exist = self.matching_listener_exists(
@@ -627,9 +629,10 @@ def delete_listener_safe(self, request, context=None):
627629
"""Deletes a listener from a subsystem at a given IP/Port."""
628630

629631
ret = True
632+
traddr = GatewayConfig.escape_address_if_ipv6(request.traddr)
630633
self.logger.info(f"Received request to delete {request.gateway_name}"
631634
f" {request.trtype} listener for {request.nqn} at"
632-
f" {request.traddr}:{request.trsvcid}.")
635+
f" {traddr}:{request.trsvcid}.")
633636
try:
634637
if request.gateway_name == self.gateway_name:
635638
ret = rpc_nvmf.nvmf_subsystem_remove_listener(

control/server.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
from .state import GatewayState, LocalGatewayState, OmapGatewayState, GatewayStateHandler
2929
from .grpc import GatewayService
3030
from .discovery import DiscoveryService
31+
from .config import GatewayConfig
3132

3233
def sigchld_handler(signum, frame):
3334
"""Handle SIGCHLD, runs when a spdk process terminates."""
@@ -158,6 +159,8 @@ def _add_server_listener(self):
158159
enable_auth = self.config.getboolean("gateway", "enable_auth")
159160
gateway_addr = self.config.get("gateway", "addr")
160161
gateway_port = self.config.get("gateway", "port")
162+
# We need to enclose IPv6 addresses in brackets before concatenating a colon and port number to it
163+
gateway_addr = GatewayConfig.escape_address_if_ipv6(gateway_addr)
161164
if enable_auth:
162165
# Read in key and certificates for authentication
163166
server_key = self.config.get("mtls", "server_key")

docker-compose.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ services:
7878
networks:
7979
default:
8080
ipv4_address: 192.168.13.2
81+
ipv6_address: 2001:db8::2
8182
nvmeof-base:
8283
build:
8384
context: .
@@ -213,6 +214,8 @@ volumes:
213214
ceph-conf:
214215
networks:
215216
default:
217+
enable_ipv6: true
216218
ipam:
217219
config:
218220
- subnet: 192.168.13.0/24
221+
- subnet: 2001:0DB8::/112

mk/demo.mk

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ rbd: CMD = bash -c "rbd -p $(RBD_POOL) info $(RBD_IMAGE_NAME) || rbd -p $(RBD_PO
1010
demo: export NVMEOF_HOSTNAME != docker ps -q -f name=$(NVMEOF_CONTAINER_NAME)
1111
demo: rbd ## Expose RBD_IMAGE_NAME as NVMe-oF target
1212
$(NVMEOF_CLI) create_bdev --pool $(RBD_POOL) --image $(RBD_IMAGE_NAME) --bdev $(BDEV_NAME)
13+
$(NVMEOF_CLI_IPV6) create_bdev --pool $(RBD_POOL) --image $(RBD_IMAGE_NAME) --bdev $(BDEV_NAME)_ipv6
1314
$(NVMEOF_CLI) create_subsystem --subnqn $(NQN)
1415
$(NVMEOF_CLI) add_namespace --subnqn $(NQN) --bdev $(BDEV_NAME)
16+
$(NVMEOF_CLI) add_namespace --subnqn $(NQN) --bdev $(BDEV_NAME)_ipv6
1517
$(NVMEOF_CLI) create_listener --subnqn $(NQN) --gateway-name $(NVMEOF_HOSTNAME) --traddr $(NVMEOF_IP_ADDRESS) --trsvcid $(NVMEOF_IO_PORT)
18+
$(NVMEOF_CLI_IPV6) create_listener --subnqn $(NQN) --gateway-name $(NVMEOF_HOSTNAME) --traddr '$(NVMEOF_IPV6_ADDRESS)' --trsvcid $(NVMEOF_IO_PORT) --adrfam IPV6
1619
$(NVMEOF_CLI) add_host --subnqn $(NQN) --host "*"
1720

1821
.PHONY: demo rbd

mk/misc.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@
22

33
# nvmeof_cli
44
NVMEOF_CLI = $(DOCKER_COMPOSE_ENV) $(DOCKER_COMPOSE) run --rm nvmeof-cli --server-address $(NVMEOF_IP_ADDRESS) --server-port $(NVMEOF_GW_PORT)
5+
NVMEOF_CLI_IPV6 = $(DOCKER_COMPOSE_ENV) $(DOCKER_COMPOSE) run --rm nvmeof-cli --server-address $(NVMEOF_IPV6_ADDRESS) --server-port $(NVMEOF_GW_PORT)
56

67
alias: ## Print bash alias command for the nvmeof-cli. Usage: "eval $(make alias)"
7-
@echo alias nvmeof-cli=\"$(NVMEOF_CLI)\"
8+
@echo alias nvmeof-cli=\"$(NVMEOF_CLI)\" \; alias nvmeof-cli-ipv6=\'$(NVMEOF_CLI_IPV6)\'
89

910
.PHONY: alias

tests/test_cli.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,15 @@
1212
serial = "SPDK00000000000001"
1313
host_list = ["nqn.2016-06.io.spdk:host1", "*"]
1414
nsid = "1"
15+
nsid_ipv6 = "2"
1516
anagrpid = "2"
1617
trtype = "TCP"
1718
gateway_name = socket.gethostname()
1819
addr = "127.0.0.1"
20+
addr_ipv6 = "::1"
21+
server_addr_ipv6 = "2001:db8::3"
1922
listener_list = [["-g", gateway_name, "-a", addr, "-s", "5001"], ["-g", gateway_name, "-a", addr,"-s", "5002"]]
23+
listener_list_ipv6 = [["-g", gateway_name, "-a", addr_ipv6, "-s", "5003"], ["-g", gateway_name, "-a", addr_ipv6, "-s", "5004"]]
2024
config = "ceph-nvmeof.conf"
2125

2226
@pytest.fixture(scope="module")
@@ -38,6 +42,10 @@ def test_get_subsystems(self, caplog, gateway):
3842
cli(["get_subsystems"])
3943
assert "Failed to get" not in caplog.text
4044

45+
def test_get_subsystems_ipv6(self, caplog, gateway):
46+
cli(["--server-address", server_addr_ipv6, "get_subsystems"])
47+
assert "Failed to get" not in caplog.text
48+
4149

4250
class TestCreate:
4351
def test_create_bdev(self, caplog, gateway):
@@ -46,6 +54,12 @@ def test_create_bdev(self, caplog, gateway):
4654
cli(["create_bdev", "-i", image, "-p", pool, "-b", bdev1])
4755
assert "Failed to create" not in caplog.text
4856

57+
def test_create_bdev_ipv6(self, caplog, gateway):
58+
cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev + "_ipv6"])
59+
assert "Failed to create" not in caplog.text
60+
cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev1 + "_ipv6"])
61+
assert "Failed to create" not in caplog.text
62+
4963
def test_create_subsystem(self, caplog, gateway):
5064
cli(["create_subsystem", "-n", subsystem])
5165
assert "Failed to create" not in caplog.text
@@ -62,6 +76,12 @@ def test_add_namespace(self, caplog, gateway):
6276
cli(["add_namespace", "-n", subsystem, "-b", bdev1])
6377
assert "Failed to add" not in caplog.text
6478

79+
def test_add_namespace_ipv6(self, caplog, gateway):
80+
cli(["--server-address", server_addr_ipv6, "add_namespace", "-n", subsystem, "-b", bdev + "_ipv6"])
81+
assert "Failed to add" not in caplog.text
82+
cli(["--server-address", server_addr_ipv6, "add_namespace", "-n", subsystem, "-b", bdev1 + "_ipv6"])
83+
assert "Failed to add" not in caplog.text
84+
6585
@pytest.mark.parametrize("host", host_list)
6686
def test_add_host(self, caplog, host):
6787
cli(["add_host", "-n", subsystem, "-t", host])
@@ -72,6 +92,11 @@ def test_create_listener(self, caplog, listener, gateway):
7292
cli(["create_listener", "-n", subsystem] + listener)
7393
assert "Failed to create" not in caplog.text
7494

95+
@pytest.mark.parametrize("listener_ipv6", listener_list_ipv6)
96+
def test_create_listener_ipv6(self, caplog, listener_ipv6, gateway):
97+
cli(["--server-address", server_addr_ipv6, "create_listener", "-n", subsystem, "--adrfam", "IPV6"] + listener_ipv6)
98+
assert "Failed to create" not in caplog.text
99+
75100

76101
class TestDelete:
77102
@pytest.mark.parametrize("host", host_list)
@@ -84,15 +109,26 @@ def test_delete_listener(self, caplog, listener, gateway):
84109
cli(["delete_listener", "-n", subsystem] + listener)
85110
assert "Failed to delete" not in caplog.text
86111

112+
@pytest.mark.parametrize("listener_ipv6", listener_list_ipv6)
113+
def test_delete_listener_ipv6(self, caplog, listener_ipv6, gateway):
114+
cli(["--server-address", server_addr_ipv6, "delete_listener", "-n", subsystem, "--adrfam", "IPV6"] + listener_ipv6)
115+
assert "Failed to delete" not in caplog.text
116+
87117
def test_remove_namespace(self, caplog, gateway):
88118
cli(["remove_namespace", "-n", subsystem, "-i", nsid])
89119
assert "Failed to remove" not in caplog.text
120+
cli(["remove_namespace", "-n", subsystem, "-i", nsid_ipv6])
121+
assert "Failed to remove" not in caplog.text
90122

91123
def test_delete_bdev(self, caplog, gateway):
92124
cli(["delete_bdev", "-b", bdev, "-f"])
93125
assert "Failed to delete" not in caplog.text
94126
cli(["delete_bdev", "-b", bdev1, "--force"])
95127
assert "Failed to delete" not in caplog.text
128+
cli(["delete_bdev", "-b", bdev + "_ipv6", "-f"])
129+
assert "Failed to delete" not in caplog.text
130+
cli(["delete_bdev", "-b", bdev1 + "_ipv6", "--force"])
131+
assert "Failed to delete" not in caplog.text
96132

97133
def test_delete_subsystem(self, caplog, gateway):
98134
cli(["delete_subsystem", "-n", subsystem])
@@ -106,6 +142,10 @@ def test_create_bdev_ana(self, caplog, gateway):
106142
cli(["create_bdev", "-i", image, "-p", pool, "-b", bdev])
107143
assert "Failed to create" not in caplog.text
108144

145+
def test_create_bdev_ana_ipv6(self, caplog, gateway):
146+
cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev + "_ipv6"])
147+
assert "Failed to create" not in caplog.text
148+
109149

110150
def test_create_subsystem_ana(self, caplog, gateway):
111151
cli(["create_subsystem", "-n", subsystem, "-a", "-t"])
@@ -137,6 +177,8 @@ def test_remove_namespace_ana(self, caplog, gateway):
137177
def test_delete_bdev_ana(self, caplog, gateway):
138178
cli(["delete_bdev", "-b", bdev, "-f"])
139179
assert "Failed to delete" not in caplog.text
180+
cli(["delete_bdev", "-b", bdev + "_ipv6", "-f"])
181+
assert "Failed to delete" not in caplog.text
140182

141183
def test_delete_subsystem_ana(self, caplog, gateway):
142184
cli(["delete_subsystem", "-n", subsystem])

0 commit comments

Comments
 (0)