1
# vim: tabstop=4 shiftwidth=4 softtabstop=4
3
# Copyright 2011 OpenStack LLC.
6
# Licensed under the Apache License, Version 2.0 (the "License"); you may
7
# not use this file except in compliance with the License. You may obtain
8
# a copy of the License at
10
# http://www.apache.org/licenses/LICENSE-2.0
12
# Unless required by applicable law or agreed to in writing, software
13
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
15
# License for the specific language governing permissions and limitations
18
"""Volume drivers for libvirt."""
23
from nova import exception
24
from nova import flags
25
from nova.openstack.common import log as logging
26
from nova import utils
27
from nova.virt.libvirt import config
28
from nova.virt.libvirt import utils as virtutils
30
LOG = logging.getLogger(__name__)
32
flags.DECLARE('num_iscsi_scan_tries', 'nova.volume.driver')
35
class LibvirtVolumeDriver(object):
36
"""Base class for volume drivers."""
37
def __init__(self, connection):
38
self.connection = connection
40
def connect_volume(self, connection_info, mount_device):
41
"""Connect the volume. Returns xml for libvirt."""
42
conf = config.LibvirtConfigGuestDisk()
43
conf.source_type = "block"
44
conf.driver_name = virtutils.pick_disk_driver_name(is_block_dev=True)
45
conf.driver_format = "raw"
46
conf.driver_cache = "none"
47
conf.source_path = connection_info['data']['device_path']
48
conf.target_dev = mount_device
49
conf.target_bus = "virtio"
50
conf.serial = connection_info.get('serial')
53
def disconnect_volume(self, connection_info, mount_device):
54
"""Disconnect the volume"""
58
class LibvirtFakeVolumeDriver(LibvirtVolumeDriver):
59
"""Driver to attach Network volumes to libvirt."""
61
def connect_volume(self, connection_info, mount_device):
62
conf = config.LibvirtConfigGuestDisk()
63
conf.source_type = "network"
64
conf.driver_name = "qemu"
65
conf.driver_format = "raw"
66
conf.driver_cache = "none"
67
conf.source_protocol = "fake"
68
conf.source_host = "fake"
69
conf.target_dev = mount_device
70
conf.target_bus = "virtio"
71
conf.serial = connection_info.get('serial')
75
class LibvirtNetVolumeDriver(LibvirtVolumeDriver):
76
"""Driver to attach Network volumes to libvirt."""
78
def connect_volume(self, connection_info, mount_device):
79
conf = config.LibvirtConfigGuestDisk()
80
conf.source_type = "network"
81
conf.driver_name = virtutils.pick_disk_driver_name(is_block_dev=False)
82
conf.driver_format = "raw"
83
conf.driver_cache = "none"
84
conf.source_protocol = connection_info['driver_volume_type']
85
conf.source_host = connection_info['data']['name']
86
conf.target_dev = mount_device
87
conf.target_bus = "virtio"
88
conf.serial = connection_info.get('serial')
89
netdisk_properties = connection_info['data']
90
if netdisk_properties.get('auth_enabled'):
91
conf.auth_username = netdisk_properties['auth_username']
92
conf.auth_secret_type = netdisk_properties['secret_type']
93
conf.auth_secret_uuid = netdisk_properties['secret_uuid']
97
class LibvirtISCSIVolumeDriver(LibvirtVolumeDriver):
98
"""Driver to attach Network volumes to libvirt."""
100
def _run_iscsiadm(self, iscsi_properties, iscsi_command, **kwargs):
101
check_exit_code = kwargs.pop('check_exit_code', 0)
102
(out, err) = utils.execute('iscsiadm', '-m', 'node', '-T',
103
iscsi_properties['target_iqn'],
104
'-p', iscsi_properties['target_portal'],
105
*iscsi_command, run_as_root=True,
106
check_exit_code=check_exit_code)
107
LOG.debug("iscsiadm %s: stdout=%s stderr=%s" %
108
(iscsi_command, out, err))
111
def _iscsiadm_update(self, iscsi_properties, property_key, property_value,
113
iscsi_command = ('--op', 'update', '-n', property_key,
114
'-v', property_value)
115
return self._run_iscsiadm(iscsi_properties, iscsi_command, **kwargs)
117
@utils.synchronized('connect_volume')
118
def connect_volume(self, connection_info, mount_device):
119
"""Attach the volume to instance_name"""
120
iscsi_properties = connection_info['data']
121
# NOTE(vish): If we are on the same host as nova volume, the
122
# discovery makes the target so we don't need to
123
# run --op new. Therefore, we check to see if the
124
# target exists, and if we get 255 (Not Found), then
125
# we run --op new. This will also happen if another
126
# volume is using the same target.
128
self._run_iscsiadm(iscsi_properties, ())
129
except exception.ProcessExecutionError as exc:
130
# iscsiadm returns 21 for "No records found" after version 2.0-871
131
if exc.exit_code in [21, 255]:
132
self._run_iscsiadm(iscsi_properties, ('--op', 'new'))
136
if iscsi_properties.get('auth_method'):
137
self._iscsiadm_update(iscsi_properties,
138
"node.session.auth.authmethod",
139
iscsi_properties['auth_method'])
140
self._iscsiadm_update(iscsi_properties,
141
"node.session.auth.username",
142
iscsi_properties['auth_username'])
143
self._iscsiadm_update(iscsi_properties,
144
"node.session.auth.password",
145
iscsi_properties['auth_password'])
147
# NOTE(vish): If we have another lun on the same target, we may
148
# have a duplicate login
149
self._run_iscsiadm(iscsi_properties, ("--login",),
150
check_exit_code=[0, 255])
152
self._iscsiadm_update(iscsi_properties, "node.startup", "automatic")
154
host_device = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" %
155
(iscsi_properties['target_portal'],
156
iscsi_properties['target_iqn'],
157
iscsi_properties.get('target_lun', 0)))
159
# The /dev/disk/by-path/... node is not always present immediately
160
# TODO(justinsb): This retry-with-delay is a pattern, move to utils?
162
while not os.path.exists(host_device):
163
if tries >= FLAGS.num_iscsi_scan_tries:
164
raise exception.NovaException(_("iSCSI device not found at %s")
167
LOG.warn(_("ISCSI volume not yet found at: %(mount_device)s. "
168
"Will rescan & retry. Try number: %(tries)s") %
171
# The rescan isn't documented as being necessary(?), but it helps
172
self._run_iscsiadm(iscsi_properties, ("--rescan",))
175
if not os.path.exists(host_device):
176
time.sleep(tries ** 2)
179
LOG.debug(_("Found iSCSI node %(mount_device)s "
180
"(after %(tries)s rescans)") %
183
connection_info['data']['device_path'] = host_device
184
sup = super(LibvirtISCSIVolumeDriver, self)
185
return sup.connect_volume(connection_info, mount_device)
187
@utils.synchronized('connect_volume')
188
def disconnect_volume(self, connection_info, mount_device):
189
"""Detach the volume from instance_name"""
190
sup = super(LibvirtISCSIVolumeDriver, self)
191
sup.disconnect_volume(connection_info, mount_device)
192
iscsi_properties = connection_info['data']
193
# NOTE(vish): Only disconnect from the target if no luns from the
195
device_prefix = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-" %
196
(iscsi_properties['target_portal'],
197
iscsi_properties['target_iqn']))
198
devices = self.connection.get_all_block_devices()
199
devices = [dev for dev in devices if dev.startswith(device_prefix)]
201
self._iscsiadm_update(iscsi_properties, "node.startup", "manual",
202
check_exit_code=[0, 255])
203
self._run_iscsiadm(iscsi_properties, ("--logout",),
204
check_exit_code=[0, 255])
205
self._run_iscsiadm(iscsi_properties, ('--op', 'delete'),
206
check_exit_code=[0, 21, 255])