Add initiator to initialize_connection

Some volumes need to know the name of the initiator that will be
connecting to the iscsi volume.  This adds a call down to the hypervisor
driver to get the ip and the initiator name for the vm before calling
initialize connection. This connection is passed down to the volume
driver so that it can be used to authenticate when the hypervisor
tries to connect to the volume.

 * Adds initiator initialize_connection
 * Makes a call to driver to get initiator name and ip address
 * Gets initiator from openiscsi for libvirt
 * Gets initiator from config for xenapi
 * Add tests for the driver calls
 * Fixes bug 924461

Change-Id: I5b6a2dd84560c7f7b447571e0abf0993e5512ca0
This commit is contained in:
Vishvananda Ishaya
2012-01-28 01:17:00 -08:00
parent 65e233133e
commit 94d8553201
15 changed files with 176 additions and 49 deletions

View File

@@ -652,9 +652,10 @@ class ComputeManager(manager.SchedulerDependentManager):
# NOTE(vish): actual driver detach done in driver.destroy, so
# just tell nova-volume that we are done with it.
volume = self.volume_api.get(context, bdm['volume_id'])
connector = self.driver.get_volume_connector(instance)
self.volume_api.terminate_connection(context,
volume,
FLAGS.my_ip)
connector)
self.volume_api.detach(context, volume)
except exception.DiskNotFound as exc:
LOG.warn(_("Ignoring DiskNotFound: %s") % exc)
@@ -1597,10 +1598,10 @@ class ComputeManager(manager.SchedulerDependentManager):
msg = _("instance %(instance_uuid)s: booting with "
"volume %(volume_id)s at %(mountpoint)s")
LOG.audit(msg % locals(), context=context)
address = FLAGS.my_ip
connector = self.driver.get_volume_connector(instance)
connection_info = self.volume_api.initialize_connection(context,
volume,
address)
connector)
self.volume_api.attach(context, volume, instance_id, mountpoint)
return connection_info
@@ -1616,10 +1617,10 @@ class ComputeManager(manager.SchedulerDependentManager):
msg = _("instance %(instance_uuid)s: attaching volume %(volume_id)s"
" to %(mountpoint)s")
LOG.audit(msg % locals(), context=context)
address = FLAGS.my_ip
connector = self.driver.get_volume_connector(instance_ref)
connection_info = self.volume_api.initialize_connection(context,
volume,
address)
connector)
try:
self.driver.attach_volume(connection_info,
instance_ref['name'],
@@ -1631,7 +1632,7 @@ class ComputeManager(manager.SchedulerDependentManager):
LOG.exception(msg % locals(), context=context)
self.volume_api.terminate_connection(context,
volume,
address)
connector)
self.volume_api.attach(context, volume, instance_id, mountpoint)
values = {
@@ -1674,7 +1675,8 @@ class ComputeManager(manager.SchedulerDependentManager):
bdm = self._get_instance_volume_bdm(context, instance_id, volume_id)
self._detach_volume(context, instance_ref, bdm)
volume = self.volume_api.get(context, volume_id)
self.volume_api.terminate_connection(context, volume, FLAGS.my_ip)
connector = self.driver.get_volume_connector(instance_ref)
self.volume_api.terminate_connection(context, volume, connector)
self.volume_api.detach(context.elevated(), volume)
self.db.block_device_mapping_destroy_by_instance_and_volume(
context, instance_id, volume_id)
@@ -1693,7 +1695,8 @@ class ComputeManager(manager.SchedulerDependentManager):
volume_id)
self._detach_volume(context, instance_ref, bdm)
volume = self.volume_api.get(context, volume_id)
self.volume_api.terminate_connection(context, volume, FLAGS.my_ip)
connector = self.driver.get_volume_connector(instance_ref)
self.volume_api.terminate_connection(context, volume, connector)
except exception.NotFound:
pass

View File

@@ -168,4 +168,7 @@ filterlist = [
# nova/virt/libvirt/utils.py: 'mkswap'
# nova/virt/xenapi/vm_utils.py: 'mkswap'
filters.CommandFilter("/sbin/mkswap", "root"),
# nova/virt/libvirt/connection.py:
filters.ReadFileFilter("/etc/iscsi/initiatorname.iscsi"),
]

View File

@@ -21,6 +21,10 @@ disk_sizes = {}
disk_backing_files = {}
def get_iscsi_initiator():
return "fake.initiator.iqn"
def create_image(disk_format, path, size):
pass

View File

@@ -123,6 +123,10 @@ class LibvirtVolumeTestCase(test.TestCase):
def get_hypervisor_type(self):
return self.hyperv
self.fake_conn = FakeLibvirtConnection("Xen")
self.connr = {
'ip': '127.0.0.1',
'initiator': 'fake_initiator'
}
def test_libvirt_iscsi_driver(self):
# NOTE(vish) exists is to make driver assume connecting worked
@@ -136,8 +140,7 @@ class LibvirtVolumeTestCase(test.TestCase):
'name': name,
'provider_auth': None,
'provider_location': '%s,fake %s' % (location, iqn)}
address = '127.0.0.1'
connection_info = vol_driver.initialize_connection(vol, '127.0.0.1')
connection_info = vol_driver.initialize_connection(vol, self.connr)
mount_device = "vde"
xml = libvirt_driver.connect_volume(connection_info, mount_device)
tree = xml_to_tree(xml)
@@ -145,7 +148,7 @@ class LibvirtVolumeTestCase(test.TestCase):
self.assertEqual(tree.get('type'), 'block')
self.assertEqual(tree.find('./source').get('dev'), dev_str)
libvirt_driver.disconnect_volume(connection_info, mount_device)
connection_info = vol_driver.terminate_connection(vol, '127.0.0.1')
connection_info = vol_driver.terminate_connection(vol, self.connr)
expected_commands = [('iscsiadm', '-m', 'node', '-T', iqn,
'-p', location),
('iscsiadm', '-m', 'node', '-T', iqn,
@@ -167,8 +170,7 @@ class LibvirtVolumeTestCase(test.TestCase):
libvirt_driver = volume.LibvirtNetVolumeDriver(self.fake_conn)
name = 'volume-00000001'
vol = {'id': 1, 'name': name}
address = '127.0.0.1'
connection_info = vol_driver.initialize_connection(vol, address)
connection_info = vol_driver.initialize_connection(vol, self.connr)
mount_device = "vde"
xml = libvirt_driver.connect_volume(connection_info, mount_device)
tree = xml_to_tree(xml)
@@ -176,15 +178,14 @@ class LibvirtVolumeTestCase(test.TestCase):
self.assertEqual(tree.find('./source').get('protocol'), 'sheepdog')
self.assertEqual(tree.find('./source').get('name'), name)
libvirt_driver.disconnect_volume(connection_info, mount_device)
connection_info = vol_driver.terminate_connection(vol, '127.0.0.1')
connection_info = vol_driver.terminate_connection(vol, self.connr)
def test_libvirt_rbd_driver(self):
vol_driver = volume_driver.RBDDriver()
libvirt_driver = volume.LibvirtNetVolumeDriver(self.fake_conn)
name = 'volume-00000001'
vol = {'id': 1, 'name': name}
address = '127.0.0.1'
connection_info = vol_driver.initialize_connection(vol, address)
connection_info = vol_driver.initialize_connection(vol, self.connr)
mount_device = "vde"
xml = libvirt_driver.connect_volume(connection_info, mount_device)
tree = xml_to_tree(xml)
@@ -193,7 +194,7 @@ class LibvirtVolumeTestCase(test.TestCase):
rbd_name = '%s/%s' % (FLAGS.rbd_pool, name)
self.assertEqual(tree.find('./source').get('name'), rbd_name)
libvirt_driver.disconnect_volume(connection_info, mount_device)
connection_info = vol_driver.terminate_connection(vol, '127.0.0.1')
connection_info = vol_driver.terminate_connection(vol, self.connr)
class CacheConcurrencyTestCase(test.TestCase):
@@ -355,6 +356,22 @@ class LibvirtConnTestCase(test.TestCase):
return db.service_create(context.get_admin_context(), service_ref)
def test_get_connector(self):
initiator = 'fake.initiator.iqn'
ip = 'fakeip'
self.flags(my_ip=ip)
conn = connection.LibvirtConnection(True)
expected = {
'ip': ip,
'initiator': initiator
}
volume = {
'id': 'fake'
}
result = conn.get_volume_connector(volume)
self.assertDictMatch(expected, result)
def test_preparing_xml_info(self):
conn = connection.LibvirtConnection(True)
instance_ref = db.instance_create(self.context, self.test_instance)
@@ -1764,6 +1781,17 @@ class NWFilterTestCase(test.TestCase):
class LibvirtUtilsTestCase(test.TestCase):
def test_get_iscsi_initiator(self):
self.mox.StubOutWithMock(utils, 'execute')
initiator = 'fake.initiator.iqn'
rval = ("junk\nInitiatorName=%s\njunk\n" % initiator, None)
utils.execute('cat', '/etc/iscsi/initiatorname.iscsi',
run_as_root=True).AndReturn(rval)
# Start test
self.mox.ReplayAll()
result = libvirt_utils.get_iscsi_initiator()
self.assertEqual(initiator, result)
def test_create_image(self):
self.mox.StubOutWithMock(utils, 'execute')
utils.execute('qemu-img', 'create', '-f', 'raw',

View File

@@ -224,6 +224,12 @@ class _VirtDriverTestCase(test.TestCase):
self.assertNotIn(instance_ref['name'],
self.connection.list_instances())
@catch_notimplementederror
def test_get_volume_connector(self):
result = self.connection.get_volume_connector({'id': 'fake'})
self.assertTrue('ip' in result)
self.assertTrue('initiator' in result)
@catch_notimplementederror
def test_attach_detach_volume(self):
instance_ref, network_info = self._get_running_instance()

View File

@@ -623,3 +623,17 @@ class ComputeDriver(object):
the cache and remove images which are no longer of interest.
"""
raise NotImplementedError()
def get_volume_connector(self, instance):
"""
Get connector information for the instance for attaching to volumes.
Connector information is a dictionary representing the ip of the
machine that will be making the connection and and the name of the
iscsi initiator as follows:
{
'ip': ip,
'initiator': initiator,
}
"""
raise NotImplementedError()

View File

@@ -301,3 +301,6 @@ class FakeConnection(driver.ComputeDriver):
def get_disk_available_least(self):
""" """
pass
def get_volume_connector(self, instance):
return {'ip': '127.0.0.1', 'initiator': 'fake'}

View File

@@ -187,6 +187,7 @@ class LibvirtConnection(driver.ComputeDriver):
super(LibvirtConnection, self).__init__()
self._host_state = None
self._initiator = None
self._wrapped_conn = None
self.container = None
self.read_only = read_only
@@ -423,6 +424,16 @@ class LibvirtConnection(driver.ComputeDriver):
if os.path.exists(target):
shutil.rmtree(target)
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'))
return {
'ip': FLAGS.my_ip,
'initiator': self._initiator,
}
def volume_driver_method(self, method_name, connection_info,
*args, **kwargs):
driver_type = connection_info.get('driver_volume_type')
@@ -723,7 +734,7 @@ class LibvirtConnection(driver.ComputeDriver):
if virsh_output.startswith('/dev/'):
LOG.info(_("cool, it's a device"))
out, err = utils.execute('dd',
"if=%s" % virsh_output,
'if=%s' % virsh_output,
'iflag=nonblock',
run_as_root=True,
check_exit_code=False)
@@ -751,7 +762,8 @@ class LibvirtConnection(driver.ComputeDriver):
if FLAGS.libvirt_type == 'xen':
# Xen is special
virsh_output = utils.execute('virsh', 'ttyconsole',
virsh_output = utils.execute('virsh',
'ttyconsole',
instance['name'])
data = self._flush_xen_console(virsh_output)
fpath = self._append_to_file(data, console_log)

View File

@@ -44,6 +44,16 @@ def execute(*args, **kwargs):
return utils.execute(*args, **kwargs)
def get_iscsi_initiator():
"""Get iscsi initiator name for this machine"""
# NOTE(vish) openiscsi stores initiator name in a file that
# needs root permission to read.
contents = utils.read_file_as_root('/etc/iscsi/initiatorname.iscsi')
for l in contents.split('\n'):
if l.startswith('InitiatorName='):
return l[l.index('=') + 1:].strip()
def create_image(disk_format, path, size):
"""Create a disk image

View File

@@ -178,6 +178,15 @@ class VMWareESXConnection(driver.ComputeDriver):
"""Return link to instance's ajax console."""
return self._vmops.get_ajax_console(instance)
def get_volume_connector(self, _instance):
"""Return volume connector information"""
# TODO(vish): When volume attaching is supported, return the
# proper initiator iqn.
return {
'ip': FLAGS.vmwareapi_host_ip,
'initiator': None
}
def attach_volume(self, connection_info, instance_name, mountpoint):
"""Attach volume storage to VM instance."""
pass

View File

@@ -179,6 +179,7 @@ class XenAPIConnection(driver.ComputeDriver):
self._host_state = None
self._product_version = self._session.get_product_version()
self._vmops = VMOps(self._session, self._product_version)
self._initiator = None
@property
def host_state(self):
@@ -348,6 +349,20 @@ class XenAPIConnection(driver.ComputeDriver):
"""Return link to instance's ajax console"""
return self._vmops.get_vnc_console(instance)
def get_volume_connector(self, _instance):
"""Return volume connector information"""
if not self._initiator:
stats = self.get_host_stats(update=True)
try:
self._initiator = stats['host_other-config']['iscsi_iqn']
except (TypeError, KeyError):
LOG.warn(_('Could not determine iscsi initiator name'))
self._initiator = None
return {
'ip': self.get_host_ip_addr(),
'initiator': self._initiator
}
@staticmethod
def get_host_ip_addr():
xs_url = urlparse.urlparse(FLAGS.xenapi_connection_url)

View File

@@ -244,22 +244,22 @@ class API(base.Base):
"args": {"volume_id": volume['id']}})
@wrap_check_policy
def initialize_connection(self, context, volume, address):
def initialize_connection(self, context, volume, connector):
host = volume['host']
queue = self.db.queue_get_for(context, FLAGS.volume_topic, host)
return rpc.call(context, queue,
{"method": "initialize_connection",
"args": {"volume_id": volume['id'],
"address": address}})
"connector": connector}})
@wrap_check_policy
def terminate_connection(self, context, volume, address):
def terminate_connection(self, context, volume, connector):
host = volume['host']
queue = self.db.queue_get_for(context, FLAGS.volume_topic, host)
return rpc.call(context, queue,
{"method": "terminate_connection",
"args": {"volume_id": volume['id'],
"address": address}})
"connector": connector}})
def _create_snapshot(self, context, volume, name, description,
force=False):

View File

@@ -215,12 +215,12 @@ class VolumeDriver(object):
"""Make sure volume is exported."""
raise NotImplementedError()
def initialize_connection(self, volume, address):
"""Allow connection to ip and return connection info."""
def initialize_connection(self, volume, connector):
"""Allow connection to connector and return connection info."""
raise NotImplementedError()
def terminate_connection(self, volume, address):
"""Disallow connection from ip"""
def terminate_connection(self, volume, connector):
"""Disallow connection from connector"""
raise NotImplementedError()
def get_volume_stats(self, refresh=False):
@@ -409,7 +409,7 @@ class ISCSIDriver(VolumeDriver):
'-v', property_value)
return self._run_iscsiadm(iscsi_properties, iscsi_command)
def initialize_connection(self, volume, address):
def initialize_connection(self, volume, connector):
"""Initializes the connection and returns connection info.
The iscsi driver returns a driver_volume_type of 'iscsi'.
@@ -433,7 +433,7 @@ class ISCSIDriver(VolumeDriver):
'data': iscsi_properties
}
def terminate_connection(self, volume, address):
def terminate_connection(self, volume, connector):
pass
def check_for_export(self, context, volume_id):
@@ -461,13 +461,13 @@ class FakeISCSIDriver(ISCSIDriver):
"""No setup necessary in fake mode."""
pass
def initialize_connection(self, volume, address):
def initialize_connection(self, volume, connector):
return {
'driver_volume_type': 'iscsi',
'data': {}
}
def terminate_connection(self, volume, address):
def terminate_connection(self, volume, connector):
pass
@staticmethod
@@ -532,7 +532,7 @@ class RBDDriver(VolumeDriver):
"""Removes an export for a logical volume"""
pass
def initialize_connection(self, volume, address):
def initialize_connection(self, volume, connector):
return {
'driver_volume_type': 'rbd',
'data': {
@@ -540,7 +540,7 @@ class RBDDriver(VolumeDriver):
}
}
def terminate_connection(self, volume, address):
def terminate_connection(self, volume, connector):
pass
@@ -601,7 +601,7 @@ class SheepdogDriver(VolumeDriver):
"""Removes an export for a logical volume"""
pass
def initialize_connection(self, volume, address):
def initialize_connection(self, volume, connector):
return {
'driver_volume_type': 'sheepdog',
'data': {
@@ -609,7 +609,7 @@ class SheepdogDriver(VolumeDriver):
}
}
def terminate_connection(self, volume, address):
def terminate_connection(self, volume, connector):
pass
@@ -638,10 +638,10 @@ class LoggingVolumeDriver(VolumeDriver):
def remove_export(self, context, volume):
self.log_action('remove_export', volume)
def initialize_connection(self, volume, address):
def initialize_connection(self, volume, connector):
self.log_action('initialize_connection', volume)
def terminate_connection(self, volume, address):
def terminate_connection(self, volume, connector):
self.log_action('terminate_connection', volume)
def check_for_export(self, context, volume_id):

View File

@@ -254,31 +254,51 @@ class VolumeManager(manager.SchedulerDependentManager):
# TODO(vish): refactor this into a more general "unreserve"
self.db.volume_detached(context, volume_id)
def initialize_connection(self, context, volume_id, address):
"""Initialize volume to be connected from address.
def initialize_connection(self, context, volume_id, connector):
"""Prepare volume for connection from host represented by connector.
This method calls the driver initialize_connection and returns
it to the caller. The driver is responsible for doing any
necessary security setup and returning a connection_info dictionary
in the following format:
{'driver_volume_type': driver_volume_type
'data': data}
it to the caller. The connector parameter is a dictionary with
information about the host that will connect to the volume in the
following format:
{
'ip': ip,
'initiator': initiator,
}
ip: the ip address of the connecting machine
initiator: the iscsi initiator name of the connecting machine.
This can be None if the connecting machine does not support iscsi
connections.
driver is responsible for doing any necessary security setup and
returning a connection_info dictionary in the following format:
{
'driver_volume_type': driver_volume_type,
'data': data,
}
driver_volume_type: a string to identify the type of volume. This
can be used by the calling code to determine the
strategy for connecting to the volume. This could
be 'iscsi', 'rbd', 'sheepdog', etc.
data: this is the data that the calling code will use to connect
to the volume. Keep in mind that this will be serialized to
json in various places, so it should not contain any non-json
data types.
"""
volume_ref = self.db.volume_get(context, volume_id)
return self.driver.initialize_connection(volume_ref, address)
return self.driver.initialize_connection(volume_ref, connector)
def terminate_connection(self, context, volume_id, address):
def terminate_connection(self, context, volume_id, connector):
"""Cleanup connection from host represented by connector.
The format of connector is the same as for initialize_connection.
"""
volume_ref = self.db.volume_get(context, volume_id)
self.driver.terminate_connection(volume_ref, address)
self.driver.terminate_connection(volume_ref, connector)
def check_for_export(self, context, instance_id):
"""Make sure whether volume is exported."""

View File

@@ -203,7 +203,7 @@ class XenSMDriver(VolumeDriver):
"""Safely, synchronously recreates an export for a logical volume."""
pass
def initialize_connection(self, volume, address):
def initialize_connection(self, volume, connector):
try:
xensm_properties = dict(self.db.sm_volume_get(self.ctxt,
volume['id']))
@@ -236,5 +236,5 @@ class XenSMDriver(VolumeDriver):
'data': xensm_properties
}
def terminate_connection(self, volume, address):
def terminate_connection(self, volume, connector):
pass