iscsi: Add an iscsi target application

Similar to our NVMf target, this is an iSCSI target that
can interoperate with the Linux and Windows standard iSCSI
initiators.

Change-Id: I6961c5ef99f7b161c396330ed5b543ea29b0ca7b
Signed-off-by: Ben Walker <benjamin.walker@intel.com>
This commit is contained in:
Ben Walker 2016-08-03 14:37:16 -07:00
parent b91c6e0e4a
commit 1e92d78a10
22 changed files with 1836 additions and 4 deletions

@ -36,6 +36,9 @@ include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
DIRS-y += trace
DIRS-$(CONFIG_RDMA) += nvmf_tgt
ifeq ($(OS),Linux)
DIRS-y += iscsi_tgt
endif
.PHONY: all clean $(DIRS-y)

1
app/iscsi_tgt/.gitignore vendored Normal file

@ -0,0 +1 @@
iscsi_tgt

79
app/iscsi_tgt/Makefile Normal file

@ -0,0 +1,79 @@
#
# BSD LICENSE
#
# Copyright (c) Intel Corporation.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of Intel Corporation nor the names of its
# contributors may be used to endorse or promote products derived
# from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
SPDK_ROOT_DIR := $(abspath $(CURDIR)/../..)
include $(SPDK_ROOT_DIR)/mk/spdk.common.mk
include $(SPDK_ROOT_DIR)/mk/spdk.modules.mk
APP = iscsi_tgt
CFLAGS += $(DPDK_INC)
# Add iSCSI library directory to include path
# TODO: remove this once iSCSI has a public API header
CFLAGS += -I$(SPDK_ROOT_DIR)/lib
C_SRCS := iscsi_tgt.c
SPDK_LIBS = \
$(SPDK_ROOT_DIR)/lib/json/libspdk_json.a \
$(SPDK_ROOT_DIR)/lib/jsonrpc/libspdk_jsonrpc.a \
$(SPDK_ROOT_DIR)/lib/rpc/libspdk_rpc.a \
$(SPDK_ROOT_DIR)/lib/bdev/libspdk_bdev.a \
$(SPDK_ROOT_DIR)/lib/iscsi/libspdk_iscsi.a \
$(SPDK_ROOT_DIR)/lib/scsi/libspdk_scsi.a \
$(SPDK_ROOT_DIR)/lib/net/libspdk_net.a \
$(SPDK_ROOT_DIR)/lib/copy/libspdk_copy.a \
$(SPDK_ROOT_DIR)/lib/trace/libspdk_trace.a \
$(SPDK_ROOT_DIR)/lib/conf/libspdk_conf.a \
$(SPDK_ROOT_DIR)/lib/util/libspdk_util.a \
$(SPDK_ROOT_DIR)/lib/memory/libspdk_memory.a \
$(SPDK_ROOT_DIR)/lib/log/libspdk_log.a \
$(SPDK_ROOT_DIR)/lib/log/rpc/libspdk_log_rpc.a \
$(SPDK_ROOT_DIR)/lib/event/libspdk_event.a \
$(SPDK_ROOT_DIR)/lib/event/rpc/libspdk_app_rpc.a \
LIBS += -Wl,--whole-archive $(SPDK_LIBS) -Wl,--no-whole-archive
LIBS += -lcrypto $(PCIACCESS_LIB) $(DPDK_LIB)
LIBS += $(BLOCKDEV_MODULES_LINKER_ARGS) \
$(COPY_MODULES_LINKER_ARGS)
all : $(APP)
$(APP) : $(OBJS) $(SPDK_LIBS)
$(LINK_C)
clean :
$(CLEAN_C) $(APP)
include $(SPDK_ROOT_DIR)/mk/spdk.deps.mk

243
app/iscsi_tgt/iscsi_tgt.c Normal file

@ -0,0 +1,243 @@
/*-
* BSD LICENSE
*
* Copyright (c) Intel Corporation.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Intel Corporation nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <inttypes.h>
#include <stdint.h>
#include <signal.h>
#include <sys/types.h>
#include <rte_config.h>
#include <rte_memzone.h>
#include <rte_eal.h>
#include <rte_lcore.h>
#include <rte_malloc.h>
#include <rte_memory.h>
#include "spdk/event.h"
#include "spdk/net.h"
#include "iscsi/iscsi.h"
#include "spdk/scsi.h"
#include "spdk/log.h"
#include "spdk/bdev.h"
#include "spdk/copy_engine.h"
uint64_t g_flush_timeout;
static void
spdk_iscsi_dump_memory_info(void)
{
struct rte_malloc_socket_stats stats;
int i;
for (i = 0; i < RTE_MAX_NUMA_NODES; i++) {
rte_malloc_get_socket_stats(i, &stats);
if (stats.heap_totalsz_bytes > 0)
fprintf(stderr, "Socket %d: Total memory %"PRIu64" MB,"
" Free memory %"PRIu64" MB\n",
i, stats.heap_totalsz_bytes >> 20,
stats.heap_freesz_bytes >> 20);
}
}
static void
spdk_sigusr1(int signo __attribute__((__unused__)))
{
char *config_str = NULL;
if (spdk_app_get_running_config(&config_str, "iscsi.conf") < 0)
fprintf(stderr, "Error getting config\n");
else {
fprintf(stdout, "============================\n");
fprintf(stdout, " iSCSI target running config\n");
fprintf(stdout, "=============================\n");
fprintf(stdout, "%s", config_str);
}
free(config_str);
}
static void
usage(char *executable_name)
{
printf("%s [options]\n", executable_name);
printf("options:\n");
printf(" -c config config file (default %s)\n", SPDK_ISCSI_DEFAULT_CONFIG);
printf(" -e mask tracepoint group mask for spdk trace buffers (default 0x0)\n");
printf(" -m mask core mask for DPDK\n");
printf(" -i instance ID\n");
printf(" -l facility use specific syslog facility (default %s)\n",
SPDK_APP_DEFAULT_LOG_FACILITY);
printf(" -n channel number of memory channels used for DPDK\n");
printf(" -p core master (primary) core for DPDK\n");
printf(" -s size memory size in MB for DPDK\n");
#ifdef DEBUG
printf(" -t flag trace flag (all, net, iscsi, scsi, target, debug)\n");
#else
printf(" -t flag trace flag (not supported - must rebuild with CONFIG_DEBUG=y)\n");
#endif
printf(" -v verbose (enable warnings)\n");
printf(" -H show this usage\n");
printf(" -V show version\n");
printf(" -d disable coredump file enabling\n");
}
/*! \file
This is the main file.
*/
/*!
\brief This is the main function for the iSCSI server application.
\msc
c_runtime [label="C Runtime"],libuns,dpdk [label="DPDK"], iSCSI [label="iSCSI Server"];
c_runtime=>libuns [label="__msa_init()"];
libuns=>dpdk [label="rte_eal_init()"];
libuns<<dpdk;
c_runtime<<libuns;
c_runtime=>iSCSI [label="main()"];
iSCSI=>iSCSI [label="spdk_dpdk_framework_init()"];
iSCSI=>iSCSI [label="spdk_app_init()"];
iSCSI=>iSCSI [label="spdk_event_allocate()"];
iSCSI=>iSCSI [label="spdk_app_start()"];
iSCSI=>iSCSI [label="spdk_app_fini()"];
c_runtime<<iSCSI;
\endmsc
*/
static void
spdk_startup(spdk_event_t event)
{
if (getenv("MEMZONE_DUMP") != NULL) {
rte_memzone_dump(stdout);
fflush(stdout);
}
/* Dump socket memory information */
spdk_iscsi_dump_memory_info();
}
int
main(int argc, char **argv)
{
int ch;
int rc;
struct spdk_app_opts opts = {};
/* default value in opts structure */
spdk_app_opts_init(&opts);
opts.config_file = SPDK_ISCSI_DEFAULT_CONFIG;
opts.name = "iscsi";
while ((ch = getopt(argc, argv, "c:de:i:l:m:n:p:qs:t:H")) != -1) {
switch (ch) {
case 'd':
opts.enable_coredump = false;
break;
case 'c':
opts.config_file = optarg;
break;
case 'i':
opts.instance_id = atoi(optarg);
break;
case 'l':
opts.log_facility = optarg;
break;
case 't':
rc = spdk_log_set_trace_flag(optarg);
if (rc < 0) {
fprintf(stderr, "unknown flag\n");
usage(argv[0]);
exit(EXIT_FAILURE);
}
#ifndef DEBUG
fprintf(stderr, "%s must be built with CONFIG_DEBUG=y for -t flag\n",
argv[0]);
usage(argv[0]);
exit(EXIT_FAILURE);
#endif
break;
case 'e':
opts.tpoint_group_mask = optarg;
break;
case 'q':
spdk_g_notice_stderr_flag = 0;
break;
case 'm':
opts.reactor_mask = optarg;
break;
case 'n':
opts.dpdk_mem_channel = atoi(optarg);
break;
case 'p':
opts.dpdk_master_core = atoi(optarg);
break;
case 's':
opts.dpdk_mem_size = atoi(optarg);
break;
case 'H':
default:
usage(argv[0]);
exit(EXIT_SUCCESS);
}
}
if (spdk_g_notice_stderr_flag == 1 &&
isatty(STDERR_FILENO) &&
!strncmp(ttyname(STDERR_FILENO), "/dev/tty", strlen("/dev/tty"))) {
printf("Warning: printing stderr to console terminal without -q option specified.\n");
printf("Suggest using -q to disable logging to stderr and monitor syslog, or\n");
printf("redirect stderr to a file.\n");
printf("(Delaying for 10 seconds...)\n");
sleep(10);
}
optind = 1; /* reset the optind */
opts.shutdown_cb = spdk_iscsi_shutdown;
opts.usr1_handler = spdk_sigusr1;
spdk_app_init(&opts);
printf("Total cores available: %d\n", rte_lcore_count());
/* Blocks until the application is exiting */
rc = spdk_app_start(spdk_startup, NULL, NULL);
spdk_app_fini();
return rc;
}

@ -67,6 +67,21 @@ time test/lib/scsi/scsi.sh
timing_exit lib
if [ $(uname -s) = Linux ]; then
export TARGET_IP=127.0.0.1
export INITIATOR_IP=127.0.0.1
timing_enter iscsi_tgt
time ./test/iscsi_tgt/filesystem/filesystem.sh
time ./test/iscsi_tgt/fio/fio.sh
time ./test/iscsi_tgt/reset/reset.sh
time ./test/iscsi_tgt/rpc_config/rpc_config.sh
timing_exit iscsi_tgt
fi
timing_enter nvmf
time test/nvmf/fio/fio.sh

174
etc/spdk/iscsi.conf.in Normal file

@ -0,0 +1,174 @@
# iSCSI target configuration file
#
# Please write all parameters using ASCII.
# The parameter must be quoted if it includes whitespace.
#
# Configuration syntax:
# Leading whitespace is ignored.
# Lines starting with '#' are comments.
# Lines ending with '\' are concatenated with the next line.
# Bracketed ([]) names define sections
[Global]
# Instance ID for multi-process support
# Default: 0
#InstanceID 0
# Users can restrict work items to only run on certain cores by
# specifying a ReactorMask. Default is to allow work items to run
# on all cores.
#ReactorMask 0xFFFF
# Tracepoint group mask for spdk trace buffers
# Default: 0x0 (all tracepoint groups disabled)
# Set to 0xFFFFFFFFFFFFFFFF to enable all tracepoint groups.
#TpointGroupMask 0x0
# syslog facility
LogFacility "local7"
[iSCSI]
# node name (not include optional part)
# Users can optionally change this to fit their environment.
NodeBase "iqn.2016-06.io.spdk"
AuthFile /usr/local/etc/spdk/auth.conf
MinConnectionsPerCore 4
# Power saving related variable, this parameter defines how long an iSCSI
# connection must be idle before moving it to a state where it will consume
# less power. This variable is defined in terms of microseconds. We set default
# value as 5ms.
MinConnectionIdleInterval 5000
# Socket I/O timeout sec. (0 is infinite)
Timeout 30
# authentication information for discovery session
DiscoveryAuthMethod Auto
#MaxSessions 128
#MaxConnectionsPerSession 2
# iSCSI initial parameters negotiate with initiators
# NOTE: incorrect values might crash
DefaultTime2Wait 2
DefaultTime2Retain 60
ImmediateData Yes
ErrorRecoveryLevel 0
[Rpc]
# Defines whether to enable configuration via RPC.
# Default is disabled. Note that the RPC interface is not
# authenticated, so users should be careful about enabling
# RPC in non-trusted environments.
#Enable No
# Users must change the PortalGroup section(s) to match the IP addresses
# for their environment.
# PortalGroup sections define which TCP ports the iSCSI server will use
# to listen for incoming connections. These are also used to determine
# which targets are accessible over each portal group.
[PortalGroup1]
Portal DA1 192.168.2.21:3260
# Users must change the InitiatorGroup section(s) to match the IP
# addresses and initiator configuration in their environment.
# Netmask can be used to specify a single IP address or a range of IP addresses
# Netmask 192.168.1.20 <== single IP address
# Netmask 192.168.1.0/24 <== IP range 192.168.1.*
[InitiatorGroup1]
InitiatorName ALL
Netmask 192.168.2.0/24
# NVMe configuration options
[Nvme]
# NVMe Device Whitelist
# Users may specify which NVMe devices to claim by their PCI
# domain, bus, device, and function. The format is dddd:bb:dd.f, which is
# the same format displayed by lspci or in /sys/bus/pci/devices. The second
# argument is a "name" for the device that can be anything. The name
# is referenced later in the Subsystem section.
#
# Alternatively, the user can specify ClaimAllDevices. All
# NVMe devices will be claimed and named Nvme0, Nvme1, etc.
BDF 0000:00:00.0 Nvme0
BDF 0000:01:00.0 Nvme1
# If 'Yes', iscsi will automatically unbind the kernel NVMe driver from
# discovered devices and rebind it to the uio driver.
UnbindFromKernel Yes
# The following two arguments allow the user to partition NVMe namespaces
# into multiple LUNs
NvmeLunsPerNs 1
LunSizeInMB 1024
# The number of attempts per I/O when an I/O fails. Do not include
# this key to get the default behavior.
NvmeRetryCount 4
# The maximum number of NVMe controllers to claim. Do not include this key to
# claim all of them.
NumControllers 2
# Users may change this section to create a different number or size of
# malloc LUNs.
# If the system has hardware DMA engine, it will use an IOAT
# (i.e. Crystal Beach DMA) channel to do the copy instead of memcpy.
# Of course, users can disable offload even it is available.
[Malloc]
# Number of Malloc targets
NumberOfLuns 1
# Malloc targets are 128M
LunSizeInMB 128
# Block size. Default is 512 bytes.
BlockSize 4096
# Users may not want to use offload even it is available.
# Users may use the whitelist to initialize specified devices, IDS
# uses BUS:DEVICE.FUNCTION to identify each Ioat channel.
# Users can set the option "UnbindFromKernel Yes", iscsi will automatically
# unbind from the Ioat driver and bind the device to uio driver.
[Ioat]
Disable Yes
Whitelist 00:04.0
Whitelist 00:04.1
UnbindFromKernel Yes
# Users must change this section to match the /dev/sdX devices to be
# exported as iSCSI LUNs. The devices are accessed using Linux AIO.
[AIO]
AIO /dev/sdb
AIO /dev/sdc
# Users should change the TargetNode section(s) below to match the
# desired iSCSI target node configuration.
# TargetName, Mapping, LUN0 are minimum required
[TargetNode1]
TargetName disk1
TargetAlias "Data Disk1"
Mapping PortalGroup1 InitiatorGroup1
AuthMethod Auto
AuthGroup AuthGroup1
# Enable header and data digest
# UseDigest Header Data
UseDigest Auto
# Use the first malloc target
LUN0 Malloc0
# Using the first AIO target
LUN1 AIO0
# Using the second storage target
LUN2 AIO1
# Using the third storage target
LUN3 AIO2
QueueDepth 128
[TargetNode2]
TargetName disk2
TargetAlias "Data Disk2"
Mapping PortalGroup1 InitiatorGroup1
AuthMethod Auto
AuthGroup AuthGroup1
UseDigest Auto
LUN0 Nvme0
QueueDepth 32

@ -1372,9 +1372,9 @@ SPDK_TRACE_REGISTER_FN(iscsi_conn_trace)
spdk_trace_register_description("READ PDU", "", TRACE_READ_PDU,
OWNER_ISCSI_CONN, OBJECT_ISCSI_PDU, 1, 0, 0, "opc: ");
spdk_trace_register_description("ISCSI TASK DONE", "", TRACE_ISCSI_TASK_DONE,
OWNER_ISCSI_CONN, OBJECT_ISCSI_TASK, 0, 0, 0, "");
OWNER_ISCSI_CONN, OBJECT_SCSI_TASK, 0, 0, 0, "");
spdk_trace_register_description("ISCSI TASK QUEUE", "", TRACE_ISCSI_TASK_QUEUE,
OWNER_ISCSI_CONN, OBJECT_ISCSI_TASK, 1, 1, 0, "pdu: ");
OWNER_ISCSI_CONN, OBJECT_SCSI_TASK, 1, 1, 0, "pdu: ");
spdk_trace_register_description("ISCSI CONN ACTIVE", "", TRACE_ISCSI_CONN_ACTIVE,
OWNER_ISCSI_CONN, OBJECT_NONE, 0, 0, 0, "");
spdk_trace_register_description("ISCSI CONN IDLE", "", TRACE_ISCSI_CONN_IDLE,

@ -58,7 +58,6 @@
#define OWNER_ISCSI_CONN 0x1
#define OBJECT_ISCSI_PDU 0x1
#define OBJECT_ISCSI_TASK 0x2
#define TRACE_GROUP_ISCSI 0x1
#define TRACE_READ_FROM_SOCKET_DONE SPDK_TPOINT_ID(TRACE_GROUP_ISCSI, 0x0)

@ -51,7 +51,7 @@
#define SPDK_ISCSI_BUILD_ETC "/usr/local/etc/spdk"
#define SPDK_ISCSI_DEFAULT_CONFIG SPDK_ISCSI_BUILD_ETC "/iscsi.conf"
#define SPDK_ISCSI_DEFAULT_AUTHFILE SPDK_ISCSI_BUILD_ETC "/auth.conf"
#define SPDK_ISCSI_DEFAULT_NODEBASE "iqn.2013-10.com.intel.spdk"
#define SPDK_ISCSI_DEFAULT_NODEBASE "iqn.2016-06.io.spdk"
extern uint64_t g_flush_timeout;

@ -102,6 +102,30 @@ function process_core() {
return $ret
}
function waitforlisten() {
# $1 = process pid
# $2 = TCP port number
if [ -z "$1" ] || [ -z "$2" ]; then
exit 1
fi
echo "Waiting for process to start up and listen on TCP port $2..."
# turn off trace for this loop
set +x
ret=1
while [ $ret -ne 0 ]; do
# if the process is no longer running, then exit the script
# since it means the application crashed
if ! kill -s 0 $1; then
exit
fi
if netstat -an --tcp | grep -iw listen | grep -q $2; then
ret=0
fi
done
set -x
}
function killprocess() {
# $1 = process pid
if [ -z "$1" ]; then
@ -127,3 +151,25 @@ function nvme_cleanup()
parted -s /dev/$dev mklabel msdos
done
}
function iscsicleanup() {
echo "Cleaning up iSCSI connection"
iscsiadm -m node --logout || true
iscsiadm -m node -o delete || true
}
function stop_iscsi_service() {
if cat /etc/*-release | grep Ubuntu; then
service open-iscsi stop
else
service iscsid stop
fi
}
function start_iscsi_service() {
if cat /etc/*-release | grep Ubuntu; then
service open-iscsi start
else
service iscsid start
fi
}

120
scripts/fio.py Executable file

@ -0,0 +1,120 @@
#!/usr/bin/env python
from subprocess import check_call, call, check_output, Popen, PIPE
import re
import sys
import signal
fio_template = """
[global]
thread=1
invalidate=1
rw=%(testtype)s
time_based=1
runtime=%(runtime)s
ioengine=libaio
direct=1
bs=%(blocksize)d
iodepth=%(iodepth)d
norandommap=1
%(verify)s
verify_dump=1
"""
verify_template = """
do_verify=1
verify=crc32c-intel
"""
fio_job_template = """
[job%(jobnumber)d]
filename=%(device)s
"""
def interrupt_handler(signum, frame):
fio.terminate()
print "FIO terminated"
sys.exit(0)
def main():
global fio
if (len(sys.argv) < 5):
print "usage:"
print " " + sys.argv[0] + " <io_size> <queue_depth> <test_type> <runtime>"
print "advanced usage:"
print "If you want to run fio with verify, please add verify string after runtime."
print "Currently fio.py only support write rw randwrite randrw with verify enabled."
sys.exit(1)
io_size = int(sys.argv[1])
queue_depth = int(sys.argv[2])
test_type = sys.argv[3]
runtime = sys.argv[4]
if len(sys.argv) > 5:
verify = True
else:
verify = False
devices = get_target_devices()
print "Found devices: ", devices
configure_devices(devices)
fio_executable = '/usr/bin/fio'
device_paths = ['/dev/' + dev for dev in devices]
sys.stdout.flush()
signal.signal(signal.SIGTERM, interrupt_handler)
signal.signal(signal.SIGINT, interrupt_handler)
fio = Popen([fio_executable, '-'], stdin=PIPE)
fio.communicate(create_fio_config(io_size, queue_depth, device_paths, test_type, runtime, verify))
fio.stdin.close()
rc = fio.wait()
print "FIO completed with code %d\n" % rc
sys.stdout.flush()
sys.exit(rc)
def get_target_devices():
output = check_output('iscsiadm -m session -P 3', shell=True)
return re.findall("Attached scsi disk (sd[a-z]+)", output)
def create_fio_config(size, q_depth, devices, test, run_time, verify):
if not verify:
verifyfio = ""
else:
verifyfio = verify_template
fiofile = fio_template % {"blocksize": size, "iodepth": q_depth,
"testtype": test, "runtime": run_time, "verify": verifyfio}
for (i, dev) in enumerate(devices):
fiofile += fio_job_template % {"jobnumber": i, "device": dev}
return fiofile
def set_device_parameter(devices, filename_template, value):
for dev in devices:
filename = filename_template % dev
f = open(filename, 'r+b')
f.write(value)
f.close()
def configure_devices(devices):
set_device_parameter(devices, "/sys/block/%s/queue/nomerges", "2")
set_device_parameter(devices, "/sys/block/%s/queue/nr_requests", "128")
requested_qd = 128
qd = requested_qd
while qd > 0:
try:
set_device_parameter(devices, "/sys/block/%s/device/queue_depth", str(qd))
break
except IOError:
qd = qd - 1
if qd == 0:
print "Could not set block device queue depths."
else:
print "Requested queue_depth {} but only {} is supported.".format(str(requested_qd), str(qd))
set_device_parameter(devices, "/sys/block/%s/queue/scheduler", "noop")
if __name__ == "__main__":
main()

316
scripts/rpc.py Executable file

@ -0,0 +1,316 @@
#!/usr/bin/env python
import argparse
import json
import socket
SPDK_JSONRPC_PORT_BASE = 5260
def print_dict(d):
print json.dumps(d, indent=2)
parser = argparse.ArgumentParser(description='SPDK RPC command line interface')
parser.add_argument('-s', dest='server_ip', help='RPC server IP address', default='127.0.0.1')
parser.add_argument('-p', dest='instance_id', help='RPC server instance ID', default=0, type=int)
subparsers = parser.add_subparsers(help='RPC methods')
def int_arg(arg):
return int(arg, 0)
def jsonrpc_call(method, params={}):
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((args.server_ip, SPDK_JSONRPC_PORT_BASE + args.instance_id))
req = {}
req['jsonrpc'] = '2.0'
req['method'] = method
req['id'] = 1
if (params):
req['params'] = params
reqstr = json.dumps(req)
s.sendall(reqstr)
buf = ''
closed = False
response = {}
while not closed:
newdata = s.recv(4096)
if (newdata == b''):
closed = True
buf += newdata
try:
response = json.loads(buf)
except ValueError:
continue # incomplete response; keep buffering
break
s.close()
if not response:
if method == "kill_instance":
exit(0)
print "Connection closed with partial response:"
print buf
exit(1)
if 'error' in response:
print "Got JSON-RPC error response"
print "request:"
print_dict(json.loads(reqstr))
print "response:"
print_dict(response['error'])
exit(1)
return response['result']
def get_luns(args):
print_dict(jsonrpc_call('get_luns'))
p = subparsers.add_parser('get_luns', help='Display active LUNs')
p.set_defaults(func=get_luns)
def get_portal_groups(args):
print_dict(jsonrpc_call('get_portal_groups'))
p = subparsers.add_parser('get_portal_groups', help='Display current portal group configuration')
p.set_defaults(func=get_portal_groups)
def get_initiator_groups(args):
print_dict(jsonrpc_call('get_initiator_groups'))
p = subparsers.add_parser('get_initiator_groups', help='Display current initiator group configuration')
p.set_defaults(func=get_initiator_groups)
def get_target_nodes(args):
print_dict(jsonrpc_call('get_target_nodes'))
p = subparsers.add_parser('get_target_nodes', help='Display target nodes')
p.set_defaults(func=get_target_nodes)
def construct_target_node(args):
lun_name_id_dict = dict(u.split(":")
for u in args.lun_name_id_pairs.split(" "))
lun_names = lun_name_id_dict.keys()
lun_ids = list(map(int, lun_name_id_dict.values()))
pg_tags = []
ig_tags = []
for u in args.pg_ig_mappings.split(" "):
pg, ig = u.split(":")
pg_tags.append(int(pg))
ig_tags.append(int(ig))
params = {
'name': args.name,
'alias_name': args.alias_name,
'pg_tags': pg_tags,
'ig_tags': ig_tags,
'lun_names': lun_names,
'lun_ids': lun_ids,
'queue_depth': args.queue_depth,
'chap_disabled': args.chap_disabled,
'chap_required': args.chap_required,
'chap_mutual': args.chap_mutual,
'chap_auth_group': args.chap_auth_group,
}
jsonrpc_call('construct_target_node', params)
p = subparsers.add_parser('construct_target_node', help='Add a target node')
p.add_argument('name', help='Target node name (ASCII)')
p.add_argument('alias_name', help='Target node alias name (ASCII)')
p.add_argument('lun_name_id_pairs', help="""Whitespace-separated list of LUN <name:id> pairs enclosed
in quotes. Format: 'lun_name0:id0 lun_name1:id1' etc
Example: 'Malloc0:0 Malloc1:1 Malloc5:2'
*** The LUNs must pre-exist ***
*** LUN0 (id = 0) is required ***
*** LUN names cannot contain space or colon characters ***""")
p.add_argument('pg_ig_mappings', help="""List of (Portal_Group_Tag:Initiator_Group_Tag) mappings
Whitespace separated, quoted, mapping defined with colon
separated list of "tags" (int > 0)
Example: '1:1 2:2 2:1'
*** The Portal/Initiator Groups must be precreated ***""")
p.add_argument('queue_depth', help='Desired target queue depth', type=int)
p.add_argument('chap_disabled', help="""CHAP authentication should be disabled for this target node.
*** Mutually exclusive with chap_required ***""", type=int)
p.add_argument('chap_required', help="""CHAP authentication should be required for this target node.
*** Mutually exclusive with chap_disabled ***""", type=int)
p.add_argument('chap_mutual', help='CHAP authentication should be mutual/bidirectional.', type=int)
p.add_argument('chap_auth_group', help="""Authentication group ID for this target node.
*** Authentication group must be precreated ***""", type=int)
p.set_defaults(func=construct_target_node)
def construct_malloc_lun(args):
num_blocks = (args.total_size * 1024 * 1024) / args.block_size
params = {'num_blocks': num_blocks, 'block_size': args.block_size}
jsonrpc_call('construct_malloc_lun', params)
p = subparsers.add_parser('construct_malloc_lun', help='Add a LUN with malloc backend')
p.add_argument('total_size', help='Size of malloc LUN in MB (int > 0)', type=int)
p.add_argument('block_size', help='Block size for this LUN', type=int)
p.set_defaults(func=construct_malloc_lun)
def construct_aio_lun(args):
params = {'fname': args.fname}
jsonrpc_call('construct_aio_lun', params)
p = subparsers.add_parser('construct_aio_lun', help='Add a LUN with aio backend')
p.add_argument('fname', help='Path to device or file (ex: /dev/sda)')
p.set_defaults(func=construct_aio_lun)
def set_trace_flag(args):
params = {'flag': args.flag}
jsonrpc_call('set_trace_flag', params)
p = subparsers.add_parser('set_trace_flag', help='set trace flag')
p.add_argument('flag', help='trace mask we want to set. (for example "debug").')
p.set_defaults(func=set_trace_flag)
def clear_trace_flag(args):
params = {'flag': args.flag}
jsonrpc_call('clear_trace_flag', params)
p = subparsers.add_parser('clear_trace_flag', help='clear trace flag')
p.add_argument('flag', help='trace mask we want to clear. (for example "debug").')
p.set_defaults(func=clear_trace_flag)
def get_trace_flags(args):
print_dict(jsonrpc_call('get_trace_flags'))
p = subparsers.add_parser('get_trace_flags', help='get trace flags')
p.set_defaults(func=get_trace_flags)
def add_portal_group(args):
# parse out portal list host1:port1 host2:port2
portals = []
for p in args.portal_list:
host_port = p.split(':')
portals.append({'host': host_port[0], 'port': host_port[1]})
params = {'tag': args.tag, 'portals': portals}
jsonrpc_call('add_portal_group', params)
p = subparsers.add_parser('add_portal_group', help='Add a portal group')
p.add_argument('tag', help='Portal group tag (unique, integer > 0)', type=int)
p.add_argument('portal_list', nargs=argparse.REMAINDER, help="""List of portals in 'host:port' format, separated by whitespace
Example: '192.168.100.100:3260' '192.168.100.100:3261'""")
p.set_defaults(func=add_portal_group)
def add_initiator_group(args):
initiators = []
netmasks = []
for i in args.initiator_list.split(' '):
initiators.append(i)
for n in args.netmask_list.split(' '):
netmasks.append(n)
params = {'tag': args.tag, 'initiators': initiators, 'netmasks': netmasks}
jsonrpc_call('add_initiator_group', params)
p = subparsers.add_parser('add_initiator_group', help='Add an initiator group')
p.add_argument('tag', help='Initiator group tag (unique, integer > 0)', type=int)
p.add_argument('initiator_list', help="""Whitespace-separated list of initiator hostnames or IP addresses,
enclosed in quotes. Example: 'ALL' or '127.0.0.1 192.168.200.100'""")
p.add_argument('netmask_list', help="""Whitespace-separated list of initiator netmasks enclosed in quotes.
Example: '255.255.0.0 255.248.0.0' etc""")
p.set_defaults(func=add_initiator_group)
def delete_target_node(args):
params = {'name': args.target_node_name}
jsonrpc_call('delete_target_node', params)
p = subparsers.add_parser('delete_target_node', help='Delete a target node')
p.add_argument('target_node_name', help='Target node name to be deleted. Example: iqn.2016-06.io.spdk:disk1.')
p.set_defaults(func=delete_target_node)
def delete_portal_group(args):
params = {'tag': args.tag}
jsonrpc_call('delete_portal_group', params)
p = subparsers.add_parser('delete_portal_group', help='Delete a portal group')
p.add_argument('tag', help='Portal group tag (unique, integer > 0)', type=int)
p.set_defaults(func=delete_portal_group)
def delete_initiator_group(args):
params = {'tag': args.tag}
jsonrpc_call('delete_initiator_group', params)
p = subparsers.add_parser('delete_initiator_group', help='Delete an initiator group')
p.add_argument('tag', help='Initiator group tag (unique, integer > 0)', type=int)
p.set_defaults(func=delete_initiator_group)
def delete_lun(args):
params = {'name': args.lun_name}
jsonrpc_call('delete_lun', params)
p = subparsers.add_parser('delete_lun', help='Delete a LUN')
p.add_argument('lun_name', help='LUN name to be deleted. Example: Malloc0.')
p.set_defaults(func=delete_lun)
def get_iscsi_connections(args):
print_dict(jsonrpc_call('get_iscsi_connections'))
p = subparsers.add_parser('get_iscsi_connections', help='Display iSCSI connections')
p.set_defaults(func=get_iscsi_connections)
def get_scsi_devices(args):
print_dict(jsonrpc_call('get_scsi_devices'))
p = subparsers.add_parser('get_scsi_devices', help='Display SCSI devices')
p.set_defaults(func=get_scsi_devices)
def add_ip_address(args):
params = {'ifc_index': args.ifc_index, 'ip_address': args.ip_addr}
jsonrpc_call('add_ip_address', params)
p = subparsers.add_parser('add_ip_address', help='Add IP address')
p.add_argument('ifc_index', help='ifc index of the nic device.', type=int)
p.add_argument('ip_addr', help='ip address will be added.')
p.set_defaults(func=add_ip_address)
def delete_ip_address(args):
params = {'ifc_index': args.ifc_index, 'ip_address': args.ip_addr}
jsonrpc_call('delete_ip_address', params)
p = subparsers.add_parser('delete_ip_address', help='Delete IP address')
p.add_argument('ifc_index', help='ifc index of the nic device.', type=int)
p.add_argument('ip_addr', help='ip address will be deleted.')
p.set_defaults(func=delete_ip_address)
def get_interfaces(args):
print_dict(jsonrpc_call('get_interfaces'))
p = subparsers.add_parser('get_interfaces', help='Display current interface list')
p.set_defaults(func=get_interfaces)
def kill_instance(args):
params = {'sig_name': args.sig_name}
jsonrpc_call('kill_instance', params)
p = subparsers.add_parser('kill_instance', help='Send signal to instance')
p.add_argument('sig_name', help='signal will be sent to server.')
p.set_defaults(func=kill_instance)
args = parser.parse_args()
args.func(args)

@ -0,0 +1,99 @@
#!/usr/bin/env bash
testdir=$(readlink -f $(dirname $0))
rootdir=$testdir/../../..
source $rootdir/scripts/autotest_common.sh
if [ -z "$TARGET_IP" ]; then
echo "TARGET_IP not defined in environment"
exit 1
fi
if [ -z "$INITIATOR_IP" ]; then
echo "INITIATOR_IP not defined in environment"
exit 1
fi
timing_enter filesystem
# iSCSI target configuration
PORT=3260
RPC_PORT=5260
INITIATOR_TAG=2
INITIATOR_NAME=ALL
NETMASK=$INITIATOR_IP/32
MALLOC_LUN_SIZE=256
MALLOC_BLOCK_SIZE=512
rpc_py="python $rootdir/scripts/rpc.py"
./app/iscsi_tgt/iscsi_tgt -c $testdir/iscsi.conf &
pid=$!
echo "Process pid: $pid"
trap "process_core; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
waitforlisten $pid ${RPC_PORT}
echo "iscsi_tgt is listening. Running tests..."
$rpc_py add_portal_group 1 $TARGET_IP:$PORT
$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
$rpc_py construct_malloc_lun $MALLOC_LUN_SIZE $MALLOC_BLOCK_SIZE
# "Malloc0:0" ==> use Malloc0 blockdev for LUN0
# "1:2" ==> map PortalGroup1 to InitiatorGroup2
# "64" ==> iSCSI queue depth 64
# "1 0 0 0" ==> disable CHAP authentication
$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' '1:2' 256 1 0 0 0
sleep 1
iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$PORT
iscsiadm -m node --login -p $TARGET_IP:$PORT
trap "umount /mnt/device; rm -rf /mnt/device; iscsicleanup; process_core; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
sleep 1
mkdir -p /mnt/device
dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
parted -s /dev/$dev mklabel msdos
parted -s /dev/$dev mkpart primary '0%' '100%'
sleep 1
for fstype in "ext4" "btrfs" "xfs"; do
if [ "$fstype" == "ext4" ]; then
mkfs.${fstype} -F /dev/${dev}1
else
mkfs.${fstype} -f /dev/${dev}1
fi
mount /dev/${dev}1 /mnt/device
touch /mnt/device/aaa
umount /mnt/device
iscsiadm -m node --logout
sleep 1
iscsiadm -m node --login -p $TARGET_IP:$PORT
sleep 1
dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
mount -o rw /dev/${dev}1 /mnt/device
if [ -f "/mnt/device/aaa" ]; then
echo "File existed."
else
echo "File doesn't exist."
exit 1
fi
rm -rf /mnt/device/aaa
umount /mnt/device
done
rm -rf /mnt/device
trap - SIGINT SIGTERM EXIT
iscsicleanup
killprocess $pid
timing_exit filesystem

@ -0,0 +1,15 @@
[Global]
ReactorMask 0xFFFF
LogFacility "local7"
[iSCSI]
NodeBase "iqn.2016-06.io.spdk"
AuthFile /usr/local/etc/spdk/auth.conf
Timeout 30
DiscoveryAuthMethod Auto
MaxSessions 16
ImmediateData Yes
ErrorRecoveryLevel 0
[Rpc]
Enable Yes

103
test/iscsi_tgt/fio/fio.sh Executable file

@ -0,0 +1,103 @@
#!/usr/bin/env bash
testdir=$(readlink -f $(dirname $0))
rootdir=$testdir/../../..
source $rootdir/scripts/autotest_common.sh
function running_config() {
# generate a config file from the running iscsi_tgt
# running_config.sh will leave the file at /tmp/iscsi.conf
$testdir/running_config.sh
sleep 1
# now start iscsi_tgt again using the generated config file
# keep the same iscsiadm configuration to confirm that the
# config file matched the running configuration
killprocess $pid
trap "iscsicleanup; exit 1" SIGINT SIGTERM EXIT
./app/iscsi_tgt/iscsi_tgt -c /tmp/iscsi.conf &
pid=$!
echo "Process pid: $pid"
trap "iscsicleanup; process_core; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
waitforlisten $pid ${RPC_PORT}
echo "iscsi_tgt is listening. Running tests..."
sleep 1
$fio_py 4096 1 randrw 5
}
if [ -z "$TARGET_IP" ]; then
echo "TARGET_IP not defined in environment"
exit 1
fi
if [ -z "$INITIATOR_IP" ]; then
echo "INITIATOR_IP not defined in environment"
exit 1
fi
timing_enter fio
# iSCSI target configuration
PORT=3260
RPC_PORT=5260
INITIATOR_TAG=2
INITIATOR_NAME=ALL
NETMASK=$INITIATOR_IP/32
MALLOC_LUN_SIZE=64
MALLOC_BLOCK_SIZE=4096
rpc_py="python $rootdir/scripts/rpc.py"
fio_py="python $rootdir/scripts/fio.py"
./app/iscsi_tgt/iscsi_tgt -c $testdir/iscsi.conf &
pid=$!
echo "Process pid: $pid"
trap "process_core; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
waitforlisten $pid ${RPC_PORT}
echo "iscsi_tgt is listening. Running tests..."
$rpc_py add_portal_group 1 $TARGET_IP:$PORT
$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
$rpc_py construct_malloc_lun $MALLOC_LUN_SIZE $MALLOC_BLOCK_SIZE
# "Malloc0:0" ==> use Malloc0 blockdev for LUN0
# "1:2" ==> map PortalGroup1 to InitiatorGroup2
# "64" ==> iSCSI queue depth 64
# "1 0 0 0" ==> disable CHAP authentication
$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' '1:2' 64 1 0 0 0
sleep 1
iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$PORT
iscsiadm -m node --login -p $TARGET_IP:$PORT
trap "iscsicleanup; process_core; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
sleep 1
$fio_py 4096 1 randrw 5 verify
$fio_py 131072 32 randrw 5 verify
if [ $RUN_NIGHTLY -eq 1 ]; then
VERIFY_TIME=300
else
VERIFY_TIME=10
fi
$fio_py 4096 1 write $VERIFY_TIME verify
# Run the running_config test which will generate a config file from the
# running iSCSI target, then kill and restart the iSCSI target using the
# generated config file
if [ $RUN_NIGHTLY -eq 1 ]; then
running_config
fi
if [ -f "./local-job0-0-verify.state" ]; then
mv ./local-job0-0-verify.state $output_dir
fi
trap - SIGINT SIGTERM EXIT
iscsicleanup
killprocess $pid
timing_exit fio

@ -0,0 +1,18 @@
[Global]
LogFacility "local7"
[iSCSI]
NodeBase "iqn.2016-06.io.spdk"
AuthFile /usr/local/etc/spdk/auth.conf
Timeout 30
DiscoveryAuthMethod Auto
MaxSessions 16
ImmediateData Yes
ErrorRecoveryLevel 0
[Rpc]
Enable Yes
[Nvme]
NvmeLunsPerNs 1
UnbindFromKernel Yes

@ -0,0 +1,25 @@
#!/usr/bin/env bash
set -xe
if [ $EUID -ne 0 ]; then
echo "$0 must be run as root"
exit 1
fi
if [ ! -f /var/run/iscsi.pid.0 ]; then
echo "ids is not running"
exit 1
fi
# delete any existing temporary iscsi.conf files
rm -f /tmp/iscsi.conf.*
kill -USR1 `cat /var/run/iscsi.pid.0`
if [ ! -f `ls /tmp/iscsi.conf.*` ]; then
echo "ids did not generate config file"
exit 1
fi
mv `ls /tmp/iscsi.conf.*` /tmp/iscsi.conf

@ -0,0 +1,14 @@
[Global]
LogFacility "local7"
[iSCSI]
NodeBase "iqn.2016-06.io.spdk"
AuthFile /usr/local/etc/spdk/auth.conf
Timeout 30
DiscoveryAuthMethod Auto
MaxSessions 16
ImmediateData Yes
ErrorRecoveryLevel 0
[Rpc]
Enable Yes

85
test/iscsi_tgt/reset/reset.sh Executable file

@ -0,0 +1,85 @@
#!/usr/bin/env bash
set -xe
testdir=$(readlink -f $(dirname $0))
rootdir=$testdir/../../..
source $rootdir/scripts/autotest_common.sh
if [ -z "$TARGET_IP" ]; then
echo "TARGET_IP not defined in environment"
exit 1
fi
if [ -z "$INITIATOR_IP" ]; then
echo "INITIATOR_IP not defined in environment"
exit 1
fi
timing_enter reset
# iSCSI target configuration
PORT=3260
RPC_PORT=5260
INITIATOR_TAG=2
INITIATOR_NAME=ALL
NETMASK=$INITIATOR_IP/32
MALLOC_LUN_SIZE=64
MALLOC_BLOCK_SIZE=512
rpc_py="python $rootdir/scripts/rpc.py"
fio_py="python $rootdir/scripts/fio.py"
if ! hash sg_reset; then
exit 1
fi
./app/iscsi_tgt/iscsi_tgt -c $testdir/iscsi.conf &
pid=$!
echo "Process pid: $pid"
trap "process_core; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
waitforlisten $pid ${RPC_PORT}
echo "iscsi_tgt is listening. Running tests..."
$rpc_py add_portal_group 1 $TARGET_IP:$PORT
$rpc_py add_initiator_group $INITIATOR_TAG $INITIATOR_NAME $NETMASK
$rpc_py construct_malloc_lun $MALLOC_LUN_SIZE $MALLOC_BLOCK_SIZE
# "Malloc0:0" ==> use Malloc0 blockdev for LUN0
# "1:2" ==> map PortalGroup1 to InitiatorGroup2
# "64" ==> iSCSI queue depth 64
# "1 0 0 0" ==> disable CHAP authentication
$rpc_py construct_target_node Target3 Target3_alias 'Malloc0:0' '1:2' 64 1 0 0 0
sleep 1
iscsiadm -m discovery -t sendtargets -p $TARGET_IP:$PORT
iscsiadm -m node --login -p $TARGET_IP:$PORT
dev=$(iscsiadm -m session -P 3 | grep "Attached scsi disk" | awk '{print $4}')
sleep 1
$fio_py 512 1 read 60 &
fiopid=$!
echo "FIO pid: $fiopid"
trap "iscsicleanup; process_core; killprocess $pid; killprocess $fiopid; exit 1" SIGINT SIGTERM EXIT
# Do 3 resets while making sure iscsi_tgt and fio are still running
for i in 1 2 3; do
sleep 1
kill -s 0 $pid
kill -s 0 $fiopid
sg_reset -d /dev/$dev
sleep 1
kill -s 0 $pid
kill -s 0 $fiopid
done
kill $fiopid
wait $fiopid || true
trap - SIGINT SIGTERM EXIT
iscsicleanup
killprocess $pid
timing_exit reset

@ -0,0 +1,14 @@
[Global]
LogFacility "local7"
[iSCSI]
NodeBase "iqn.2016-06.io.spdk"
AuthFile /usr/local/etc/spdk/auth.conf
Timeout 30
DiscoveryAuthMethod Auto
MaxSessions 16
ImmediateData Yes
ErrorRecoveryLevel 0
[Rpc]
Enable Yes

@ -0,0 +1,416 @@
#!/usr/bin/env python
import os
import os.path
import re
import sys
import time
import json
import random
from subprocess import check_call, call, check_output, Popen, PIPE, CalledProcessError
netmask = ('127.0.0.1', '127.0.0.0')
rpc_param = {
'target_ip': '127.0.0.1',
'port': 3260,
'initiator_name': 'ALL',
'netmask': netmask,
'lun_total': 3,
'malloc_lun_size': 64,
'malloc_block_size': 512,
'queue_depth': 64,
'target_name': 'Target3',
'alias_name': 'Target3_alias',
'chap_disable': 1,
'chap_mutal': 0,
'chap_required': 0,
'chap_auth_group': 0,
'trace_flag': 'rpc'
}
class RpcException(Exception):
def __init__(self, retval, *args):
super(RpcException, self).__init__(*args)
self.retval = retval
class spdk_rpc(object):
def __init__(self, rpc_py):
self.rpc_py = rpc_py
def __getattr__(self, name):
def call(*args):
cmd = "python {} {}".format(self.rpc_py, name)
for arg in args:
cmd += " {}".format(arg)
return check_output(cmd, shell=True)
return call
def verify(expr, retcode, msg):
if not expr:
raise RpcException(retcode, msg)
def verify_trace_flag_rpc_methods(rpc_py, rpc_param):
rpc = spdk_rpc(rpc_py)
output = rpc.get_trace_flags()
jsonvalue = json.loads(output)
verify(not jsonvalue[rpc_param['trace_flag']], 1,
"get_trace_flags returned {}, expected false".format(jsonvalue))
rpc.set_trace_flag(rpc_param['trace_flag'])
output = rpc.get_trace_flags()
jsonvalue = json.loads(output)
verify(jsonvalue[rpc_param['trace_flag']], 1,
"get_trace_flags returned {}, expected true".format(jsonvalue))
rpc.clear_trace_flag(rpc_param['trace_flag'])
output = rpc.get_trace_flags()
jsonvalue = json.loads(output)
verify(not jsonvalue[rpc_param['trace_flag']], 1,
"get_trace_flags returned {}, expected false".format(jsonvalue))
print "verify_trace_flag_rpc_methods passed"
def verify_iscsi_connection_rpc_methods(rpc_py):
rpc = spdk_rpc(rpc_py)
output = rpc.get_iscsi_connections()
jsonvalue = json.loads(output)
verify(not jsonvalue, 1,
"get_iscsi_connections returned {}, expected empty".format(jsonvalue))
portal_tag = '1'
initiator_tag = '1'
rpc.construct_malloc_lun(rpc_param['malloc_lun_size'], rpc_param['malloc_block_size'])
rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port'])))
rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask'][0])
lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0"
net_mapping = portal_tag + ":" + initiator_tag
rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'],
rpc_param['chap_disable'], rpc_param['chap_mutal'], rpc_param['chap_required'], rpc_param['chap_auth_group'])
check_output('iscsiadm -m discovery -t st -p {}'.format(rpc_param['target_ip']), shell=True)
check_output('iscsiadm -m node --login', shell=True)
name = json.loads(rpc.get_target_nodes())[0]['name']
output = rpc.get_iscsi_connections()
jsonvalues = json.loads(output)
verify(jsonvalues[0]['target_node_name'] == rpc_param['target_name'], 1,
"target node name vaule is {}, expected {}".format(jsonvalues[0]['target_node_name'], rpc_param['target_name']))
verify(jsonvalues[0]['id'] == 0, 1,
"device id value is {}, expected 0".format(jsonvalues[0]['id']))
verify(jsonvalues[0]['initiator_addr'] == rpc_param['target_ip'], 1,
"initiator address values is {}, expected {}".format(jsonvalues[0]['initiator_addr'], rpc_param['target_ip']))
verify(jsonvalues[0]['target_addr'] == rpc_param['target_ip'], 1,
"target address values is {}, expected {}".format(jsonvalues[0]['target_addr'], rpc_param['target_ip']))
check_output('iscsiadm -m node --logout', shell=True)
check_output('iscsiadm -m node -o delete', shell=True)
rpc.delete_initiator_group(initiator_tag)
rpc.delete_portal_group(portal_tag)
rpc.delete_target_node(name)
output = rpc.get_iscsi_connections()
jsonvalues = json.loads(output)
verify(not jsonvalues, 1,
"get_iscsi_connections returned {}, expected empty".format(jsonvalues))
print "verify_iscsi_connection_rpc_methods passed"
def verify_scsi_devices_rpc_methods(rpc_py):
rpc = spdk_rpc(rpc_py)
output = rpc.get_scsi_devices()
jsonvalue = json.loads(output)
verify(not jsonvalue, 1,
"get_scsi_devices returned {}, expected empty".format(jsonvalue))
portal_tag = '1'
initiator_tag = '1'
rpc.construct_malloc_lun(rpc_param['malloc_lun_size'], rpc_param['malloc_block_size'])
rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port'])))
rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask'][0])
lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0"
net_mapping = portal_tag + ":" + initiator_tag
rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'],
rpc_param['chap_disable'], rpc_param['chap_mutal'], rpc_param['chap_required'], rpc_param['chap_auth_group'])
check_output('iscsiadm -m discovery -t st -p {}'.format(rpc_param['target_ip']), shell=True)
check_output('iscsiadm -m node --login', shell=True)
name = json.loads(rpc.get_target_nodes())[0]['name']
output = rpc.get_scsi_devices()
jsonvalues = json.loads(output)
verify(jsonvalues[0]['device_name'] == rpc_param['target_name'], 1,
"device name vaule is {}, expected {}".format(jsonvalues[0]['device_name'], rpc_param['target_name']))
verify(jsonvalues[0]['id'] == 0, 1,
"device id value is {}, expected 0".format(jsonvalues[0]['id']))
check_output('iscsiadm -m node --logout', shell=True)
check_output('iscsiadm -m node -o delete', shell=True)
rpc.delete_initiator_group(initiator_tag)
rpc.delete_portal_group(portal_tag)
rpc.delete_target_node(name)
output = rpc.get_scsi_devices()
jsonvalues = json.loads(output)
verify(not jsonvalues, 1,
"get_scsi_devices returned {}, expected empty".format(jsonvalues))
print "verify_scsi_devices_rpc_methods passed"
def verify_luns_rpc_methods(rpc_py, rpc_param):
rpc = spdk_rpc(rpc_py)
output = rpc.get_luns()
jsonvalue = json.loads(output)
verify(not jsonvalue, 1,
"get_luns returned {}, expected empty".format(jsonvalue))
for i in range(1, rpc_param['lun_total'] + 1):
rpc.construct_malloc_lun(rpc_param['malloc_lun_size'], rpc_param['malloc_block_size'])
output = rpc.get_luns()
jsonvalue = json.loads(output)
verify(not jsonvalue, 1,
"get_luns returned {}, expected empty".format(jsonvalue))
print "verify_luns_rpc_methods passed"
def verify_portal_groups_rpc_methods(rpc_py, rpc_param):
rpc = spdk_rpc(rpc_py)
output = rpc.get_portal_groups()
jsonvalues = json.loads(output)
verify(not jsonvalues, 1,
"get_portal_groups returned {} groups, expected empty".format(jsonvalues))
lo_ip = ('127.0.0.1', '127.0.0.6')
nics = json.loads(rpc.get_interfaces())
for x in nics:
if x["ifc_index"] == 'lo':
rpc.add_ip_address(x["ifc_index"], lo_ip[1])
for idx, value in enumerate(lo_ip):
# The portal group tag must start at 1
tag = idx + 1
rpc.add_portal_group(tag, "{}:{}".format(value, rpc_param['port']))
output = rpc.get_portal_groups()
jsonvalues = json.loads(output)
verify(len(jsonvalues) == tag, 1,
"get_portal_groups returned {} groups, expected {}".format(len(jsonvalues), tag))
tag_list = []
for idx, value in enumerate(jsonvalues):
verify(value['portals'][0]['host'] == lo_ip[idx], 1,
"host value is {}, expected {}".format(value['portals'][0]['host'], rpc_param['target_ip']))
verify(value['portals'][0]['port'] == str(rpc_param['port']), 1,
"port value is {}, expected {}".format(value['portals'][0]['port'], str(rpc_param['port'])))
tag_list.append(value['tag'])
verify(value['tag'] == idx + 1, 1,
"tag value is {}, expected {}".format(value['tag'], idx + 1))
for idx, value in enumerate(tag_list):
rpc.delete_portal_group(value)
output = rpc.get_portal_groups()
jsonvalues = json.loads(output)
verify(len(jsonvalues) == (len(tag_list) - (idx + 1)), 1,
"get_portal_group returned {} groups, expected {}".format(len(jsonvalues), (len(tag_list) - (idx + 1))))
if not jsonvalues:
break
for jidx, jvalue in enumerate(jsonvalues):
verify(jvalue['portals'][0]['host'] == lo_ip[idx + jidx + 1], 1,
"host value is {}, expected {}".format(jvalue['portals'][0]['host'], lo_ip[idx + jidx + 1]))
verify(jvalue['portals'][0]['port'] == str(rpc_param['port']), 1,
"port value is {}, expected {}".format(jvalue['portals'][0]['port'], str(rpc_param['port'])))
verify(jvalue['tag'] != value or jvalue['tag'] == tag_list[idx + jidx + 1], 1,
"tag value is {}, expected {} and not {}".format(jvalue['tag'], tag_list[idx + jidx + 1], value))
for x in nics:
if x["ifc_index"] == 'lo':
rpc.delete_ip_address(x["ifc_index"], lo_ip[1])
print "verify_portal_groups_rpc_methods passed"
def verify_initiator_groups_rpc_methods(rpc_py, rpc_param):
rpc = spdk_rpc(rpc_py)
output = rpc.get_initiator_groups()
jsonvalues = json.loads(output)
verify(not jsonvalues, 1,
"get_initiator_groups returned {}, expected empty".format(jsonvalues))
for idx, value in enumerate(rpc_param['netmask']):
# The initiator group tag must start at 1
tag = idx + 1
rpc.add_initiator_group(tag, rpc_param['initiator_name'], value)
output = rpc.get_initiator_groups()
jsonvalues = json.loads(output)
verify(len(jsonvalues) == tag, 1,
"get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), tag))
tag_list = []
for idx, value in enumerate(jsonvalues):
verify(value['initiators'][0] == rpc_param['initiator_name'], 1,
"initiator value is {}, expected {}".format(value['initiators'][0], rpc_param['initiator_name']))
tag_list.append(value['tag'])
verify(value['tag'] == idx + 1, 1,
"tag value is {}, expected {}".format(value['tag'], idx + 1))
verify(value['netmasks'][0] == rpc_param['netmask'][idx], 1,
"netmasks value is {}, expected {}".format(value['netmasks'][0], rpc_param['netmask'][idx]))
for idx, value in enumerate(tag_list):
rpc.delete_initiator_group(value)
output = rpc.get_initiator_groups()
jsonvalues = json.loads(output)
verify(len(jsonvalues) == (len(tag_list) - (idx + 1)), 1,
"get_initiator_groups returned {} groups, expected {}".format(len(jsonvalues), (len(tag_list) - (idx + 1))))
if not jsonvalues:
break
for jidx, jvalue in enumerate(jsonvalues):
verify(jvalue['initiators'][0] == rpc_param['initiator_name'], 1,
"initiator value is {}, expected {}".format(jvalue['initiators'][0], rpc_param['initiator_name']))
verify(jvalue['tag'] != value or jvalue['tag'] == tag_list[idx + jidx + 1], 1,
"tag value is {}, expected {} and not {}".format(jvalue['tag'], tag_list[idx + jidx + 1], value))
verify(jvalue['netmasks'][0] == rpc_param['netmask'][idx + jidx + 1], 1,
"netmasks value is {}, expected {}".format(jvalue['netmasks'][0], rpc_param['netmask'][idx + jidx + 1]))
print "verify_initiator_groups_rpc_method passed."
def verify_target_nodes_rpc_methods(rpc_py, rpc_param):
rpc = spdk_rpc(rpc_py)
portal_tag = '1'
initiator_tag = '1'
output = rpc.get_target_nodes()
jsonvalues = json.loads(output)
verify(not jsonvalues, 1,
"get_target_nodes returned {}, expected empty".format(jsonvalues))
rpc.construct_malloc_lun(rpc_param['malloc_lun_size'], rpc_param['malloc_block_size'])
rpc.add_portal_group(portal_tag, "{}:{}".format(rpc_param['target_ip'], str(rpc_param['port'])))
rpc.add_initiator_group(initiator_tag, rpc_param['initiator_name'], rpc_param['netmask'][0])
lun_mapping = "Malloc" + str(rpc_param['lun_total']) + ":0"
net_mapping = portal_tag + ":" + initiator_tag
rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'],
rpc_param['chap_disable'], rpc_param['chap_mutal'], rpc_param['chap_required'], rpc_param['chap_auth_group'])
output = rpc.get_target_nodes()
jsonvalues = json.loads(output)
verify(len(jsonvalues) == 1, 1,
"get_target_nodes returned {} nodes, expected 1".format(len(jsonvalues)))
verify(jsonvalues[0]['lun_names'][0] == "Malloc" + str(rpc_param['lun_total']), 1,
"lun_name value is {}, expected Malloc{}".format(jsonvalues[0]['lun_names'][0], str(rpc_param['lun_total'])))
name = jsonvalues[0]['name']
verify(name == "iqn.2016-06.io.spdk:" + rpc_param['target_name'], 1,
"target name value is {}, expected {}".format(name, "iqn.2016-06.io.spdk:" + rpc_param['target_name']))
verify(jsonvalues[0]['alias_name'] == rpc_param['alias_name'], 1,
"target alias_name value is {}, expected {}".format(jsonvalues[0]['alias_name'], rpc_param['alias_name']))
verify(jsonvalues[0]['lun_ids'][0] == 0, 1,
"lun id value is {}, expected 0".format(jsonvalues[0]['lun_ids'][0]))
verify(jsonvalues[0]['initiator_group_tags'][0] == int(initiator_tag), 1,
"initiator group tag value is {}, expected {}".format(jsonvalues[0]['initiator_group_tags'][0], initiator_tag))
verify(jsonvalues[0]['queue_depth'] == rpc_param['queue_depth'], 1,
"queue depth value is {}, expected {}".format(jsonvalues[0]['queue_depth'], rpc_param['queue_depth']))
verify(jsonvalues[0]['portal_group_tags'][0] == int(portal_tag), 1,
"portal group tag value is {}, expected {}".format(jsonvalues[0]['portal_group_tags'][0], portal_tag))
verify(jsonvalues[0]['chap_disabled'] == rpc_param['chap_disable'], 1,
"chap disable value is {}, expected {}".format(jsonvalues[0]['chap_disabled'], rpc_param['chap_disable']))
verify(jsonvalues[0]['chap_mutual'] == rpc_param['chap_mutal'], 1,
"chap mutual value is {}, expected {}".format(jsonvalues[0]['chap_mutual'], rpc_param['chap_mutal']))
verify(jsonvalues[0]['chap_required'] == rpc_param['chap_required'], 1,
"chap required value is {}, expected {}".format(jsonvalues[0]['chap_required'], rpc_param['chap_required']))
verify(jsonvalues[0]['chap_auth_group'] == rpc_param['chap_auth_group'], 1,
"chap auth group value is {}, expected {}".format(jsonvalues[0]['chap_auth_group'], rpc_param['chap_auth_group']))
output = rpc.get_luns()
jsonvalue = json.loads(output)
verify(jsonvalue[0]['claimed'] is True, 1,
"The claimed value is {}, expected true".format(jsonvalue[0]['claimed']))
rpc.delete_target_node(name)
output = rpc.get_target_nodes()
jsonvalues = json.loads(output)
verify(not jsonvalues, 1,
"get_target_nodes returned {}, expected empty".format(jsonvalues))
rpc.construct_target_node(rpc_param['target_name'], rpc_param['alias_name'], lun_mapping, net_mapping, rpc_param['queue_depth'],
rpc_param['chap_disable'], rpc_param['chap_mutal'], rpc_param['chap_required'], rpc_param['chap_auth_group'])
rpc.delete_portal_group(portal_tag)
rpc.delete_initiator_group(initiator_tag)
rpc.delete_target_node(name)
output = rpc.get_target_nodes()
jsonvalues = json.loads(output)
if not jsonvalues:
print "This issue will be fixed later."
print "verify_target_nodes_rpc_methods passed."
def verify_get_interfaces(rpc_py):
rpc = spdk_rpc(rpc_py)
nics = json.loads(rpc.get_interfaces())
nics_names = set(x["name"].encode('ascii', 'ignore') for x in nics)
# parse ip link show to verify the get_interfaces result
ifcfg_nics = set(re.findall("\S+:\s(\S+):\s<.*", check_output(["ip", "link", "show"])))
verify(nics_names == ifcfg_nics, 1, "get_interfaces returned {}".format(nics))
print "verify_get_interfaces passed."
def help_get_interface_ip_list(rpc_py, nic_name):
rpc = spdk_rpc(rpc_py)
nics = json.loads(rpc.get_interfaces())
nic = filter(lambda x: x["name"] == nic_name, nics)
verify(len(nic) != 0, 1,
"Nic name: {} is not found in {}".format(nic_name, [x["name"] for x in nics]))
return nic[0]["ip_addr"]
def verify_add_delete_ip_address(rpc_py):
rpc = spdk_rpc(rpc_py)
nics = json.loads(rpc.get_interfaces())
# add ip on all nic
for x in nics:
faked_ip = "123.123.{}.{}".format(random.randint(1, 254), random.randint(1, 254))
rpc.add_ip_address(x["ifc_index"], faked_ip)
verify(faked_ip in help_get_interface_ip_list(rpc_py, x["name"]), 1,
"add ip {} to nic {} failed.".format(faked_ip, x["name"]))
try:
check_call(["ping", "-c", "1", "-W", "1", faked_ip])
except:
verify(False, 1,
"ping ip {} for {} was failed(adding was successful)".format
(faked_ip, x["name"]))
rpc.delete_ip_address(x["ifc_index"], faked_ip)
verify(faked_ip not in help_get_interface_ip_list(rpc_py, x["name"]), 1,
"delete ip {} from nic {} failed.(adding and ping were successful)".format
(faked_ip, x["name"]))
# ping should be failed and throw an CalledProcessError exception
try:
check_call(["ping", "-c", "1", "-W", "1", faked_ip])
except CalledProcessError as _:
pass
except Exception as e:
verify(False, 1,
"Unexpected exception was caught {}(adding/ping/delete were successful)".format
(str(e)))
else:
verify(False, 1,
"ip {} for {} could be pinged after delete ip(adding/ping/delete were successful)".format
(faked_ip, x["name"]))
print "verify_add_delete_ip_address passed."
if __name__ == "__main__":
rpc_py = sys.argv[1]
try:
verify_trace_flag_rpc_methods(rpc_py, rpc_param)
verify_get_interfaces(rpc_py)
verify_add_delete_ip_address(rpc_py)
verify_luns_rpc_methods(rpc_py, rpc_param)
verify_portal_groups_rpc_methods(rpc_py, rpc_param)
verify_initiator_groups_rpc_methods(rpc_py, rpc_param)
verify_target_nodes_rpc_methods(rpc_py, rpc_param)
verify_scsi_devices_rpc_methods(rpc_py)
verify_iscsi_connection_rpc_methods(rpc_py)
except RpcException as e:
print "{}. Exiting with status {}".format(e.message, e.retval)
raise e
except Exception as e:
raise e
sys.exit(0)

@ -0,0 +1,47 @@
#!/usr/bin/env bash
testdir=$(readlink -f $(dirname $0))
rootdir=$testdir/../../..
source $rootdir/scripts/autotest_common.sh
if [ -z "$TARGET_IP" ]; then
echo "TARGET_IP not defined in environment"
exit 1
fi
if [ -z "$INITIATOR_IP" ]; then
echo "INITIATOR_IP not defined in environment"
exit 1
fi
timing_enter rpc_config
# iSCSI target configuration
PORT=3260
RPC_PORT=5260
INITIATOR_TAG=2
INITIATOR_NAME=ALL
NETMASK=$INITIATOR_IP/32
MALLOC_LUN_SIZE=64
rpc_py=$rootdir/scripts/rpc.py
rpc_config_py="python $testdir/rpc_config.py"
./app/iscsi_tgt/iscsi_tgt -c $testdir/iscsi.conf &
pid=$!
echo "Process pid: $pid"
trap "process_core; killprocess $pid; exit 1" SIGINT SIGTERM EXIT
waitforlisten $pid ${RPC_PORT}
echo "iscsi_tgt is listening. Running tests..."
$rpc_config_py $rpc_py
trap - SIGINT SIGTERM EXIT
iscsicleanup
killprocess $pid
timing_exit rpc_config