nova/nova/virt/libvirt/volume.py
Sean Dague bf31e02d13 Fix N402 for nova/virt
Fix N402 errors (single line docstring should end in a period)
for nova/virt, part of a larger attempt to stop ignoring our own
hacking.py tests.

Change-Id: I523ce41bd2b38c73cf3fdb031101ccc0695f2488
2013-01-08 23:45:07 -05:00

231 lines
9.7 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# 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.
"""Volume drivers for libvirt."""
import os
import time
from nova import exception
from nova.openstack.common import cfg
from nova.openstack.common import lockutils
from nova.openstack.common import log as logging
from nova import utils
from nova.virt.libvirt import config as vconfig
from nova.virt.libvirt import utils as virtutils
LOG = logging.getLogger(__name__)
volume_opts = [
cfg.IntOpt('num_iscsi_scan_tries',
default=3,
help='number of times to rescan iSCSI target to find volume'),
cfg.StrOpt('rbd_user',
default=None,
help='the RADOS client name for accessing rbd volumes'),
cfg.StrOpt('rbd_secret_uuid',
default=None,
help='the libvirt uuid of the secret for the rbd_user'
'volumes')
]
CONF = cfg.CONF
CONF.register_opts(volume_opts)
class LibvirtVolumeDriver(object):
"""Base class for volume drivers."""
def __init__(self, connection):
self.connection = connection
def connect_volume(self, connection_info, mount_device):
"""Connect the volume. Returns xml for libvirt."""
conf = vconfig.LibvirtConfigGuestDisk()
conf.source_type = "block"
conf.driver_name = virtutils.pick_disk_driver_name(is_block_dev=True)
conf.driver_format = "raw"
conf.driver_cache = "none"
conf.source_path = connection_info['data']['device_path']
conf.target_dev = mount_device
conf.target_bus = "virtio"
conf.serial = connection_info.get('serial')
return conf
def disconnect_volume(self, connection_info, mount_device):
"""Disconnect the volume."""
pass
class LibvirtFakeVolumeDriver(LibvirtVolumeDriver):
"""Driver to attach Network volumes to libvirt."""
def connect_volume(self, connection_info, mount_device):
conf = vconfig.LibvirtConfigGuestDisk()
conf.source_type = "network"
conf.driver_name = "qemu"
conf.driver_format = "raw"
conf.driver_cache = "none"
conf.source_protocol = "fake"
conf.source_host = "fake"
conf.target_dev = mount_device
conf.target_bus = "virtio"
conf.serial = connection_info.get('serial')
return conf
class LibvirtNetVolumeDriver(LibvirtVolumeDriver):
"""Driver to attach Network volumes to libvirt."""
def connect_volume(self, connection_info, mount_device):
conf = vconfig.LibvirtConfigGuestDisk()
conf.source_type = "network"
conf.driver_name = virtutils.pick_disk_driver_name(is_block_dev=False)
conf.driver_format = "raw"
conf.driver_cache = "none"
conf.source_protocol = connection_info['driver_volume_type']
conf.source_host = connection_info['data']['name']
conf.target_dev = mount_device
conf.target_bus = "virtio"
conf.serial = connection_info.get('serial')
netdisk_properties = connection_info['data']
auth_enabled = netdisk_properties.get('auth_enabled')
if (conf.source_protocol == 'rbd' and
CONF.rbd_secret_uuid):
conf.auth_secret_uuid = CONF.rbd_secret_uuid
auth_enabled = True # Force authentication locally
if CONF.rbd_user:
conf.auth_username = CONF.rbd_user
if auth_enabled:
conf.auth_username = (conf.auth_username or
netdisk_properties['auth_username'])
conf.auth_secret_type = netdisk_properties['secret_type']
conf.auth_secret_uuid = (conf.auth_secret_uuid or
netdisk_properties['secret_uuid'])
return conf
class LibvirtISCSIVolumeDriver(LibvirtVolumeDriver):
"""Driver to attach Network volumes to libvirt."""
def _run_iscsiadm(self, iscsi_properties, iscsi_command, **kwargs):
check_exit_code = kwargs.pop('check_exit_code', 0)
(out, err) = utils.execute('iscsiadm', '-m', 'node', '-T',
iscsi_properties['target_iqn'],
'-p', iscsi_properties['target_portal'],
*iscsi_command, run_as_root=True,
check_exit_code=check_exit_code)
LOG.debug("iscsiadm %s: stdout=%s stderr=%s" %
(iscsi_command, out, err))
return (out, err)
def _iscsiadm_update(self, iscsi_properties, property_key, property_value,
**kwargs):
iscsi_command = ('--op', 'update', '-n', property_key,
'-v', property_value)
return self._run_iscsiadm(iscsi_properties, iscsi_command, **kwargs)
@lockutils.synchronized('connect_volume', 'nova-')
def connect_volume(self, connection_info, mount_device):
"""Attach the volume to instance_name."""
iscsi_properties = connection_info['data']
# NOTE(vish): If we are on the same host as nova volume, the
# discovery makes the target so we don't need to
# run --op new. Therefore, we check to see if the
# target exists, and if we get 255 (Not Found), then
# we run --op new. This will also happen if another
# volume is using the same target.
try:
self._run_iscsiadm(iscsi_properties, ())
except exception.ProcessExecutionError as exc:
# iscsiadm returns 21 for "No records found" after version 2.0-871
if exc.exit_code in [21, 255]:
self._run_iscsiadm(iscsi_properties, ('--op', 'new'))
else:
raise
if iscsi_properties.get('auth_method'):
self._iscsiadm_update(iscsi_properties,
"node.session.auth.authmethod",
iscsi_properties['auth_method'])
self._iscsiadm_update(iscsi_properties,
"node.session.auth.username",
iscsi_properties['auth_username'])
self._iscsiadm_update(iscsi_properties,
"node.session.auth.password",
iscsi_properties['auth_password'])
# NOTE(vish): If we have another lun on the same target, we may
# have a duplicate login
self._run_iscsiadm(iscsi_properties, ("--login",),
check_exit_code=[0, 255])
self._iscsiadm_update(iscsi_properties, "node.startup", "automatic")
host_device = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-%s" %
(iscsi_properties['target_portal'],
iscsi_properties['target_iqn'],
iscsi_properties.get('target_lun', 0)))
# The /dev/disk/by-path/... node is not always present immediately
# TODO(justinsb): This retry-with-delay is a pattern, move to utils?
tries = 0
while not os.path.exists(host_device):
if tries >= CONF.num_iscsi_scan_tries:
raise exception.NovaException(_("iSCSI device not found at %s")
% (host_device))
LOG.warn(_("ISCSI volume not yet found at: %(mount_device)s. "
"Will rescan & retry. Try number: %(tries)s") %
locals())
# The rescan isn't documented as being necessary(?), but it helps
self._run_iscsiadm(iscsi_properties, ("--rescan",))
tries = tries + 1
if not os.path.exists(host_device):
time.sleep(tries ** 2)
if tries != 0:
LOG.debug(_("Found iSCSI node %(mount_device)s "
"(after %(tries)s rescans)") %
locals())
connection_info['data']['device_path'] = host_device
sup = super(LibvirtISCSIVolumeDriver, self)
return sup.connect_volume(connection_info, mount_device)
@lockutils.synchronized('connect_volume', 'nova-')
def disconnect_volume(self, connection_info, mount_device):
"""Detach the volume from instance_name."""
sup = super(LibvirtISCSIVolumeDriver, self)
sup.disconnect_volume(connection_info, mount_device)
iscsi_properties = connection_info['data']
# NOTE(vish): Only disconnect from the target if no luns from the
# target are in use.
device_prefix = ("/dev/disk/by-path/ip-%s-iscsi-%s-lun-" %
(iscsi_properties['target_portal'],
iscsi_properties['target_iqn']))
devices = self.connection.get_all_block_devices()
devices = [dev for dev in devices if dev.startswith(device_prefix)]
if not devices:
self._iscsiadm_update(iscsi_properties, "node.startup", "manual",
check_exit_code=[0, 21, 255])
self._run_iscsiadm(iscsi_properties, ("--logout",),
check_exit_code=[0, 21, 255])
self._run_iscsiadm(iscsi_properties, ('--op', 'delete'),
check_exit_code=[0, 21, 255])