NVMeOF Connection Agent

This is a console script responsible for initiator-side monitoring and self healing of NVMeoF connections and replicated mdraid volumes.

It initially supports block storage exposed by KumoScale volume driver used by NVMeoF connector.

The functions of the agent are:
* Probing of storage orchestrator
* Monitor NVMeoF connections and replicated volumes' mdraid states
* Report initiator-side connection and volume events to storage orchestrator
* Connection and replication state re-conciliation

Since this agent relies on methods defined in the NVMeOFConnector "Replicated+" mode, such methods were converted to be usable by the console script.
This was done such that the top level privileged methods (for running nvme and mdadm cli tools) are no longer class methods, and are kept in privileged/nvmeof.py
All other methods now either use these privileged methods, or run their own rootwrap execute.

Implements: Blueprint nvmeof-connection-agent
Change-Id: I34e475f9a2a07e19413d40371d62fc809df2105a
This commit is contained in:
Zohar Mamedov 2021-07-28 11:57:20 +00:00
parent 98c0916336
commit b03014b297
9 changed files with 3216 additions and 351 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,467 @@
# (c) Copyright Kioxia Corporation 2021 All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
class JsonClass(object):
def __init__(self):
pass
def to_json(self):
return json.dumps(
self,
default=lambda o: o.__dict__,
sort_keys=True,
indent=4)
def __str__(self):
return ', '.join(['{key}={value}'.format(
key=key, value=self.__dict__.get(key)) for key in self.__dict__])
def __getattr__(self, item):
return "N/A"
def set_items(self, json_object):
json_keys = json_object.keys()
for key in json_keys:
if not isinstance(json_object[key], 'dict'):
self.__dict__[key] = json_object[key]
class ProvisionerResponse(JsonClass):
#
# Provisioner response data
#
def __init__(
self,
prov_entities,
res_id=None,
status=None,
description=None,
path=None):
JsonClass.__init__(self)
self.prov_entities = prov_entities
self.resID = res_id
self.status = "Success" if status is None else status
self.description = self.status if description is None else description
self.path = path
def __str__(self):
items = ""
if self.prov_entities:
num_of_entities = len(self.prov_entities)
if num_of_entities == 1:
items = self.prov_entities[0]
else:
items = num_of_entities
return "(" + str(items) + ", " + str(self.resID) + ", " + \
str(self.status) + ", " + str(self.description) + ")"
class ProvisionerInfo(JsonClass):
#
# Provisioner Info data
#
def __init__(self, totalFreeSpace, version, syslogsBackend=None):
self.totalFreeSpace = totalFreeSpace
self.version = version
self.syslogsBackend = syslogsBackend
class Backend(JsonClass):
#
# Backend data
#
def __init__(
self,
mgmt_ips=None,
rack=None,
region=None,
zone=None,
persistentID=None,
inUse=None,
hostId=None,
state=None,
totalCapacity=None,
availableCapacity=None,
lastProbTime=None,
probeInterval=None,
totalBW=None,
availableBW=None,
totalIOPS=None,
availableIOPS=None):
self.mgmtIPs = mgmt_ips
self.rack = rack
self.region = region
self.zone = zone
self.persistentID = persistentID
self.inUse = inUse
self.state = state
self.totalCapacity = totalCapacity
self.availableCapacity = availableCapacity
self.lastProbTime = lastProbTime
self.probeInterval = probeInterval
self.totalBW = totalBW
self.availableBW = availableBW
self.totalIOPS = totalIOPS
self.availableIOPS = availableIOPS
self.hostId = hostId
class Replica(JsonClass):
#
# Backend data
#
def __init__(self, sameRackAllowed, racks, regions, zones):
self.sameRackAllowed = sameRackAllowed
self.racks = racks
self.regions = regions
self.zones = zones
class Location(JsonClass):
#
# Location data
#
def __init__(
self,
uuid=None,
backend=None,
replicaState=None,
currentStateTime=None):
self.uuid = uuid
self.backend = backend
self.replicaState = replicaState
self.currentStateTime = currentStateTime
class VolumeProv(JsonClass):
#
# Provisioner Volume data
#
def __init__(
self,
uuid=None,
alias=None,
capacity=None,
numReplicas=None,
maxIOPS=None,
desiredIOPS=None,
maxBW=None,
desiredBW=None,
blockSize=None,
maxReplicaDownTime=None,
snapshotID=None,
writable=None,
reservedSpace=None,
location=None):
self.uuid = uuid
self.alias = alias
self.capacity = capacity
self.numReplicas = numReplicas
self.maxIOPS = maxIOPS
self.desiredIOPS = desiredIOPS
self.maxBW = maxBW
self.desiredBW = desiredBW
self.blockSize = blockSize
self.maxReplicaDownTime = maxReplicaDownTime
self.snapshotID = snapshotID
self.writable = writable
self.reservedSpacePercentage = reservedSpace
self.location = location
class StorageClass(JsonClass):
#
# Provisioner Storage Class
#
def __init__(
self,
replicas,
racks=None,
regions=None,
zones=None,
blockSize=None,
maxIOPSPerGB=None,
desiredIOPSPerGB=None,
maxBWPerGB=None,
desiredBWPerGB=None,
sameRackAllowed=None,
maxReplicaDownTime=None,
hostId=None,
spanAllowed=None,
name=None,
shareSSDBetweenVolumes=None):
self.numReplicas = replicas
if racks is not None:
self.racks = racks
if regions is not None:
self.regions = regions
if zones is not None:
self.zones = zones
if blockSize is not None:
self.blockSize = blockSize
if maxIOPSPerGB is not None:
self.maxIOPSPerGB = maxIOPSPerGB
if desiredIOPSPerGB is not None:
self.desiredIOPSPerGB = desiredIOPSPerGB
if maxBWPerGB is not None:
self.maxBWPerGB = maxBWPerGB
if desiredBWPerGB is not None:
self.desiredBWPerGB = desiredBWPerGB
if sameRackAllowed is not None:
self.sameRackAllowed = sameRackAllowed
if maxReplicaDownTime is not None:
self.maxReplicaDownTime = maxReplicaDownTime
if hostId is not None:
self.hostId = hostId
if spanAllowed is not None:
self.allowSpan = spanAllowed
if name is not None:
self.name = name
if shareSSDBetweenVolumes is not None:
self.shareSSDBetweenVolumes = shareSSDBetweenVolumes
class VolumeCreate(JsonClass):
#
# Provisioner Volume data for Create operation
#
def __init__(
self,
alias,
capacity,
storage_class,
prov_type,
reserved_space=None,
protocol=None,
uuid=None):
self.alias = alias
self.capacity = capacity
self.storageClass = storage_class
self.provisioningType = prov_type
if reserved_space is not None:
self.reservedSpacePercentage = reserved_space
if protocol is not None:
self.protocol = protocol
if uuid is not None:
self.uuid = uuid
class SyslogEntity(JsonClass):
#
# Syslog Entity object
#
def __init__(
self,
name=None,
url=None,
state=None,
useTls=None,
certFileName=None):
self.name = name
self.url = url
self.state = state
self.useTls = useTls
self.certFileName = certFileName
class SnapshotCreate(JsonClass):
#
# Provisioner Snapshot data for Create operation
#
def __init__(
self,
alias,
volumeID,
reservedSpacePercentage=None,
snapshotID=None):
self.alias = alias
self.volumeID = volumeID
if reservedSpacePercentage is not None:
self.reservedSpacePercentage = reservedSpacePercentage
if snapshotID is not None:
self.snapshotID = snapshotID
class SnapshotEntity(JsonClass):
#
# Provisioner Snapshot Entity data for Show operation
#
def __init__(
self,
alias=None,
snapshotID=None,
reservedSpace=None,
volumeID=None,
capacity=None,
timestamp=None):
self.alias = alias
self.volumeID = volumeID
self.reservedSpace = reservedSpace
self.snapshotID = snapshotID
self.capacity = capacity
self.timestamp = timestamp
class SnapshotVolumeCreate(JsonClass):
#
# Provisioner Snapshot Volume data for Create operation
#
def __init__(
self,
alias,
snapshotID,
writable,
reservedSpacePercentage=None,
volumeID=None,
maxIOPSPerGB=None,
maxBWPerGB=None,
protocol=None,
spanAllowed=None,
storageClassName=None):
self.alias = alias
self.snapshotID = snapshotID
self.writable = writable
if reservedSpacePercentage is not None:
self.reservedSpacePercentage = reservedSpacePercentage
if volumeID is not None:
self.volumeID = volumeID
if maxIOPSPerGB is not None:
self.maxIOPSPerGB = maxIOPSPerGB
if maxBWPerGB is not None:
self.maxBWPerGB = maxBWPerGB
if protocol is not None:
self.protocol = protocol
if spanAllowed is not None:
self.allowSpan = spanAllowed
if storageClassName is not None:
self.storageClassName = storageClassName
class ForwardEntity(JsonClass):
#
# Provisioner Forward Entity data
#
def __init__(
self,
loggingType,
level,
host,
appName,
message,
parametersList):
self.loggingType = loggingType
self.level = level
self.host = host
self.appName = appName
self.message = message
self.parametersList = parametersList
class LicenseEntity(JsonClass):
#
# Provisioner License Entity data
#
def __init__(
self,
license_type=None,
expirationDate=None,
maxBackends=None):
self.type = license_type
self.expirationDate = expirationDate
self.maxBackends = maxBackends
class HostEntity(JsonClass):
#
# Provisioner Host Entity data
#
def __init__(
self,
nqn=None,
uuid=None,
name=None,
clientType=None,
version=None,
state=None,
lastProbeTime=None,
duration=None):
self.nqn = nqn
self.uuid = uuid
self.name = name
self.clientType = clientType
self.version = version
self.state = state
self.lastProbeTime = lastProbeTime
self.duration = duration
class TargetEntity(JsonClass):
#
# Provisioner Target Entity data for Show operation
#
def __init__(self, alias=None):
self.alias = alias
class TenantEntity(JsonClass):
#
# Provisioner Tenant Entity data for Show operation
#
def __init__(self, capacity, iops, bw, uuid=None, name=None):
self.capacity = capacity
self.totalIOPS = iops
self.totalBW = bw
if uuid is not None:
self.tenantId = uuid
if name is not None:
self.name = name
class CloneEntity(JsonClass):
#
# Provisioner Clone Entity data
#
def __init__(self, sourceVolumeId, alias, volumeId=None,
reservedSpacePercentage=None,
capacity=None):
self.sourceVolumeId = sourceVolumeId
self.alias = alias
if volumeId is not None:
self.volumeId = volumeId
if reservedSpacePercentage is not None:
self.reservedSpacePercentage = reservedSpacePercentage
if capacity is not None:
self.capacity = capacity

File diff suppressed because it is too large Load Diff

View File

@ -24,10 +24,6 @@ from oslo_log import log as logging
from os_brick import exception
from os_brick.i18n import _
from os_brick.initiator.connectors import base
try:
from os_brick.initiator.connectors import nvmeof_agent
except ImportError:
nvmeof_agent = None
from os_brick.privileged import nvmeof as priv_nvme
from os_brick.privileged import rootwrap as priv_rootwrap
from os_brick import utils
@ -63,19 +59,20 @@ class NVMeOFConnector(base.BaseLinuxConnector):
if device_path:
return [device_path]
volume_replicas = connection_properties.get('volume_replicas')
replica_count = connection_properties.get('replica_count')
if not volume_replicas: # compatibility
return []
try:
if volume_replicas and len(volume_replicas) > 1:
if volume_replicas and replica_count > 1:
return ['/dev/md/' + connection_properties.get('alias')]
if volume_replicas and len(volume_replicas) == 1:
if volume_replicas and replica_count == 1:
return [NVMeOFConnector.get_nvme_device_path(
self, volume_replicas[0]['target_nqn'],
volume_replicas[0]['target_nqn'],
volume_replicas[0]['vol_uuid'])]
else:
return [NVMeOFConnector.get_nvme_device_path(
self, connection_properties.get('target_nqn'),
connection_properties.get('target_nqn'),
connection_properties.get('vol_uuid'))]
except exception.VolumeDeviceNotFound:
return []
@ -101,7 +98,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
ret = {}
nqn = None
uuid = nvmf._get_host_uuid()
uuid = priv_nvme.get_host_uuid()
suuid = nvmf._get_system_uuid()
if cls.nvme_present():
nqn = nvmf._get_host_nqn()
@ -113,21 +110,6 @@ class NVMeOFConnector(base.BaseLinuxConnector):
ret['nqn'] = nqn
return ret
def _get_host_uuid(self):
cmd = ('findmnt', '/', '-n', '-o', 'SOURCE')
try:
lines, err = self._execute(
*cmd, run_as_root=True, root_helper=self._root_helper)
blkid_cmd = (
'blkid', lines.split('\n')[0], '-s', 'UUID', '-o', 'value')
lines, _err = self._execute(
*blkid_cmd, run_as_root=True, root_helper=self._root_helper)
return lines.split('\n')[0]
except putils.ProcessExecutionError as e:
LOG.warning(
"Process execution error in _get_host_uuid: %s" % str(e))
return None
def _get_host_nqn(self):
try:
with open('/etc/nvme/hostnqn', 'r') as f:
@ -424,6 +406,10 @@ class NVMeOFConnector(base.BaseLinuxConnector):
{'props': connection_properties})
raise exception.VolumePathsNotFound()
#
# "Replicated+" NVMeOF Connector methods below
#
@utils.trace
def _connect_volume_replicated(self, connection_properties):
"""connect to volume on host
@ -435,6 +421,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
"""
volume_replicas = connection_properties.get('volume_replicas')
replica_count = connection_properties.get('replica_count')
volume_alias = connection_properties.get('alias')
if volume_replicas:
@ -453,9 +440,9 @@ class NVMeOFConnector(base.BaseLinuxConnector):
raise exception.VolumeDeviceNotFound(
device=volume_replicas)
if len(volume_replicas) > 1:
if replica_count > 1:
device_path = self._handle_replicated_volume(
host_device_paths, volume_alias, len(volume_replicas))
host_device_paths, volume_alias, replica_count)
else:
device_path = self._handle_single_replica(
host_device_paths, volume_alias)
@ -465,9 +452,6 @@ class NVMeOFConnector(base.BaseLinuxConnector):
connection_properties['vol_uuid'],
connection_properties['portals'])
if nvmeof_agent:
nvmeof_agent.NVMeOFAgent.ensure_running(self)
return {'type': 'block', 'path': device_path}
@utils.trace
@ -483,18 +467,19 @@ class NVMeOFConnector(base.BaseLinuxConnector):
device_path = '/dev/md/' + connection_properties['alias']
if volume_replicas and len(volume_replicas) > 1:
NVMeOFConnector.end_raid(self, device_path)
NVMeOFConnector.end_raid(device_path)
else:
if self._get_fs_type(device_path) == 'linux_raid_member':
NVMeOFConnector.end_raid(self, device_path)
if (NVMeOFConnector._get_fs_type(device_path)
== 'linux_raid_member'):
NVMeOFConnector.end_raid(device_path)
def _extend_volume_replicated(self, connection_properties):
volume_replicas = connection_properties.get('volume_replicas')
if volume_replicas and len(volume_replicas) > 1:
device_path = '/dev/md/' + connection_properties['alias']
NVMeOFConnector.run_mdadm(
self, ['mdadm', '--grow', '--size', 'max', device_path])
priv_nvme.run_mdadm(
['mdadm', '--grow', '--size', 'max', device_path])
else:
if not volume_replicas:
target_nqn = connection_properties['target_nqn']
@ -503,20 +488,20 @@ class NVMeOFConnector(base.BaseLinuxConnector):
target_nqn = volume_replicas[0]['target_nqn']
vol_uuid = volume_replicas[0]['vol_uuid']
device_path = NVMeOFConnector.get_nvme_device_path(
self, target_nqn, vol_uuid)
target_nqn, vol_uuid)
return self._linuxscsi.get_device_size(device_path)
def _connect_target_volume(self, target_nqn, vol_uuid, portals):
try:
host_device_path = NVMeOFConnector.get_nvme_device_path(
self, target_nqn, vol_uuid)
target_nqn, vol_uuid)
except exception.VolumeDeviceNotFound:
host_device_path = None
if not host_device_path:
any_connect = NVMeOFConnector.connect_to_portals(
self, target_nqn, portals)
target_nqn, portals)
if not any_connect:
LOG.error(
"No successful connections: %(host_devices)s",
@ -524,21 +509,57 @@ class NVMeOFConnector(base.BaseLinuxConnector):
raise exception.VolumeDeviceNotFound(device=target_nqn)
host_device_path = NVMeOFConnector.get_nvme_device_path(
self, target_nqn, vol_uuid)
target_nqn, vol_uuid)
if not host_device_path:
LOG.error(
"No accessible volume device: %(host_devices)s",
{'host_devices': target_nqn})
raise exception.VolumeDeviceNotFound(device=target_nqn)
else:
NVMeOFConnector.rescan(self, target_nqn, vol_uuid)
NVMeOFConnector.rescan(target_nqn, vol_uuid)
host_device_path = NVMeOFConnector.get_nvme_device_path(
self, target_nqn, vol_uuid)
target_nqn, vol_uuid)
return host_device_path
def _handle_replicated_volume(self, host_device_paths,
volume_alias, num_of_replicas):
path_in_raid = False
for dev_path in host_device_paths:
path_in_raid = NVMeOFConnector._is_device_in_raid(dev_path)
if path_in_raid:
break
device_path = '/dev/md/' + volume_alias
if path_in_raid:
NVMeOFConnector.stop_and_assemble_raid(
host_device_paths, device_path, False)
else:
paths_found = len(host_device_paths)
if num_of_replicas > paths_found:
LOG.error(
'Cannot create MD as %s out of %s legs were found.',
paths_found, num_of_replicas)
raise exception.VolumeDeviceNotFound(device=volume_alias)
NVMeOFConnector.create_raid(host_device_paths, '1',
volume_alias, volume_alias, False)
return device_path
def _handle_single_replica(self, host_device_paths, volume_alias):
if (NVMeOFConnector._get_fs_type(host_device_paths[0])
== 'linux_raid_member'):
md_path = '/dev/md/' + volume_alias
NVMeOFConnector.stop_and_assemble_raid(
host_device_paths, md_path, False)
return md_path
return host_device_paths[0]
#
# Static methods below are also used by NVMeOF-Agent
#
@staticmethod
def connect_to_portals(executor, target_nqn, target_portals):
def connect_to_portals(target_nqn, target_portals):
"""connect to any of NVMe-oF target portals"""
any_connect = False
for portal in target_portals:
@ -552,7 +573,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
'connect', '-a', portal_address, '-s', portal_port, '-t',
portal_transport, '-n', target_nqn, '-Q', '128', '-l', '-1')
try:
NVMeOFConnector.run_nvme_cli(executor, nvme_command)
priv_nvme.run_nvme_cli(nvme_command)
any_connect = True
break
except Exception:
@ -560,18 +581,16 @@ class NVMeOFConnector(base.BaseLinuxConnector):
return any_connect
@staticmethod
def _get_nvme_controller(executor, target_nqn):
def get_nvme_controller(target_nqn):
ctrls = glob.glob('/sys/class/nvme-fabrics/ctl/nvme*')
for ctrl in ctrls:
try:
lines, _err = executor._execute(
'cat', ctrl + '/subsysnqn', run_as_root=True,
root_helper=executor._root_helper)
lines, _err = priv_rootwrap.custom_execute(
'cat', ctrl + '/subsysnqn')
for line in lines.split('\n'):
if line == target_nqn:
state, _err = executor._execute(
'cat', ctrl + '/state', run_as_root=True,
root_helper=executor._root_helper)
state, _err = priv_rootwrap.custom_execute(
'cat', ctrl + '/state')
if 'live' not in state:
LOG.debug("nvmeof ctrl device not live: %s", ctrl)
raise exception.VolumeDeviceNotFound(device=ctrl)
@ -583,76 +602,30 @@ class NVMeOFConnector(base.BaseLinuxConnector):
@staticmethod
@utils.retry(exceptions=exception.VolumeDeviceNotFound)
def get_nvme_device_path(executor, target_nqn, vol_uuid):
nvme_ctrl = NVMeOFConnector._get_nvme_controller(executor, target_nqn)
try:
blocks = glob.glob(
'/sys/class/nvme-fabrics/ctl/' + nvme_ctrl +
'/' + nvme_ctrl + 'n*')
for block in blocks:
uuid_lines, _err = executor._execute(
'cat', block + '/uuid', run_as_root=True,
root_helper=executor._root_helper)
if uuid_lines.split('\n')[0] == vol_uuid:
return '/dev/' + block[block.rfind('/') + 1:]
except putils.ProcessExecutionError as e:
LOG.exception(e)
def get_nvme_device_path(target_nqn, vol_uuid):
nvme_ctrl = NVMeOFConnector.get_nvme_controller(target_nqn)
blocks = glob.glob(
'/sys/class/block/' + nvme_ctrl + 'n*')
for block in blocks:
uuid_path = block + '/uuid'
try:
exists = os.path.exists(uuid_path)
if exists:
uuid_lines, _err = priv_rootwrap.custom_execute(
'cat', uuid_path)
if uuid_lines.split('\n')[0] == vol_uuid:
return '/dev/' + block[block.rfind('/') + 1:]
except putils.ProcessExecutionError as e:
LOG.exception(e)
raise exception.VolumeDeviceNotFound(device=vol_uuid)
def _handle_replicated_volume(self, host_device_paths,
volume_alias, num_of_replicas):
path_in_raid = False
for dev_path in host_device_paths:
path_in_raid = NVMeOFConnector._is_device_in_raid(self, dev_path)
if path_in_raid:
break
device_path = '/dev/md/' + volume_alias
if path_in_raid:
NVMeOFConnector.stop_and_assemble_raid(
self, host_device_paths, device_path, False)
else:
paths_found = len(host_device_paths)
if num_of_replicas > paths_found:
LOG.error(
'Cannot create MD as %s out of %s legs were found.',
paths_found, num_of_replicas)
raise exception.VolumeDeviceNotFound(device=volume_alias)
NVMeOFConnector.create_raid(self, host_device_paths, '1',
volume_alias, volume_alias, False)
return device_path
def _handle_single_replica(self, host_device_paths, volume_alias):
if self._get_fs_type(host_device_paths[0]) == 'linux_raid_member':
md_path = '/dev/md/' + volume_alias
NVMeOFConnector.stop_and_assemble_raid(
self, host_device_paths, md_path, False)
return md_path
return host_device_paths[0]
@staticmethod
def run_mdadm(executor, cmd, raise_exception=False):
cmd_output = None
try:
lines, err = executor._execute(
*cmd, run_as_root=True, root_helper=executor._root_helper)
for line in lines.split('\n'):
cmd_output = line
break
except putils.ProcessExecutionError as ex:
LOG.warning("[!] Could not run mdadm: %s", str(ex))
if raise_exception:
raise ex
return cmd_output
@staticmethod
def _is_device_in_raid(self, device_path):
def _is_device_in_raid(device_path):
cmd = ['mdadm', '--examine', device_path]
raid_expected = device_path + ':'
try:
lines, err = self._execute(
*cmd, run_as_root=True, root_helper=self._root_helper)
lines, err = priv_nvme.run_mdadm(cmd)
for line in lines.split('\n'):
if line == raid_expected:
return True
@ -669,7 +642,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
return ''
@staticmethod
def get_md_name(executor, device_name):
def get_md_name(device_name):
get_md_cmd = (
'cat /proc/mdstat | grep ' + device_name +
' | awk \'{print $1;}\'')
@ -678,8 +651,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
cmd_output = None
try:
lines, err = executor._execute(
*cmd, run_as_root=True, root_helper=executor._root_helper)
lines, err = priv_rootwrap.custom_execute(*cmd)
for line in lines.split('\n'):
cmd_output = line
@ -695,7 +667,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
return None
@staticmethod
def stop_and_assemble_raid(executor, drives, md_path, read_only):
def stop_and_assemble_raid(drives, md_path, read_only):
md_name = None
i = 0
assembled = False
@ -703,7 +675,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
while i < 5 and not assembled:
for drive in drives:
device_name = drive[5:]
md_name = NVMeOFConnector.get_md_name(executor, device_name)
md_name = NVMeOFConnector.get_md_name(device_name)
link = NVMeOFConnector.ks_readlink(md_path)
if link != '':
link = os.path.basename(link)
@ -715,16 +687,16 @@ class NVMeOFConnector(base.BaseLinuxConnector):
time.sleep(1)
if md_name and md_name != link:
NVMeOFConnector.stop_raid(executor, md_name)
NVMeOFConnector.stop_raid(md_name)
try:
assembled = NVMeOFConnector.assemble_raid(
executor, drives, md_path, read_only)
drives, md_path, read_only)
except Exception:
i += 1
@staticmethod
def assemble_raid(executor, drives, md_path, read_only):
def assemble_raid(drives, md_path, read_only):
cmd = ['mdadm', '--assemble', '--run', md_path]
if read_only:
@ -734,7 +706,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
cmd.append(drives[i])
try:
NVMeOFConnector.run_mdadm(executor, cmd, True)
priv_nvme.run_mdadm(cmd, True)
except putils.ProcessExecutionError as ex:
LOG.warning("[!] Could not _assemble_raid: %s", str(ex))
raise ex
@ -742,7 +714,7 @@ class NVMeOFConnector(base.BaseLinuxConnector):
return True
@staticmethod
def create_raid(executor, drives, raid_type, device_name, name, read_only):
def create_raid(drives, raid_type, device_name, name, read_only):
cmd = ['mdadm']
num_drives = len(drives)
cmd.append('-C')
@ -769,16 +741,16 @@ class NVMeOFConnector(base.BaseLinuxConnector):
cmd.append(drives[i])
LOG.debug('[!] cmd = ' + str(cmd))
NVMeOFConnector.run_mdadm(executor, cmd)
priv_nvme.run_mdadm(cmd)
@staticmethod
def end_raid(executor, device_path):
raid_exists = NVMeOFConnector.is_raid_exists(executor, device_path)
def end_raid(device_path):
raid_exists = NVMeOFConnector.is_raid_exists(device_path)
if raid_exists:
for i in range(10):
try:
cmd_out = NVMeOFConnector.stop_raid(
executor, device_path)
device_path)
if not cmd_out:
break
except Exception:
@ -788,26 +760,25 @@ class NVMeOFConnector(base.BaseLinuxConnector):
is_exist = os.path.exists(device_path)
LOG.debug("[!] is_exist = %s", is_exist)
if is_exist:
NVMeOFConnector.remove_raid(executor, device_path)
NVMeOFConnector.remove_raid(device_path)
os.remove(device_path)
except Exception:
LOG.debug('[!] Exception_stop_raid!')
@staticmethod
def stop_raid(executor, md_path):
def stop_raid(md_path):
cmd = ['mdadm', '--stop', md_path]
LOG.debug("[!] cmd = " + str(cmd))
cmd_out = NVMeOFConnector.run_mdadm(executor, cmd)
return cmd_out
lines, err = priv_nvme.run_mdadm(cmd)
return lines.split('\n')[0]
@staticmethod
def is_raid_exists(executor, device_path):
def is_raid_exists(device_path):
cmd = ['mdadm', '--detail', device_path]
LOG.debug("[!] cmd = " + str(cmd))
raid_expected = device_path + ':'
try:
lines, err = executor._execute(
*cmd, run_as_root=True, root_helper=executor._root_helper)
lines, err = priv_nvme.run_mdadm(cmd)
for line in lines.split('\n'):
LOG.debug("[!] line = " + line)
@ -819,41 +790,30 @@ class NVMeOFConnector(base.BaseLinuxConnector):
return False
@staticmethod
def remove_raid(executor, device_path):
def remove_raid(device_path):
cmd = ['mdadm', '--remove', device_path]
LOG.debug("[!] cmd = " + str(cmd))
NVMeOFConnector.run_mdadm(executor, cmd)
priv_nvme.run_mdadm(cmd)
@staticmethod
def run_nvme_cli(executor, nvme_command, **kwargs):
(out, err) = executor._execute('nvme', *nvme_command, run_as_root=True,
root_helper=executor._root_helper,
check_exit_code=True)
msg = ("nvme %(nvme_command)s: stdout=%(out)s stderr=%(err)s" %
{'nvme_command': nvme_command, 'out': out, 'err': err})
LOG.debug("[!] " + msg)
return out, err
@staticmethod
def rescan(executor, target_nqn, vol_uuid):
def rescan(target_nqn, vol_uuid):
ctr_device = (
NVMeOFConnector.get_search_path() +
NVMeOFConnector._get_nvme_controller(executor, target_nqn))
NVMeOFConnector.get_nvme_controller(target_nqn))
nvme_command = ('ns-rescan', ctr_device)
try:
NVMeOFConnector.run_nvme_cli(executor, nvme_command)
priv_nvme.run_nvme_cli(nvme_command)
except Exception as e:
raise exception.CommandExecutionFailed(e, cmd=nvme_command)
def _get_fs_type(self, device_path):
@staticmethod
def _get_fs_type(device_path):
cmd = ['blkid', device_path, '-s', 'TYPE', '-o', 'value']
LOG.debug("[!] cmd = " + str(cmd))
fs_type = None
try:
lines, err = self._execute(
*cmd, run_as_root=True, root_helper=self._root_helper)
lines, err = priv_rootwrap.custom_execute(*cmd)
fs_type = lines.split('\n')[0]
except putils.ProcessExecutionError:

View File

@ -64,3 +64,36 @@ def create_hostnqn():
LOG.warning("Could not generate host nqn: %s" % str(e))
return host_nqn
@os_brick.privileged.default.entrypoint
def get_host_uuid():
try:
lines, _err = rootwrap.custom_execute(
'findmnt', '/', '-n', '-o', 'SOURCE')
lines, _err = rootwrap.custom_execute(
'blkid', lines.split('\n')[0], '-s', 'UUID', '-o', 'value')
return lines.split('\n')[0]
except putils.ProcessExecutionError as e:
LOG.warning(
"Could not get host uuid: %s" % str(e))
return None
@os_brick.privileged.default.entrypoint
def run_nvme_cli(nvme_command):
out, err = rootwrap.custom_execute(
'nvme', *nvme_command, check_exit_code=True)
LOG.debug("nvme %(nvme_command)s: stdout=%(out)s stderr=%(err)s" %
{'nvme_command': nvme_command, 'out': out, 'err': err})
return out, err
@os_brick.privileged.default.entrypoint
def run_mdadm(cmd, raise_exception=False):
try:
return rootwrap.custom_execute(*cmd)
except putils.ProcessExecutionError as ex:
LOG.warning("Could not run mdadm: %s - %s", str(cmd), str(ex))
if raise_exception:
raise ex

View File

@ -19,7 +19,6 @@ import ddt
from oslo_concurrency import processutils as putils
from os_brick import exception
from os_brick import executor
from os_brick.initiator.connectors import nvmeof
from os_brick.initiator import linuxscsi
from os_brick.privileged import rootwrap as priv_rootwrap
@ -27,7 +26,6 @@ from os_brick.tests.initiator import test_connector
TARGET_NQN = 'target.nqn'
EXECUTOR = executor.Executor(None)
VOL_UUID = 'c20aba21-6ef6-446b-b374-45733b4883ba'
NVME_DEVICE_PATH = '/dev/nvme1'
NVME_NS_PATH = '/dev/nvme1n1'
@ -44,6 +42,7 @@ volume_replicas = [{'target_nqn': 'fakenqn1', 'vol_uuid': 'fakeuuid1',
connection_properties = {
'alias': 'fakealias',
'vol_uuid': 'fakevoluuid',
'replica_count': 3,
'volume_replicas': volume_replicas
}
fake_portal = ('fake', 'portal', 'tcp')
@ -75,28 +74,13 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
log.assert_called_once()
self.assertFalse(nvme_present)
@mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True)
def test_get_sysuuid_without_newline(self, mock_execute):
mock_execute.return_value = (
"9126E942-396D-11E7-B0B7-A81E84C186D1\n", "")
uuid = self.connector._get_host_uuid()
expected_uuid = "9126E942-396D-11E7-B0B7-A81E84C186D1"
self.assertEqual(expected_uuid, uuid)
@mock.patch.object(nvmeof.NVMeOFConnector, '_execute', autospec=True)
def test_get_sysuuid_err(self, mock_execute):
mock_execute.side_effect = putils.ProcessExecutionError()
uuid = self.connector._get_host_uuid()
self.assertIsNone(uuid)
@mock.patch.object(nvmeof.NVMeOFConnector, 'nvme_present',
return_value=True)
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_nqn',
return_value='fakenqn')
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid',
return_value=None)
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_uuid',
return_value=None)
@mock.patch.object(nvmeof.priv_nvme, 'get_host_uuid', return_value = None)
def test_get_connector_properties_without_sysuuid(self, mock_host_uuid,
mock_sysuuid, mock_nqn,
mock_nvme_present):
@ -108,7 +92,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_nqn', autospec=True)
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid',
autospec=True)
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_uuid', autospec=True)
@mock.patch.object(nvmeof.priv_nvme, 'get_host_uuid')
def test_get_connector_properties_with_sysuuid(self, mock_host_uuid,
mock_sysuuid, mock_nqn,
mock_nvme_present):
@ -131,13 +115,14 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_get_device_path.return_value = '/dev/nvme1n1'
connection_properties = {
'alias': 'fakealias',
'replica_count': 1,
'volume_replicas': [volume_replicas[0]]
}
self.assertEqual(self.connector.get_volume_paths(
connection_properties),
['/dev/nvme1n1'])
mock_get_device_path.assert_called_with(
self.connector, volume_replicas[0]['target_nqn'],
volume_replicas[0]['target_nqn'],
volume_replicas[0]['vol_uuid'])
def test_get_volume_paths_replicated(self):
@ -159,6 +144,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
connection_properties1 = {
'target_nqn': 'fakenqn',
'vol_uuid': 'fakeuuid',
'replica_count': 1,
'volume_replicas': [volume_replicas[0]]
}
mock_connect_target_volume.return_value = '/dev/nvme0n1'
@ -325,8 +311,10 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
root_helper=None,
run_as_root=True)
@mock.patch.object(nvmeof.NVMeOFConnector, 'is_raid_exists')
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_fs_type')
def test_disconnect_unreplicated_volume_nova(self, mock_get_fs_type):
def test_disconnect_unreplicated_volume_nova(self, mock_get_fs_type,
mock_raidexist):
connection_properties = {
'vol_uuid': 'fakeuuid',
'portals': [('10.0.0.1', 4420, 'tcp')],
@ -345,7 +333,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_end_raid.return_value = None
self.connector.disconnect_volume(connection_properties, None)
device_path = '/dev/md/' + connection_properties['alias']
mock_end_raid.assert_called_with(self.connector, device_path)
mock_end_raid.assert_called_with(device_path)
@mock.patch.object(nvmeof.NVMeOFConnector, 'end_raid')
def test_disconnect_replicated_volume_nova(self, mock_end_raid):
@ -355,7 +343,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
'device_path': '/dev/md/md1'
}
self.connector.disconnect_volume(connection_properties, None)
mock_end_raid.assert_called_with(self.connector, '/dev/md/md1')
mock_end_raid.assert_called_with('/dev/md/md1')
def test_disconnect_unreplicated_volume_cinder(self):
connection_properties = {
@ -373,7 +361,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
self.connector.disconnect_volume(connection_properties,
device_info,
ignore_errors=True)
mock_end_raid.assert_called_with(self.connector, '/dev/md/md1')
mock_end_raid.assert_called_with('/dev/md/md1')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_device_path')
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_size')
@ -390,7 +378,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
self.connector.extend_volume(connection_properties),
100)
mock_device_path.assert_called_with(
self.connector, volume_replicas[0]['target_nqn'],
volume_replicas[0]['target_nqn'],
volume_replicas[0]['vol_uuid'])
mock_device_size.assert_called_with('/dev/nvme0n1')
@ -408,10 +396,10 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
self.connector._extend_volume_replicated(
connection_properties), 100)
mock_device_path.assert_called_with(
self.connector, 'fakenqn', 'fakeuuid')
'fakenqn', 'fakeuuid')
mock_device_size.assert_called_with('/dev/nvme0n1')
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm')
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
@mock.patch.object(linuxscsi.LinuxSCSI, 'get_device_size')
def test_extend_volume_replicated(
self, mock_device_size, mock_mdadm):
@ -421,7 +409,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
100)
device_path = '/dev/md/' + connection_properties['alias']
mock_mdadm.assert_called_with(
self.connector, ['mdadm', '--grow', '--size', 'max', device_path])
['mdadm', '--grow', '--size', 'max', device_path])
mock_device_size.assert_called_with(device_path)
@mock.patch.object(nvmeof.NVMeOFConnector, 'rescan')
@ -434,7 +422,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
'fakenqn', 'fakeuuid', [('fake', 'portal', 'tcp')]),
'/dev/nvme0n1')
mock_device_path.assert_called_with(
self.connector, 'fakenqn', 'fakeuuid')
'fakenqn', 'fakeuuid')
@mock.patch.object(nvmeof.NVMeOFConnector, 'connect_to_portals')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_device_path')
@ -446,7 +434,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
self.connector._connect_target_volume, TARGET_NQN,
VOL_UUID, [('fake', 'portal', 'tcp')])
mock_device_path.assert_called_with(
self.connector, TARGET_NQN, VOL_UUID)
TARGET_NQN, VOL_UUID)
@mock.patch.object(nvmeof.NVMeOFConnector, 'connect_to_portals')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_device_path')
@ -458,7 +446,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
self.connector._connect_target_volume, 'fakenqn',
'fakeuuid', [fake_portal])
mock_device_path.assert_called_with(
self.connector, 'fakenqn', 'fakeuuid')
'fakenqn', 'fakeuuid')
@mock.patch.object(nvmeof.NVMeOFConnector, 'connect_to_portals')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_device_path')
@ -470,22 +458,22 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
'fakenqn', 'fakeuuid', [('fake', 'portal', 'tcp')]),
'/dev/nvme0n1')
mock_connect_portal.assert_called_with(
self.connector, 'fakenqn', [('fake', 'portal', 'tcp')])
'fakenqn', [('fake', 'portal', 'tcp')])
mock_device_path.assert_called_with(
self.connector, 'fakenqn', 'fakeuuid')
'fakenqn', 'fakeuuid')
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_nvme_cli')
@mock.patch.object(nvmeof.priv_nvme, 'run_nvme_cli')
def test_connect_to_portals(self, mock_nvme_cli):
nvme_command = (
'connect', '-a', '10.0.0.1', '-s', 4420, '-t',
'tcp', '-n', 'fakenqn', '-Q', '128', '-l', '-1')
self.assertEqual(
self.connector.connect_to_portals(
self.connector, 'fakenqn', [('10.0.0.1', 4420, 'tcp')]),
'fakenqn', [('10.0.0.1', 4420, 'tcp')]),
True)
mock_nvme_cli.assert_called_with(self.connector, nvme_command)
mock_nvme_cli.assert_called_with(nvme_command)
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_nvme_cli')
@mock.patch.object(nvmeof.priv_nvme, 'run_nvme_cli')
def test_connect_to_portals_rdma_no_conn(self, mock_nvme_cli):
mock_nvme_cli.side_effect = Exception()
nvme_command = (
@ -493,9 +481,9 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
'rdma', '-n', 'fakenqn', '-Q', '128', '-l', '-1')
self.assertEqual(
self.connector.connect_to_portals(
self.connector, 'fakenqn', [('10.0.0.1', 4420, 'RoCEv2')]),
'fakenqn', [('10.0.0.1', 4420, 'RoCEv2')]),
False)
mock_nvme_cli.assert_called_with(self.connector, nvme_command)
mock_nvme_cli.assert_called_with(nvme_command)
@mock.patch.object(nvmeof.NVMeOFConnector, 'stop_and_assemble_raid')
@mock.patch.object(nvmeof.NVMeOFConnector, '_is_device_in_raid')
@ -507,9 +495,9 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
['/dev/nvme1n1', '/dev/nvme1n2', '/dev/nvme1n3'],
'fakealias', 3),
'/dev/md/fakealias')
mock_device_raid.assert_called_with(self.connector, '/dev/nvme1n1')
mock_device_raid.assert_called_with('/dev/nvme1n1')
mock_stop_assemble_raid.assert_called_with(
self.connector, ['/dev/nvme1n1', '/dev/nvme1n2', '/dev/nvme1n3'],
['/dev/nvme1n1', '/dev/nvme1n2', '/dev/nvme1n3'],
'/dev/md/fakealias', False)
@mock.patch.object(nvmeof.NVMeOFConnector, '_is_device_in_raid')
@ -520,9 +508,9 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
self.connector._handle_replicated_volume,
['/dev/nvme1n1', '/dev/nvme1n2', '/dev/nvme1n3'],
'fakealias', 4)
mock_device_raid.assert_any_call(self.connector, '/dev/nvme1n1')
mock_device_raid.assert_any_call(self.connector, '/dev/nvme1n2')
mock_device_raid.assert_any_call(self.connector, '/dev/nvme1n3')
mock_device_raid.assert_any_call('/dev/nvme1n1')
mock_device_raid.assert_any_call('/dev/nvme1n2')
mock_device_raid.assert_any_call('/dev/nvme1n3')
@mock.patch.object(nvmeof.NVMeOFConnector, 'create_raid')
@mock.patch.object(nvmeof.NVMeOFConnector, '_is_device_in_raid')
@ -534,22 +522,23 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
['/dev/nvme1n1', '/dev/nvme1n2', '/dev/nvme1n3'],
'fakealias', 3),
'/dev/md/fakealias')
mock_device_raid.assert_any_call(self.connector, '/dev/nvme1n1')
mock_device_raid.assert_any_call(self.connector, '/dev/nvme1n2')
mock_device_raid.assert_any_call(self.connector, '/dev/nvme1n3')
mock_device_raid.assert_any_call('/dev/nvme1n1')
mock_device_raid.assert_any_call('/dev/nvme1n2')
mock_device_raid.assert_any_call('/dev/nvme1n3')
mock_create_raid.assert_called_with(
self.connector, ['/dev/nvme1n1', '/dev/nvme1n2', '/dev/nvme1n3'],
['/dev/nvme1n1', '/dev/nvme1n2', '/dev/nvme1n3'],
'1', 'fakealias', 'fakealias', False)
@mock.patch.object(nvmeof.NVMeOFConnector, 'stop_raid')
@mock.patch.object(nvmeof.NVMeOFConnector, 'ks_readlink')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_md_name')
def test_stop_and_assemble_raid_existing_simple(
self, mock_md_name, mock_readlink):
self, mock_md_name, mock_readlink, mock_stopraid):
mock_readlink.return_value = ''
mock_md_name.return_value = 'mdalias'
self.assertIsNone(self.connector.stop_and_assemble_raid(
self.connector, ['/dev/sda'], '/dev/md/mdalias', False))
mock_md_name.assert_called_with(self.connector, 'sda')
['/dev/sda'], '/dev/md/mdalias', False))
mock_md_name.assert_called_with('sda')
mock_readlink.assert_called_with('/dev/md/mdalias')
@mock.patch.object(nvmeof.NVMeOFConnector, 'ks_readlink')
@ -559,51 +548,49 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_readlink.return_value = '/dev/md/mdalias'
mock_md_name.return_value = 'mdalias'
self.assertIsNone(self.connector.stop_and_assemble_raid(
self.connector, ['/dev/sda'], '/dev/md/mdalias', False))
mock_md_name.assert_called_with(self.connector, 'sda')
['/dev/sda'], '/dev/md/mdalias', False))
mock_md_name.assert_called_with('sda')
mock_readlink.assert_called_with('/dev/md/mdalias')
@mock.patch.object(nvmeof.NVMeOFConnector, 'stop_raid')
@mock.patch.object(nvmeof.NVMeOFConnector, 'assemble_raid')
@mock.patch.object(nvmeof.NVMeOFConnector, 'ks_readlink')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_md_name')
def test_stop_and_assemble_raid_err(self, mock_md_name, mock_readlink,
mock_assemble):
mock_assemble, mock_stopraid):
mock_readlink.return_value = '/dev/md/mdalias'
mock_md_name.return_value = 'dummy'
mock_assemble.side_effect = Exception()
self.assertIsNone(self.connector.stop_and_assemble_raid(
self.connector, ['/dev/sda'], '/dev/md/mdalias', False))
mock_md_name.assert_called_with(self.connector, 'sda')
['/dev/sda'], '/dev/md/mdalias', False))
mock_md_name.assert_called_with('sda')
mock_readlink.assert_called_with('/dev/md/mdalias')
mock_assemble.assert_called_with(self.connector, ['/dev/sda'],
mock_assemble.assert_called_with(['/dev/sda'],
'/dev/md/mdalias', False)
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm')
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_assemble_raid_simple(self, mock_run_mdadm):
self.assertEqual(self.connector.assemble_raid(
self.connector, ['/dev/sda'], '/dev/md/md1', True), True)
['/dev/sda'], '/dev/md/md1', True), True)
mock_run_mdadm.assert_called_with(
self.connector,
['mdadm', '--assemble', '--run', '/dev/md/md1', '-o', '/dev/sda'],
True)
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm')
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_assemble_raid_simple_err(self, mock_run_mdadm):
mock_run_mdadm.side_effect = putils.ProcessExecutionError()
self.assertRaises(putils.ProcessExecutionError,
self.connector.assemble_raid, self.connector,
self.connector.assemble_raid,
['/dev/sda'], '/dev/md/md1', True)
mock_run_mdadm.assert_called_with(
self.connector,
['mdadm', '--assemble', '--run', '/dev/md/md1', '-o', '/dev/sda'],
True)
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm')
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_create_raid_cmd_simple(self, mock_run_mdadm):
self.assertIsNone(self.connector.create_raid(
self.connector, ['/dev/sda'], '1', 'md1', 'name', True))
['/dev/sda'], '1', 'md1', 'name', True))
mock_run_mdadm.assert_called_with(
self.connector,
['mdadm', '-C', '-o', 'md1', '-R', '-N', 'name', '--level', '1',
'--raid-devices=1', '--bitmap=internal', '--homehost=any',
'--failfast', '--assume-clean', '/dev/sda'])
@ -614,9 +601,9 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_raid_exists.return_value = True
mock_stop_raid.return_value = False
self.assertIsNone(self.connector.end_raid(
self.connector, '/dev/md/md1'))
mock_raid_exists.assert_called_with(self.connector, '/dev/md/md1')
mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1')
'/dev/md/md1'))
mock_raid_exists.assert_called_with('/dev/md/md1')
mock_stop_raid.assert_called_with('/dev/md/md1')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(nvmeof.NVMeOFConnector, 'stop_raid')
@ -626,9 +613,9 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_stop_raid.return_value = False
mock_os.return_value = True
self.assertIsNone(self.connector.end_raid(
self.connector, '/dev/md/md1'))
mock_raid_exists.assert_called_with(self.connector, '/dev/md/md1')
mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1')
'/dev/md/md1'))
mock_raid_exists.assert_called_with('/dev/md/md1')
mock_stop_raid.assert_called_with('/dev/md/md1')
mock_os.assert_called_with('/dev/md/md1')
@mock.patch.object(os.path, 'exists')
@ -639,86 +626,77 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_stop_raid.side_effect = Exception()
mock_os.return_value = True
self.assertIsNone(self.connector.end_raid(
self.connector, '/dev/md/md1'))
mock_raid_exists.assert_called_with(self.connector, '/dev/md/md1')
mock_stop_raid.assert_called_with(self.connector, '/dev/md/md1')
'/dev/md/md1'))
mock_raid_exists.assert_called_with('/dev/md/md1')
mock_stop_raid.assert_called_with('/dev/md/md1')
mock_os.assert_called_with('/dev/md/md1')
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm')
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_stop_raid_simple(self, mock_run_mdadm):
mock_run_mdadm.return_value = 'mdadm output'
mock_run_mdadm.return_value = 'mdadm output', None
self.assertEqual(self.connector.stop_raid(
self.connector, '/dev/md/md1'), 'mdadm output')
'/dev/md/md1'), 'mdadm output')
mock_run_mdadm.assert_called_with(
self.connector, ['mdadm', '--stop', '/dev/md/md1'])
['mdadm', '--stop', '/dev/md/md1'])
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_mdadm')
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_remove_raid_simple(self, mock_run_mdadm):
self.assertIsNone(self.connector.remove_raid(
self.connector, '/dev/md/md1'))
'/dev/md/md1'))
mock_run_mdadm.assert_called_with(
self.connector, ['mdadm', '--remove', '/dev/md/md1'])
['mdadm', '--remove', '/dev/md/md1'])
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_nvme_cli')
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
@mock.patch.object(nvmeof.priv_nvme, 'run_nvme_cli')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_controller')
def test_rescan(self, mock_get_nvme_controller, mock_run_nvme_cli):
mock_get_nvme_controller.return_value = 'nvme1'
mock_run_nvme_cli.return_value = None
result = self.connector.rescan(EXECUTOR, TARGET_NQN, VOL_UUID)
result = self.connector.rescan(TARGET_NQN, VOL_UUID)
self.assertIsNone(result)
mock_get_nvme_controller.assert_called_with(EXECUTOR, TARGET_NQN)
mock_get_nvme_controller.assert_called_with(TARGET_NQN)
nvme_command = ('ns-rescan', NVME_DEVICE_PATH)
mock_run_nvme_cli.assert_called_with(EXECUTOR, nvme_command)
mock_run_nvme_cli.assert_called_with(nvme_command)
@mock.patch.object(nvmeof.NVMeOFConnector, 'run_nvme_cli')
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
@mock.patch.object(nvmeof.priv_nvme, 'run_nvme_cli')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_controller')
def test_rescan_err(self, mock_get_nvme_controller, mock_run_nvme_cli):
mock_get_nvme_controller.return_value = 'nvme1'
mock_run_nvme_cli.side_effect = Exception()
self.assertRaises(exception.CommandExecutionFailed,
self.connector.rescan, EXECUTOR, TARGET_NQN,
self.connector.rescan, TARGET_NQN,
VOL_UUID)
mock_get_nvme_controller.assert_called_with(EXECUTOR, TARGET_NQN)
mock_get_nvme_controller.assert_called_with(TARGET_NQN)
nvme_command = ('ns-rescan', NVME_DEVICE_PATH)
mock_run_nvme_cli.assert_called_with(EXECUTOR, nvme_command)
mock_run_nvme_cli.assert_called_with(nvme_command)
@mock.patch.object(executor.Executor, '_execute')
def test_is_raid_exists_not(self, mock_execute):
mock_execute.return_value = (VOL_UUID + "\n", "")
result = self.connector.is_raid_exists(EXECUTOR, NVME_DEVICE_PATH)
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_is_raid_exists_not(self, mock_runmdadm):
mock_runmdadm.return_value = (VOL_UUID + "\n", "")
result = self.connector.is_raid_exists(NVME_DEVICE_PATH)
self.assertEqual(False, result)
cmd = ['mdadm', '--detail', NVME_DEVICE_PATH]
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
mock_runmdadm.assert_called_with(cmd)
@mock.patch.object(executor.Executor, '_execute')
def test_is_raid_exists(self, mock_execute):
mock_execute.return_value = (NVME_DEVICE_PATH + ':' + "\n", "")
result = self.connector.is_raid_exists(EXECUTOR, NVME_DEVICE_PATH)
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_is_raid_exists(self, mock_runmdadm):
mock_runmdadm.return_value = (NVME_DEVICE_PATH + ':' + "\n", "")
result = self.connector.is_raid_exists(NVME_DEVICE_PATH)
self.assertEqual(True, result)
cmd = ['mdadm', '--detail', NVME_DEVICE_PATH]
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
mock_runmdadm.assert_called_with(cmd)
@mock.patch.object(executor.Executor, '_execute')
def test_is_raid_exists_err(self, mock_execute):
mock_execute.side_effect = putils.ProcessExecutionError
result = self.connector.is_raid_exists(EXECUTOR, NVME_DEVICE_PATH)
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_is_raid_exists_err(self, mock_runmdadm):
mock_runmdadm.side_effect = putils.ProcessExecutionError
result = self.connector.is_raid_exists(NVME_DEVICE_PATH)
self.assertEqual(False, result)
cmd = ['mdadm', '--detail', NVME_DEVICE_PATH]
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
mock_runmdadm.assert_called_with(cmd)
@mock.patch.object(executor.Executor, '_execute')
@mock.patch.object(nvmeof.priv_rootwrap, 'custom_execute')
def test_get_md_name(self, mock_execute):
mock_execute.return_value = ('nvme1' + "\n", "")
result = self.connector.get_md_name(EXECUTOR, NVME_DEVICE_PATH)
result = self.connector.get_md_name(NVME_DEVICE_PATH)
self.assertEqual('nvme1', result)
get_md_cmd = 'cat /proc/mdstat | grep /dev/nvme1 | awk \'{print $1;}\''
cmd = ['bash', '-c', get_md_cmd]
@ -727,10 +705,10 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
@mock.patch.object(executor.Executor, '_execute')
@mock.patch.object(nvmeof.priv_rootwrap, 'custom_execute')
def test_get_md_name_err(self, mock_execute):
mock_execute.side_effect = putils.ProcessExecutionError()
result = self.connector.get_md_name(EXECUTOR, NVME_DEVICE_PATH)
result = self.connector.get_md_name(NVME_DEVICE_PATH)
self.assertIsNone(result)
get_md_cmd = 'cat /proc/mdstat | grep /dev/nvme1 | awk \'{print $1;}\''
cmd = ['bash', '-c', get_md_cmd]
@ -739,116 +717,82 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
@mock.patch.object(executor.Executor, '_execute')
def test_is_device_in_raid(self, mock_execute):
mock_execute.return_value = (NVME_DEVICE_PATH + ':' + "\n", "")
result = self.connector._is_device_in_raid(self.connector,
NVME_DEVICE_PATH)
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_is_device_in_raid(self, mock_runmdadm):
mock_runmdadm.return_value = (NVME_DEVICE_PATH + ':' + "\n", "")
result = self.connector._is_device_in_raid(NVME_DEVICE_PATH)
self.assertEqual(True, result)
cmd = ['mdadm', '--examine', NVME_DEVICE_PATH]
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
mock_runmdadm.assert_called_with(cmd)
@mock.patch.object(executor.Executor, '_execute')
def test_is_device_in_raid_not_found(self, mock_execute):
mock_execute.return_value = (VOL_UUID + "\n", "")
result = self.connector._is_device_in_raid(self.connector,
NVME_DEVICE_PATH)
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_is_device_in_raid_not_found(self, mock_runmdadm):
mock_runmdadm.return_value = (VOL_UUID + "\n", "")
result = self.connector._is_device_in_raid(NVME_DEVICE_PATH)
self.assertEqual(False, result)
cmd = ['mdadm', '--examine', NVME_DEVICE_PATH]
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
mock_runmdadm.assert_called_with(cmd)
@mock.patch.object(executor.Executor, '_execute')
def test_is_device_in_raid_err(self, mock_execute):
mock_execute.side_effect = putils.ProcessExecutionError()
result = self.connector._is_device_in_raid(self.connector,
NVME_DEVICE_PATH)
@mock.patch.object(nvmeof.priv_nvme, 'run_mdadm')
def test_is_device_in_raid_err(self, mock_runmdadm):
mock_runmdadm.side_effect = putils.ProcessExecutionError()
result = self.connector._is_device_in_raid(NVME_DEVICE_PATH)
self.assertEqual(False, result)
cmd = ['mdadm', '--examine', NVME_DEVICE_PATH]
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
mock_runmdadm.assert_called_with(cmd)
@mock.patch.object(executor.Executor, '_execute')
def test_run_mdadm(self, mock_execute):
mock_execute.return_value = (VOL_UUID + "\n", "")
cmd = ['mdadm', '--examine', NVME_DEVICE_PATH]
result = self.connector.run_mdadm(EXECUTOR, cmd)
self.assertEqual(VOL_UUID, result)
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
@mock.patch.object(executor.Executor, '_execute')
def test_run_mdadm_err(self, mock_execute):
mock_execute.side_effect = putils.ProcessExecutionError()
cmd = ['mdadm', '--examine', NVME_DEVICE_PATH]
result = self.connector.run_mdadm(EXECUTOR, cmd)
self.assertIsNone(result)
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
@mock.patch.object(executor.Executor, '_execute')
@mock.patch.object(os.path, 'exists')
@mock.patch.object(nvmeof.priv_rootwrap, 'custom_execute')
@mock.patch.object(glob, 'glob')
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_nvme_controller')
@mock.patch.object(nvmeof.NVMeOFConnector, 'get_nvme_controller')
def test_get_nvme_device_path(self, mock_get_nvme_controller, mock_glob,
mock_execute):
mock_execute, mock_os_exists):
mock_get_nvme_controller.return_value = 'nvme1'
block_dev_path = '/sys/class/nvme-fabrics/ctl/nvme1/nvme1n*'
mock_glob.side_effect = [['/sys/class/nvme-fabrics/ctl/nvme1/nvme1n1']]
mock_os_exists.return_value = True
block_dev_path = '/sys/class/block/nvme1n*'
mock_glob.return_value = ['/sys/class/block/nvme1n1']
mock_execute.return_value = (VOL_UUID + "\n", "")
cmd = ['cat', '/sys/class/nvme-fabrics/ctl/nvme1/nvme1n1/uuid']
result = self.connector.get_nvme_device_path(EXECUTOR, TARGET_NQN,
cmd = ['cat', '/sys/class/block/nvme1n1/uuid']
result = self.connector.get_nvme_device_path(TARGET_NQN,
VOL_UUID)
mock_get_nvme_controller.assert_called_with(EXECUTOR, TARGET_NQN)
mock_get_nvme_controller.assert_called_with(TARGET_NQN)
self.assertEqual(NVME_NS_PATH, result)
mock_glob.assert_any_call(block_dev_path)
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
mock_execute.assert_called_with(*cmd)
def execute_side_effect(self, value, run_as_root, root_helper):
def execute_side_effect(self, value):
if 'nqn' in value:
return TARGET_NQN + "\n", ""
if 'state' in value:
return 'live' + "\n", ""
def execute_side_effect_not_live(self, value, run_as_root, root_helper):
def execute_side_effect_not_live(self, value):
if 'nqn' in value:
return TARGET_NQN + "\n", ""
if 'state' in value:
return 'dead' + "\n", ""
def execute_side_effect_not_found(self, value, run_as_root, root_helper):
def execute_side_effect_not_found(self, value):
if 'nqn' in value:
return "dummy" + "\n", ""
if 'state' in value:
return 'live' + "\n", ""
@mock.patch.object(executor.Executor, '_execute',
@mock.patch.object(nvmeof.priv_rootwrap, 'custom_execute',
side_effect=execute_side_effect)
@mock.patch.object(glob, 'glob')
def test_get_nvme_controller(self, mock_glob, mock_execute):
ctrl_path = '/sys/class/nvme-fabrics/ctl/nvme*'
mock_glob.side_effect = [['/sys/class/nvme-fabrics/ctl/nvme1']]
cmd = ['cat', '/sys/class/nvme-fabrics/ctl/nvme1/state']
result = self.connector._get_nvme_controller(EXECUTOR, TARGET_NQN)
result = self.connector.get_nvme_controller(TARGET_NQN)
self.assertEqual('nvme1', result)
mock_glob.assert_any_call(ctrl_path)
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
@mock.patch.object(executor.Executor, '_execute',
@mock.patch.object(nvmeof.priv_rootwrap, 'custom_execute',
side_effect=execute_side_effect_not_live)
@mock.patch.object(glob, 'glob')
def test_get_nvme_controller_not_live(self, mock_glob, mock_execute):
@ -856,14 +800,14 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_glob.side_effect = [['/sys/class/nvme-fabrics/ctl/nvme1']]
cmd = ['cat', '/sys/class/nvme-fabrics/ctl/nvme1/state']
self.assertRaises(exception.VolumeDeviceNotFound,
self.connector._get_nvme_controller, EXECUTOR,
self.connector.get_nvme_controller,
TARGET_NQN)
mock_glob.assert_any_call(ctrl_path)
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
@mock.patch.object(executor.Executor, '_execute',
@mock.patch.object(nvmeof.priv_rootwrap, 'custom_execute',
side_effect=execute_side_effect_not_found)
@mock.patch.object(glob, 'glob')
def test_get_nvme_controller_not_found(self, mock_glob, mock_execute):
@ -871,7 +815,7 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_glob.side_effect = [['/sys/class/nvme-fabrics/ctl/nvme1']]
cmd = ['cat', '/sys/class/nvme-fabrics/ctl/nvme1/state']
self.assertRaises(exception.VolumeDeviceNotFound,
self.connector._get_nvme_controller, EXECUTOR,
self.connector.get_nvme_controller,
TARGET_NQN)
mock_glob.assert_any_call(ctrl_path)
args, kwargs = mock_execute.call_args
@ -904,19 +848,12 @@ class NVMeOFConnectorTestCase(test_connector.ConnectorTestCase):
mock_create.assert_not_called()
self.assertIsNone(result)
@mock.patch.object(executor.Executor, '_execute')
def test_run_nvme_cli(self, mock_execute):
mock_execute.return_value = ("\n", "")
cmd = 'dummy command'
result = self.connector.run_nvme_cli(EXECUTOR, cmd)
self.assertEqual(("\n", ""), result)
def test_ks_readlink(self):
dest = 'dummy path'
result = self.connector.ks_readlink(dest)
self.assertEqual('', result)
@mock.patch.object(executor.Executor, '_execute')
@mock.patch.object(nvmeof.priv_rootwrap, 'custom_execute')
def test_get_fs_type_err(self, mock_execute):
mock_execute.side_effect = putils.ProcessExecutionError()
result = self.connector._get_fs_type(NVME_DEVICE_PATH)

View File

@ -43,7 +43,7 @@ class ConnectorUtilsTestCase(test_base.TestCase):
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_system_uuid',
return_value=None)
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_uuid',
@mock.patch.object(nvmeof.priv_nvme, 'get_host_uuid',
return_value=None)
@mock.patch.object(nvmeof.NVMeOFConnector, '_get_host_nqn',
return_value=None)

View File

@ -95,3 +95,42 @@ class PrivNVMeTestCase(base.TestCase):
exist_ok=True)
mock_exec.assert_called_once_with('nvme', 'show-hostnqn')
self.assertEqual('', res)
@mock.patch.object(rootwrap, 'custom_execute')
def test_get_host_uuid(self, mock_execute):
mock_execute.return_value = 'fakeuuid', ''
self.assertEqual('fakeuuid', privsep_nvme.get_host_uuid())
@mock.patch.object(rootwrap, 'custom_execute')
def test_get_host_uuid_err(self, mock_execute):
mock_execute.side_effect = putils.ProcessExecutionError()
self.assertIsNone(privsep_nvme.get_host_uuid())
@mock.patch.object(rootwrap, 'custom_execute')
def test_run_nvme_cli(self, mock_execute):
mock_execute.return_value = ("\n", "")
cmd = 'dummy command'
result = privsep_nvme.run_nvme_cli(cmd)
self.assertEqual(("\n", ""), result)
@mock.patch.object(rootwrap, 'custom_execute')
def test_run_mdadm(self, mock_execute):
mock_execute.return_value = "fakeuuid"
cmd = ['mdadm', '--examine', '/dev/nvme1']
result = privsep_nvme.run_mdadm(cmd)
self.assertEqual('fakeuuid', result)
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])
@mock.patch.object(rootwrap, 'custom_execute')
def test_run_mdadm_err(self, mock_execute):
mock_execute.side_effect = putils.ProcessExecutionError()
cmd = ['mdadm', '--examine', '/dev/nvme1']
result = privsep_nvme.run_mdadm(cmd)
self.assertIsNone(result)
args, kwargs = mock_execute.call_args
self.assertEqual(args[0], cmd[0])
self.assertEqual(args[1], cmd[1])
self.assertEqual(args[2], cmd[2])

View File

@ -0,0 +1,8 @@
---
features:
- |
`https://blueprints.launchpad.net/cinder/+spec/nvmeof-connection-agent`
NVMeOF Connection Agent
A console script that when launched, is responsible for initiator-side
monitoring and self healing of NVMeoF connections and replicated mdraid
volumes.