1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
4
# Copyright (c) 2012 NTT DOCOMO, INC.
7
# Licensed under the Apache License, Version 2.0 (the "License"); you may
8
# not use this file except in compliance with the License. You may obtain
9
# a copy of the License at
11
# http://www.apache.org/licenses/LICENSE-2.0
13
# Unless required by applicable law or agreed to in writing, software
14
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
15
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
16
# License for the specific language governing permissions and limitations
21
from nova import context as nova_context
22
from nova import exception
23
from nova.openstack.common import cfg
24
from nova.openstack.common import importutils
25
from nova.openstack.common import log as logging
26
from nova import utils
27
from nova.virt.baremetal import db as bmdb
28
from nova.virt.libvirt import utils as libvirt_utils
31
cfg.BoolOpt('use_unsafe_iscsi',
33
help='Do not set this out of dev/test environments. '
34
'If a node does not have an fixed PXE IP address, '
35
'volumes are exported with globally opened ACL'),
36
cfg.StrOpt('iscsi_iqn_prefix',
37
default='iqn.2010-10.org.openstack.baremetal',
38
help='iSCSI IQN prefix used in baremetal volume connections.'),
41
baremetal_group = cfg.OptGroup(name='baremetal',
42
title='Baremetal Options')
45
CONF.register_group(baremetal_group)
46
CONF.register_opts(opts, baremetal_group)
48
CONF.import_opt('libvirt_volume_drivers', 'nova.virt.libvirt.driver')
50
LOG = logging.getLogger(__name__)
53
def _get_baremetal_node_by_instance_uuid(instance_uuid):
54
context = nova_context.get_admin_context()
55
return bmdb.bm_node_get_by_instance_uuid(context, instance_uuid)
58
def _create_iscsi_export_tgtadm(path, tid, iqn):
59
utils.execute('tgtadm', '--lld', 'iscsi',
65
utils.execute('tgtadm', '--lld', 'iscsi',
66
'--mode', 'logicalunit',
70
'--backing-store', path,
74
def _allow_iscsi_tgtadm(tid, address):
75
utils.execute('tgtadm', '--lld', 'iscsi',
79
'--initiator-address', address,
83
def _delete_iscsi_export_tgtadm(tid):
85
utils.execute('tgtadm', '--lld', 'iscsi',
86
'--mode', 'logicalunit',
91
except exception.ProcessExecutionError:
94
utils.execute('tgtadm', '--lld', 'iscsi',
99
except exception.ProcessExecutionError:
101
# Check if the tid is deleted, that is, check the tid no longer exists.
102
# If the tid dose not exist, tgtadm returns with exit_code 22.
103
# utils.execute() can check the exit_code if check_exit_code parameter is
104
# passed. But, regardless of whether check_exit_code contains 0 or not,
105
# if the exit_code is 0, the function dose not report errors. So we have to
106
# catch a ProcessExecutionError and test its exit_code is 22.
108
utils.execute('tgtadm', '--lld', 'iscsi',
113
except exception.ProcessExecutionError as e:
114
if e.exit_code == 22:
115
# OK, the tid is deleted
118
raise exception.NovaException(_(
119
'baremetal driver was unable to delete tid %s') % tid)
123
out, _ = utils.execute('tgtadm', '--lld', 'iscsi',
130
def _list_backingstore_path():
133
for line in out.split('\n'):
134
m = re.search(r'Backing store path: (.*)$', line)
136
if '/' in m.group(1):
144
for line in out.split('\n'):
145
m = re.search(r'^Target (\d+):', line)
147
tid = int(m.group(1))
155
pattern = r'^Target (\d+): *' + re.escape(iqn)
156
for line in out.split('\n'):
157
m = re.search(pattern, line)
159
return int(m.group(1))
163
def _get_iqn(instance_name, mountpoint):
164
mp = mountpoint.replace('/', '-').strip('-')
165
iqn = '%s:%s-%s' % (CONF.baremetal.iscsi_iqn_prefix,
171
class VolumeDriver(object):
173
def __init__(self, virtapi):
174
super(VolumeDriver, self).__init__()
175
self.virtapi = virtapi
176
self._initiator = None
178
def get_volume_connector(self, instance):
179
if not self._initiator:
180
self._initiator = libvirt_utils.get_iscsi_initiator()
181
if not self._initiator:
182
LOG.warn(_('Could not determine iscsi initiator name '
183
'for instance %s') % instance)
186
'initiator': self._initiator,
190
def attach_volume(self, connection_info, instance, mountpoint):
191
raise NotImplementedError()
193
def detach_volume(self, connection_info, instance, mountpoint):
194
raise NotImplementedError()
197
class LibvirtVolumeDriver(VolumeDriver):
198
"""The VolumeDriver deligates to nova.virt.libvirt.volume."""
200
def __init__(self, virtapi):
201
super(LibvirtVolumeDriver, self).__init__(virtapi)
202
self.volume_drivers = {}
203
for driver_str in CONF.libvirt_volume_drivers:
204
driver_type, _sep, driver = driver_str.partition('=')
205
driver_class = importutils.import_class(driver)
206
self.volume_drivers[driver_type] = driver_class(self)
208
def _volume_driver_method(self, method_name, connection_info,
210
driver_type = connection_info.get('driver_volume_type')
211
if not driver_type in self.volume_drivers:
212
raise exception.VolumeDriverNotFound(driver_type=driver_type)
213
driver = self.volume_drivers[driver_type]
214
method = getattr(driver, method_name)
215
return method(connection_info, *args, **kwargs)
217
def attach_volume(self, connection_info, instance, mountpoint):
218
node = _get_baremetal_node_by_instance_uuid(instance['uuid'])
219
ctx = nova_context.get_admin_context()
220
pxe_ip = bmdb.bm_pxe_ip_get_by_bm_node_id(ctx, node['id'])
222
if not CONF.baremetal.use_unsafe_iscsi:
223
raise exception.NovaException(_(
224
'No fixed PXE IP is associated to %s') % instance['uuid'])
226
mount_device = mountpoint.rpartition("/")[2]
227
self._volume_driver_method('connect_volume',
230
device_path = connection_info['data']['device_path']
231
iqn = _get_iqn(instance['name'], mountpoint)
232
tid = _get_next_tid()
233
_create_iscsi_export_tgtadm(device_path, tid, iqn)
236
_allow_iscsi_tgtadm(tid, pxe_ip['address'])
238
# NOTE(NTTdocomo): Since nova-compute does not know the
239
# instance's initiator ip, it allows any initiators
240
# to connect to the volume. This means other bare-metal
241
# instances that are not attached the volume can connect
242
# to the volume. Do not set CONF.baremetal.use_unsafe_iscsi
243
# out of dev/test environments.
244
# TODO(NTTdocomo): support CHAP
245
_allow_iscsi_tgtadm(tid, 'ALL')
247
@exception.wrap_exception()
248
def detach_volume(self, connection_info, instance, mountpoint):
249
mount_device = mountpoint.rpartition("/")[2]
251
iqn = _get_iqn(instance['name'], mountpoint)
254
_delete_iscsi_export_tgtadm(tid)
256
LOG.warn(_('detach volume could not find tid for %s') % iqn)
258
self._volume_driver_method('disconnect_volume',
262
def get_all_block_devices(self):
264
Return all block devices in use on this node.
266
return _list_backingstore_path()