diff --git a/control/cli.py b/control/cli.py index 97fbc317..26bfdcfc 100644 --- a/control/cli.py +++ b/control/cli.py @@ -175,6 +175,19 @@ def create_bdev(self, args): ret = self.stub.create_bdev(req) self.logger.info(f"Created bdev {ret.bdev_name}: {ret.status}") + @cli.cmd([ + argument("-b", "--bdev", help="Bdev name", required=True), + argument("-s", "--size", help="New size in MiB", type=int, required=True), + ]) + def resize_bdev(self, args): + """Resizes a bdev.""" + req = pb2.resize_bdev_req( + bdev_name=args.bdev, + new_size=args.size, + ) + ret = self.stub.resize_bdev(req) + self.logger.info(f"Resized bdev {args.bdev}: {ret.status}") + @cli.cmd([ argument("-b", "--bdev", help="Bdev name", required=True), argument("-f", "--force", help="Delete any namespace using this bdev before deleting bdev", action='store_true', required=False), @@ -287,16 +300,17 @@ def remove_host(self, args): ]) def create_listener(self, args): """Creates a listener for a subsystem at a given IP/Port.""" + traddr = GatewayConfig.escape_address_if_ipv6(args.traddr) req = pb2.create_listener_req( nqn=args.subnqn, gateway_name=args.gateway_name, trtype=args.trtype, adrfam=args.adrfam, - traddr=args.traddr, + traddr=traddr, trsvcid=args.trsvcid, ) ret = self.stub.create_listener(req) - self.logger.info(f"Created {args.subnqn} listener: {ret.status}") + self.logger.info(f"Created {args.subnqn} listener at {traddr}:{args.trsvcid}: {ret.status}") @cli.cmd([ argument("-n", "--subnqn", help="Subsystem NQN", required=True), @@ -308,17 +322,17 @@ def create_listener(self, args): ]) def delete_listener(self, args): """Deletes a listener from a subsystem at a given IP/Port.""" + traddr = GatewayConfig.escape_address_if_ipv6(args.traddr) req = pb2.delete_listener_req( nqn=args.subnqn, gateway_name=args.gateway_name, trtype=args.trtype, adrfam=args.adrfam, - traddr=args.traddr, + traddr=traddr, trsvcid=args.trsvcid, ) ret = self.stub.delete_listener(req) - self.logger.info( - f"Deleted {args.traddr} from {args.subnqn}: {ret.status}") + self.logger.info(f"Deleted {traddr}:{args.trsvcid} from {args.subnqn}: {ret.status}") @cli.cmd() def get_subsystems(self, args): diff --git a/control/grpc.py b/control/grpc.py index c02142f0..3e8c7d86 100644 --- a/control/grpc.py +++ b/control/grpc.py @@ -177,6 +177,27 @@ def create_bdev_safe(self, request, context=None): def create_bdev(self, request, context=None): return self.execute_grpc_function(self.create_bdev_safe, request, context) + def resize_bdev_safe(self, request): + """Resizes a bdev.""" + + self.logger.info(f"Received request to resize bdev {request.bdev_name} to size {request.new_size} MiB") + try: + ret = rpc_bdev.bdev_rbd_resize( + self.spdk_rpc_client, + name=request.bdev_name, + new_size=request.new_size, + ) + self.logger.info(f"resize_bdev: {request.bdev_name}: {ret}") + except Exception as ex: + self.logger.error(f"resize_bdev failed with: \n {ex}") + return pb2.req_status() + + return pb2.req_status(status=ret) + + def resize_bdev(self, request, context=None): + with self.rpc_lock: + return self.resize_bdev_safe(request) + def get_bdev_namespaces(self, bdev_name) -> list: ns_list = [] local_state_dict = self.gateway_state.local.get_state() @@ -619,7 +640,7 @@ def create_listener_safe(self, request, context=None): ret = True traddr = GatewayConfig.escape_address_if_ipv6(request.traddr) self.logger.info(f"Received request to create {request.gateway_name}" - f" {request.trtype} listener for {request.nqn} at" + f" {request.trtype} {request.adrfam} listener for {request.nqn} at" f" {traddr}:{request.trsvcid}., context: {context}") if self.is_discovery_nqn(request.nqn): diff --git a/control/proto/gateway.proto b/control/proto/gateway.proto index 418d7e15..3212a0ea 100644 --- a/control/proto/gateway.proto +++ b/control/proto/gateway.proto @@ -14,6 +14,9 @@ service Gateway { // Creates a bdev from an RBD image rpc create_bdev(create_bdev_req) returns (bdev) {} + // Resizes a bdev + rpc resize_bdev(resize_bdev_req) returns (req_status) {} + // Deletes a bdev rpc delete_bdev(delete_bdev_req) returns (req_status) {} @@ -64,6 +67,11 @@ message create_bdev_req { optional string uuid = 5; } +message resize_bdev_req { + string bdev_name = 1; + int32 new_size = 2; +} + message delete_bdev_req { string bdev_name = 1; bool force = 2; diff --git a/tests/test_cli.py b/tests/test_cli.py index 6ff3f1ec..3ad304d6 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -2,17 +2,20 @@ from control.server import GatewayServer import socket from control.cli import main as cli +import spdk.rpc.bdev as rpc_bdev image = "mytestdevimage" pool = "rbd" bdev = "Ceph0" bdev1 = "Ceph1" +bdev_ipv6 = bdev + "_ipv6" +bdev1_ipv6 = bdev1 + "_ipv6" subsystem = "nqn.2016-06.io.spdk:cnode1" subsystem2 = "nqn.2016-06.io.spdk:cnode2" serial = "SPDK00000000000001" host_list = ["nqn.2016-06.io.spdk:host1", "*"] nsid = "1" -nsid_ipv6 = "2" +nsid_ipv6 = "3" anagrpid = "2" trtype = "TCP" gateway_name = socket.gethostname() @@ -31,7 +34,7 @@ def gateway(config): # Start gateway gateway.serve() - yield + yield gateway.gateway_rpc # Stop gateway gateway.server.stop(grace=1) @@ -39,156 +42,247 @@ def gateway(config): class TestGet: def test_get_subsystems(self, caplog, gateway): + caplog.clear() cli(["get_subsystems"]) - assert "Failed to get" not in caplog.text + assert "[]" in caplog.text def test_get_subsystems_ipv6(self, caplog, gateway): + caplog.clear() cli(["--server-address", server_addr_ipv6, "get_subsystems"]) - assert "Failed to get" not in caplog.text + assert "[]" in caplog.text class TestCreate: def test_create_bdev(self, caplog, gateway): + gw = gateway + bdev_found = False + caplog.clear() cli(["create_bdev", "-i", image, "-p", pool, "-b", bdev]) - assert "Failed to create" not in caplog.text - cli(["create_bdev", "-i", image, "-p", pool, "-b", bdev1]) - assert "Failed to create" not in caplog.text + assert f"Created bdev {bdev}: True" in caplog.text + bdev_list = rpc_bdev.bdev_get_bdevs(gw.spdk_rpc_client) + for onedev in bdev_list: + if onedev["name"] == bdev: + bdev_found = True + assert onedev["block_size"] == 512 + break + assert bdev_found + caplog.clear() + bdev_found = False + cli(["create_bdev", "-i", image, "-p", pool, "-b", bdev1, "-s", "1024"]) + assert f"Created bdev {bdev1}: True" in caplog.text + bdev_list = rpc_bdev.bdev_get_bdevs(gw.spdk_rpc_client) + for onedev in bdev_list: + if onedev["name"] == bdev1: + bdev_found = True + assert onedev["block_size"] == 1024 + break + assert bdev_found def test_create_bdev_ipv6(self, caplog, gateway): - cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev + "_ipv6"]) - assert "Failed to create" not in caplog.text - cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev1 + "_ipv6"]) - assert "Failed to create" not in caplog.text + caplog.clear() + cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev_ipv6]) + assert f"Created bdev {bdev_ipv6}: True" in caplog.text + cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev1_ipv6]) + assert f"Created bdev {bdev1_ipv6}: True" in caplog.text + + def test_resize_bdev(self, caplog, gateway): + caplog.clear() + bdev_found = False + gw = gateway + cli(["resize_bdev", "-b", bdev, "-s", "20"]) + assert f"Resized bdev {bdev}: True" in caplog.text + bdev_list = rpc_bdev.bdev_get_bdevs(gw.spdk_rpc_client) + for onedev in bdev_list: + if onedev["name"] == bdev: + bdev_found = True + assert onedev["block_size"] == 512 + num_blocks = onedev["num_blocks"] + # Should be 20M now + assert num_blocks * 512 == 20971520 + break + assert bdev_found def test_create_subsystem(self, caplog, gateway): + caplog.clear() cli(["create_subsystem", "-n", subsystem]) - assert "Failed to create" not in caplog.text + assert f"Created subsystem {subsystem}: True" in caplog.text assert "ana reporting: False" in caplog.text cli(["get_subsystems"]) assert serial not in caplog.text caplog.clear() cli(["create_subsystem", "-n", subsystem2, "-s", serial]) - assert "Failed to create" not in caplog.text + assert f"Created subsystem {subsystem2}: True" in caplog.text assert "ana reporting: False" in caplog.text + caplog.clear() cli(["get_subsystems"]) assert serial in caplog.text def test_add_namespace(self, caplog, gateway): + caplog.clear() cli(["add_namespace", "-n", subsystem, "-b", bdev]) - assert "Failed to add" not in caplog.text + assert f"Added namespace 1 to {subsystem}, ANA group id None : True" in caplog.text cli(["add_namespace", "-n", subsystem, "-b", bdev1]) - assert "Failed to add" not in caplog.text + assert f"Added namespace 2 to {subsystem}, ANA group id None : True" in caplog.text def test_add_namespace_ipv6(self, caplog, gateway): - cli(["--server-address", server_addr_ipv6, "add_namespace", "-n", subsystem, "-b", bdev + "_ipv6"]) - assert "Failed to add" not in caplog.text - cli(["--server-address", server_addr_ipv6, "add_namespace", "-n", subsystem, "-b", bdev1 + "_ipv6"]) - assert "Failed to add" not in caplog.text + caplog.clear() + cli(["--server-address", server_addr_ipv6, "add_namespace", "-n", subsystem, "-b", bdev_ipv6]) + assert f"Added namespace 3 to {subsystem}, ANA group id None : True" in caplog.text + cli(["--server-address", server_addr_ipv6, "add_namespace", "-n", subsystem, "-b", bdev1_ipv6]) + assert f"Added namespace 4 to {subsystem}, ANA group id None : True" in caplog.text @pytest.mark.parametrize("host", host_list) def test_add_host(self, caplog, host): + caplog.clear() cli(["add_host", "-n", subsystem, "-t", host]) - assert "Failed to add" not in caplog.text + if host == "*": + assert f"Allowed open host access to {subsystem}: True" in caplog.text + else: + assert f"Added host {host} access to {subsystem}: True" in caplog.text @pytest.mark.parametrize("listener", listener_list) def test_create_listener(self, caplog, listener, gateway): + caplog.clear() cli(["create_listener", "-n", subsystem] + listener) - assert "Failed to create" not in caplog.text + assert "enable_ha: False" in caplog.text + assert "ipv4" in caplog.text + assert f"Created {subsystem} listener at {listener[3]}:{listener[5]}: True" in caplog.text @pytest.mark.parametrize("listener_ipv6", listener_list_ipv6) def test_create_listener_ipv6(self, caplog, listener_ipv6, gateway): + caplog.clear() cli(["--server-address", server_addr_ipv6, "create_listener", "-n", subsystem, "--adrfam", "IPV6"] + listener_ipv6) - assert "Failed to create" not in caplog.text - + assert "enable_ha: False" in caplog.text + assert "IPV6" in caplog.text + assert f"Created {subsystem} listener at [{listener_ipv6[3]}]:{listener_ipv6[5]}: True" in caplog.text class TestDelete: @pytest.mark.parametrize("host", host_list) def test_remove_host(self, caplog, host, gateway): + caplog.clear() cli(["remove_host", "-n", subsystem, "-t", host]) - assert "Failed to remove" not in caplog.text + if host == "*": + assert f"Disabled open host access to {subsystem}: True" in caplog.text + else: + assert f"Removed host {host} access from {subsystem}: True" in caplog.text @pytest.mark.parametrize("listener", listener_list) def test_delete_listener(self, caplog, listener, gateway): + caplog.clear() cli(["delete_listener", "-n", subsystem] + listener) - assert "Failed to delete" not in caplog.text + assert f"Deleted {listener[3]}:{listener[5]} from {subsystem}: True" in caplog.text @pytest.mark.parametrize("listener_ipv6", listener_list_ipv6) def test_delete_listener_ipv6(self, caplog, listener_ipv6, gateway): + caplog.clear() cli(["--server-address", server_addr_ipv6, "delete_listener", "-n", subsystem, "--adrfam", "IPV6"] + listener_ipv6) - assert "Failed to delete" not in caplog.text + assert f"Deleted [{listener_ipv6[3]}]:{listener_ipv6[5]} from {subsystem}: True" in caplog.text def test_remove_namespace(self, caplog, gateway): + caplog.clear() cli(["remove_namespace", "-n", subsystem, "-i", nsid]) - assert "Failed to remove" not in caplog.text + assert f"Removed namespace {nsid} from {subsystem}: True" in caplog.text cli(["remove_namespace", "-n", subsystem, "-i", nsid_ipv6]) - assert "Failed to remove" not in caplog.text + assert f"Removed namespace {nsid_ipv6} from {subsystem}: True" in caplog.text def test_delete_bdev(self, caplog, gateway): + caplog.clear() cli(["delete_bdev", "-b", bdev, "-f"]) - assert "Failed to delete" not in caplog.text + assert f"Deleted bdev {bdev}: True" in caplog.text + assert "Will remove namespace" not in caplog.text + caplog.clear() + # Should fail as there is a namespace using the bdev + with pytest.raises(Exception) as ex: + try: + cli(["delete_bdev", "-b", bdev1]) + except SystemExit as sysex: + # should fail with non-zero return code + assert sysex != 0 + pass + assert "Device or resource busy" in str(ex.value) + assert f"Namespace 2 from {subsystem} is still using bdev {bdev1}" in caplog.text + caplog.clear() cli(["delete_bdev", "-b", bdev1, "--force"]) - assert "Failed to delete" not in caplog.text - cli(["delete_bdev", "-b", bdev + "_ipv6", "-f"]) - assert "Failed to delete" not in caplog.text - cli(["delete_bdev", "-b", bdev1 + "_ipv6", "--force"]) - assert "Failed to delete" not in caplog.text + assert f"Deleted bdev {bdev1}: True" in caplog.text + assert f"Removed namespace 2 from {subsystem}" in caplog.text + caplog.clear() + cli(["delete_bdev", "-b", bdev_ipv6, "-f"]) + assert f"Deleted bdev {bdev_ipv6}: True" in caplog.text + assert "Will remove namespace" not in caplog.text + caplog.clear() + cli(["delete_bdev", "-b", bdev1_ipv6, "--force"]) + assert f"Deleted bdev {bdev1_ipv6}: True" in caplog.text + assert f"Removed namespace 4 from {subsystem}" in caplog.text def test_delete_subsystem(self, caplog, gateway): + caplog.clear() cli(["delete_subsystem", "-n", subsystem]) - assert "Failed to delete" not in caplog.text + assert f"Deleted subsystem {subsystem}: True" in caplog.text + caplog.clear() cli(["delete_subsystem", "-n", subsystem2]) - assert "Failed to delete" not in caplog.text - + assert f"Deleted subsystem {subsystem2}: True" in caplog.text class TestCreateWithAna: def test_create_bdev_ana(self, caplog, gateway): + caplog.clear() cli(["create_bdev", "-i", image, "-p", pool, "-b", bdev]) - assert "Failed to create" not in caplog.text + assert f"Created bdev {bdev}: True" in caplog.text def test_create_bdev_ana_ipv6(self, caplog, gateway): - cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev + "_ipv6"]) - assert "Failed to create" not in caplog.text - + caplog.clear() + cli(["--server-address", server_addr_ipv6, "create_bdev", "-i", image, "-p", pool, "-b", bdev_ipv6]) + assert f"Created bdev {bdev_ipv6}: True" in caplog.text def test_create_subsystem_ana(self, caplog, gateway): caplog.clear() cli(["create_subsystem", "-n", subsystem, "-a", "-t"]) - assert "Failed to create" not in caplog.text + assert f"Created subsystem {subsystem}: True" in caplog.text assert "ana reporting: True" in caplog.text + caplog.clear() cli(["get_subsystems"]) assert serial not in caplog.text def test_add_namespace_ana(self, caplog, gateway): + caplog.clear() cli(["add_namespace", "-n", subsystem, "-b", bdev, "-a", anagrpid]) - assert "Failed to add" not in caplog.text + assert f"Added namespace 1 to {subsystem}, ANA group id {anagrpid}" in caplog.text @pytest.mark.parametrize("listener", listener_list) def test_create_listener_ana(self, caplog, listener, gateway): + caplog.clear() cli(["create_listener", "-n", subsystem] + listener) - assert "Failed to create" not in caplog.text assert "enable_ha: True" in caplog.text + assert "ipv4" in caplog.text + assert f"Created {subsystem} listener at {listener[3]}:{listener[5]}: True" in caplog.text class TestDeleteAna: @pytest.mark.parametrize("listener", listener_list) def test_delete_listener_ana(self, caplog, listener, gateway): + caplog.clear() cli(["delete_listener", "-n", subsystem] + listener) - assert "Failed to delete" not in caplog.text + assert f"Deleted {listener[3]}:{listener[5]} from {subsystem}: True" in caplog.text def test_remove_namespace_ana(self, caplog, gateway): + caplog.clear() cli(["remove_namespace", "-n", subsystem, "-i", nsid]) - assert "Failed to remove" not in caplog.text + assert f"Removed namespace 1 from {subsystem}: True" in caplog.text def test_delete_bdev_ana(self, caplog, gateway): + caplog.clear() cli(["delete_bdev", "-b", bdev, "-f"]) - assert "Failed to delete" not in caplog.text - cli(["delete_bdev", "-b", bdev + "_ipv6", "-f"]) - assert "Failed to delete" not in caplog.text + assert f"Deleted bdev {bdev}: True" in caplog.text + assert "Will remove namespace" not in caplog.text + caplog.clear() + cli(["delete_bdev", "-b", bdev_ipv6, "-f"]) + assert f"Deleted bdev {bdev_ipv6}: True" in caplog.text + assert "Will remove namespace" not in caplog.text def test_delete_subsystem_ana(self, caplog, gateway): + caplog.clear() cli(["delete_subsystem", "-n", subsystem]) - assert "Failed to delete" not in caplog.text + assert f"Deleted subsystem {subsystem}: True" in caplog.text class TestSDKLOg: def test_log_flags(self, caplog, gateway):