cinder/cinder/volume/targets/nvmet.py

220 lines
8.0 KiB
Python

# 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 tempfile
from oslo_concurrency import processutils as putils
from oslo_log import log as logging
from oslo_serialization import jsonutils as json
from oslo_utils import excutils
from oslo_utils import uuidutils
from cinder import exception
from cinder.privsep import nvmcli
import cinder.privsep.path
from cinder import utils
from cinder.volume.targets import nvmeof
LOG = logging.getLogger(__name__)
class NVMETTargetAddError(exception.CinderException):
message = "Failed to add subsystem: %(subsystem)s"
class NVMETTargetDeleteError(exception.CinderException):
message = "Failed to delete subsystem: %(subsystem)s"
class NVMET(nvmeof.NVMeOF):
@utils.synchronized('nvmetcli', external=True)
def create_nvmeof_target(self,
volume_id,
subsystem_name,
target_ip,
target_port,
transport_type,
nvmet_port_id,
ns_id,
volume_path):
# Create NVME subsystem for previously created LV
nvmf_subsystems = self._get_available_nvmf_subsystems()
# Check if subsystem already exists
search_for_subsystem = self._get_nvmf_subsystem(
nvmf_subsystems, volume_id)
if search_for_subsystem is None:
newly_added_subsystem = self._add_nvmf_subsystem(
nvmf_subsystems,
target_ip,
target_port,
nvmet_port_id,
subsystem_name,
ns_id, volume_id, volume_path)
if newly_added_subsystem is None:
LOG.error('Failed to add subsystem: %s', subsystem_name)
raise NVMETTargetAddError(subsystem=subsystem_name)
LOG.info('Added subsystem: %s', newly_added_subsystem)
search_for_subsystem = newly_added_subsystem
else:
LOG.debug('Skip creating subsystem %s as '
'it already exists.', search_for_subsystem)
return {
'location': self.get_nvmeof_location(
search_for_subsystem,
target_ip,
target_port,
transport_type,
ns_id),
'auth': ''}
def _restore(self, nvmf_subsystems):
# Dump updated JSON dict to append new subsystem
with tempfile.NamedTemporaryFile(mode='w') as tmp_fd:
tmp_fd.write(json.dumps(nvmf_subsystems))
tmp_fd.flush()
try:
out, err = nvmcli.restore(tmp_fd.name)
except putils.ProcessExecutionError:
with excutils.save_and_reraise_exception():
LOG.exception('Error from nvmetcli restore')
def _add_nvmf_subsystem(self, nvmf_subsystems, target_ip, target_port,
nvmet_port_id, nvmet_subsystem_name, nvmet_ns_id,
volume_id, volume_path):
subsystem_name = self._get_target_info(nvmet_subsystem_name, volume_id)
# Create JSON sections for the new subsystem to be created
# Port section
port_section = {
"addr": {
"adrfam": "ipv4",
"traddr": target_ip,
"treq": "not specified",
"trsvcid": target_port,
"trtype": "rdma"
},
"portid": nvmet_port_id,
"referrals": [],
"subsystems": [subsystem_name]
}
nvmf_subsystems['ports'].append(port_section)
# Subsystem section
subsystem_section = {
"allowed_hosts": [],
"attr": {
"allow_any_host": "1"
},
"namespaces": [
{
"device": {
"nguid": str(uuidutils.generate_uuid()),
"path": volume_path,
},
"enable": 1,
"nsid": nvmet_ns_id
}
],
"nqn": subsystem_name}
nvmf_subsystems['subsystems'].append(subsystem_section)
LOG.info(
'Trying to load the following subsystems: %s', nvmf_subsystems)
self._restore(nvmf_subsystems)
return subsystem_name
@utils.synchronized('nvmetcli', external=True)
def delete_nvmeof_target(self, volume):
nvmf_subsystems = self._get_available_nvmf_subsystems()
subsystem_name = self._get_nvmf_subsystem(
nvmf_subsystems, volume['id'])
if subsystem_name:
removed_subsystem = self._delete_nvmf_subsystem(
nvmf_subsystems, subsystem_name)
if removed_subsystem is None:
LOG.error(
'Failed to delete subsystem: %s', subsystem_name)
raise NVMETTargetDeleteError(subsystem=subsystem_name)
elif removed_subsystem == subsystem_name:
LOG.info(
'Managed to delete subsystem: %s', subsystem_name)
return removed_subsystem
else:
LOG.info("Skipping remove_export. No NVMe subsystem "
"for volume: %s", volume['id'])
def _delete_nvmf_subsystem(self, nvmf_subsystems, subsystem_name):
LOG.debug(
'Removing this subsystem: %s', subsystem_name)
for port in nvmf_subsystems['ports']:
if subsystem_name in port['subsystems']:
port['subsystems'].remove(subsystem_name)
break
for subsys in nvmf_subsystems['subsystems']:
if subsys['nqn'] == subsystem_name:
nvmf_subsystems['subsystems'].remove(subsys)
break
LOG.debug(
'Newly loaded subsystems will be: %s', nvmf_subsystems)
self._restore(nvmf_subsystems)
return subsystem_name
def _get_nvmf_subsystem(self, nvmf_subsystems, volume_id):
subsystem_name = self._get_target_info(
self.nvmet_subsystem_name, volume_id)
for subsys in nvmf_subsystems['subsystems']:
if subsys['nqn'] == subsystem_name:
return subsystem_name
def _get_available_nvmf_subsystems(self):
__, tmp_file_path = tempfile.mkstemp(prefix='nvmet')
# nvmetcli doesn't support printing to stdout yet,
try:
out, err = nvmcli.save(tmp_file_path)
except putils.ProcessExecutionError:
with excutils.save_and_reraise_exception():
LOG.exception('Error from nvmetcli save')
self._delete_file(tmp_file_path)
# temp file must be readable by this process user
# in order to avoid executing cat as root
with utils.temporary_chown(tmp_file_path):
try:
out = cinder.privsep.path.readfile(tmp_file_path)
except putils.ProcessExecutionError:
with excutils.save_and_reraise_exception():
LOG.exception('Failed to read: %s', tmp_file_path)
self._delete_file(tmp_file_path)
nvmf_subsystems = json.loads(out)
self._delete_file(tmp_file_path)
return nvmf_subsystems
def _get_target_info(self, subsystem, volume_id):
return "nqn.%s-%s" % (subsystem, volume_id)
def _delete_file(self, file_path):
try:
cinder.privsep.path.removefile(file_path)
except putils.ProcessExecutionError:
LOG.exception('Failed to delete file: %s', file_path)