af276eb0b2
The cfg API is now available via the oslo-config library, so switch to it and remove the copied-and-pasted version. Add the 2013.1b4 tarball to tools/pip-requires - this will be changed to 'oslo-config>=2013.1' when oslo-config is published to pypi. This will happen in time for grizzly final. Add dependency_links to setup.py so that oslo-config can be installed from the tarball URL specified in pip-requires. Remove the 'deps = pep8==1.3.3' from tox.ini as it means all the other deps get installed with easy_install which can't install oslo-config from the URL. Make tools/hacking.py include oslo in IMPORT_EXCEPTIONS like it already does for paste. It turns out imp.find_module() doesn't correct handle namespace packages. Retain dummy cfg.py file until keystoneclient middleware has been updated (I18c450174277c8e2d15ed93879da6cd92074c27a). Change-Id: I4815aeb8a9341a31a250e920157f15ee15cfc5bc
269 lines
9.4 KiB
Python
269 lines
9.4 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
# coding=utf-8
|
|
|
|
# Copyright (c) 2012 NTT DOCOMO, INC.
|
|
# 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 re
|
|
|
|
from oslo.config import cfg
|
|
|
|
from nova import context as nova_context
|
|
from nova import exception
|
|
from nova.openstack.common import importutils
|
|
from nova.openstack.common import log as logging
|
|
from nova import utils
|
|
from nova.virt.baremetal import db as bmdb
|
|
from nova.virt.libvirt import utils as libvirt_utils
|
|
|
|
opts = [
|
|
cfg.BoolOpt('use_unsafe_iscsi',
|
|
default=False,
|
|
help='Do not set this out of dev/test environments. '
|
|
'If a node does not have a fixed PXE IP address, '
|
|
'volumes are exported with globally opened ACL'),
|
|
cfg.StrOpt('iscsi_iqn_prefix',
|
|
default='iqn.2010-10.org.openstack.baremetal',
|
|
help='iSCSI IQN prefix used in baremetal volume connections.'),
|
|
]
|
|
|
|
baremetal_group = cfg.OptGroup(name='baremetal',
|
|
title='Baremetal Options')
|
|
|
|
CONF = cfg.CONF
|
|
CONF.register_group(baremetal_group)
|
|
CONF.register_opts(opts, baremetal_group)
|
|
|
|
CONF.import_opt('host', 'nova.netconf')
|
|
CONF.import_opt('use_ipv6', 'nova.netconf')
|
|
CONF.import_opt('libvirt_volume_drivers', 'nova.virt.libvirt.driver')
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def _get_baremetal_node_by_instance_uuid(instance_uuid):
|
|
context = nova_context.get_admin_context()
|
|
return bmdb.bm_node_get_by_instance_uuid(context, instance_uuid)
|
|
|
|
|
|
def _create_iscsi_export_tgtadm(path, tid, iqn):
|
|
utils.execute('tgtadm', '--lld', 'iscsi',
|
|
'--mode', 'target',
|
|
'--op', 'new',
|
|
'--tid', tid,
|
|
'--targetname', iqn,
|
|
run_as_root=True)
|
|
utils.execute('tgtadm', '--lld', 'iscsi',
|
|
'--mode', 'logicalunit',
|
|
'--op', 'new',
|
|
'--tid', tid,
|
|
'--lun', '1',
|
|
'--backing-store', path,
|
|
run_as_root=True)
|
|
|
|
|
|
def _allow_iscsi_tgtadm(tid, address):
|
|
utils.execute('tgtadm', '--lld', 'iscsi',
|
|
'--mode', 'target',
|
|
'--op', 'bind',
|
|
'--tid', tid,
|
|
'--initiator-address', address,
|
|
run_as_root=True)
|
|
|
|
|
|
def _delete_iscsi_export_tgtadm(tid):
|
|
try:
|
|
utils.execute('tgtadm', '--lld', 'iscsi',
|
|
'--mode', 'logicalunit',
|
|
'--op', 'delete',
|
|
'--tid', tid,
|
|
'--lun', '1',
|
|
run_as_root=True)
|
|
except exception.ProcessExecutionError:
|
|
pass
|
|
try:
|
|
utils.execute('tgtadm', '--lld', 'iscsi',
|
|
'--mode', 'target',
|
|
'--op', 'delete',
|
|
'--tid', tid,
|
|
run_as_root=True)
|
|
except exception.ProcessExecutionError:
|
|
pass
|
|
# Check if the tid is deleted, that is, check the tid no longer exists.
|
|
# If the tid dose not exist, tgtadm returns with exit_code 22.
|
|
# utils.execute() can check the exit_code if check_exit_code parameter is
|
|
# passed. But, regardless of whether check_exit_code contains 0 or not,
|
|
# if the exit_code is 0, the function dose not report errors. So we have to
|
|
# catch a ProcessExecutionError and test its exit_code is 22.
|
|
try:
|
|
utils.execute('tgtadm', '--lld', 'iscsi',
|
|
'--mode', 'target',
|
|
'--op', 'show',
|
|
'--tid', tid,
|
|
run_as_root=True)
|
|
except exception.ProcessExecutionError as e:
|
|
if e.exit_code == 22:
|
|
# OK, the tid is deleted
|
|
return
|
|
raise
|
|
raise exception.NovaException(_(
|
|
'baremetal driver was unable to delete tid %s') % tid)
|
|
|
|
|
|
def _show_tgtadm():
|
|
out, _ = utils.execute('tgtadm', '--lld', 'iscsi',
|
|
'--mode', 'target',
|
|
'--op', 'show',
|
|
run_as_root=True)
|
|
return out
|
|
|
|
|
|
def _list_backingstore_path():
|
|
out = _show_tgtadm()
|
|
l = []
|
|
for line in out.split('\n'):
|
|
m = re.search(r'Backing store path: (.*)$', line)
|
|
if m:
|
|
if '/' in m.group(1):
|
|
l.append(m.group(1))
|
|
return l
|
|
|
|
|
|
def _get_next_tid():
|
|
out = _show_tgtadm()
|
|
last_tid = 0
|
|
for line in out.split('\n'):
|
|
m = re.search(r'^Target (\d+):', line)
|
|
if m:
|
|
tid = int(m.group(1))
|
|
if last_tid < tid:
|
|
last_tid = tid
|
|
return last_tid + 1
|
|
|
|
|
|
def _find_tid(iqn):
|
|
out = _show_tgtadm()
|
|
pattern = r'^Target (\d+): *' + re.escape(iqn)
|
|
for line in out.split('\n'):
|
|
m = re.search(pattern, line)
|
|
if m:
|
|
return int(m.group(1))
|
|
return None
|
|
|
|
|
|
def _get_iqn(instance_name, mountpoint):
|
|
mp = mountpoint.replace('/', '-').strip('-')
|
|
iqn = '%s:%s-%s' % (CONF.baremetal.iscsi_iqn_prefix,
|
|
instance_name,
|
|
mp)
|
|
return iqn
|
|
|
|
|
|
class VolumeDriver(object):
|
|
|
|
def __init__(self, virtapi):
|
|
super(VolumeDriver, self).__init__()
|
|
self.virtapi = virtapi
|
|
self._initiator = None
|
|
|
|
def get_volume_connector(self, instance):
|
|
if not self._initiator:
|
|
self._initiator = libvirt_utils.get_iscsi_initiator()
|
|
if not self._initiator:
|
|
LOG.warn(_('Could not determine iscsi initiator name '
|
|
'for instance %s') % instance)
|
|
return {
|
|
'ip': CONF.my_ip,
|
|
'initiator': self._initiator,
|
|
'host': CONF.host,
|
|
}
|
|
|
|
def attach_volume(self, connection_info, instance, mountpoint):
|
|
raise NotImplementedError()
|
|
|
|
def detach_volume(self, connection_info, instance, mountpoint):
|
|
raise NotImplementedError()
|
|
|
|
|
|
class LibvirtVolumeDriver(VolumeDriver):
|
|
"""The VolumeDriver deligates to nova.virt.libvirt.volume."""
|
|
|
|
def __init__(self, virtapi):
|
|
super(LibvirtVolumeDriver, self).__init__(virtapi)
|
|
self.volume_drivers = {}
|
|
for driver_str in CONF.libvirt_volume_drivers:
|
|
driver_type, _sep, driver = driver_str.partition('=')
|
|
driver_class = importutils.import_class(driver)
|
|
self.volume_drivers[driver_type] = driver_class(self)
|
|
|
|
def _volume_driver_method(self, method_name, connection_info,
|
|
*args, **kwargs):
|
|
driver_type = connection_info.get('driver_volume_type')
|
|
if driver_type not in self.volume_drivers:
|
|
raise exception.VolumeDriverNotFound(driver_type=driver_type)
|
|
driver = self.volume_drivers[driver_type]
|
|
method = getattr(driver, method_name)
|
|
return method(connection_info, *args, **kwargs)
|
|
|
|
def attach_volume(self, connection_info, instance, mountpoint):
|
|
node = _get_baremetal_node_by_instance_uuid(instance['uuid'])
|
|
ctx = nova_context.get_admin_context()
|
|
pxe_ip = bmdb.bm_pxe_ip_get_by_bm_node_id(ctx, node['id'])
|
|
if not pxe_ip:
|
|
if not CONF.baremetal.use_unsafe_iscsi:
|
|
raise exception.NovaException(_(
|
|
'No fixed PXE IP is associated to %s') % instance['uuid'])
|
|
|
|
mount_device = mountpoint.rpartition("/")[2]
|
|
self._volume_driver_method('connect_volume',
|
|
connection_info,
|
|
mount_device)
|
|
device_path = connection_info['data']['device_path']
|
|
iqn = _get_iqn(instance['name'], mountpoint)
|
|
tid = _get_next_tid()
|
|
_create_iscsi_export_tgtadm(device_path, tid, iqn)
|
|
|
|
if pxe_ip:
|
|
_allow_iscsi_tgtadm(tid, pxe_ip['address'])
|
|
else:
|
|
# NOTE(NTTdocomo): Since nova-compute does not know the
|
|
# instance's initiator ip, it allows any initiators
|
|
# to connect to the volume. This means other bare-metal
|
|
# instances that are not attached the volume can connect
|
|
# to the volume. Do not set CONF.baremetal.use_unsafe_iscsi
|
|
# out of dev/test environments.
|
|
# TODO(NTTdocomo): support CHAP
|
|
_allow_iscsi_tgtadm(tid, 'ALL')
|
|
|
|
def detach_volume(self, connection_info, instance, mountpoint):
|
|
mount_device = mountpoint.rpartition("/")[2]
|
|
try:
|
|
iqn = _get_iqn(instance['name'], mountpoint)
|
|
tid = _find_tid(iqn)
|
|
if tid is not None:
|
|
_delete_iscsi_export_tgtadm(tid)
|
|
else:
|
|
LOG.warn(_('detach volume could not find tid for %s') % iqn)
|
|
finally:
|
|
self._volume_driver_method('disconnect_volume',
|
|
connection_info,
|
|
mount_device)
|
|
|
|
def get_all_block_devices(self):
|
|
"""
|
|
Return all block devices in use on this node.
|
|
"""
|
|
return _list_backingstore_path()
|