summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--nova/compute/manager.py19
-rwxr-xr-xnova/rootwrap/compute.py3
-rw-r--r--nova/tests/fake_libvirt_utils.py4
-rw-r--r--nova/tests/test_libvirt.py46
-rw-r--r--nova/tests/test_virt_drivers.py6
-rw-r--r--nova/virt/driver.py14
-rw-r--r--nova/virt/fake.py3
-rw-r--r--nova/virt/libvirt/connection.py16
-rw-r--r--nova/virt/libvirt/utils.py10
-rw-r--r--nova/virt/vmwareapi_conn.py9
-rw-r--r--nova/virt/xenapi_conn.py15
-rw-r--r--nova/volume/api.py8
-rw-r--r--nova/volume/driver.py28
-rw-r--r--nova/volume/manager.py40
-rw-r--r--nova/volume/xensm.py4
15 files changed, 176 insertions, 49 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 64401eeca..bd911ae0d 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -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
diff --git a/nova/rootwrap/compute.py b/nova/rootwrap/compute.py
index 8b7736cbd..65e6dfebb 100755
--- a/nova/rootwrap/compute.py
+++ b/nova/rootwrap/compute.py
@@ -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"),
]
diff --git a/nova/tests/fake_libvirt_utils.py b/nova/tests/fake_libvirt_utils.py
index f1d4a8ec7..27b753a64 100644
--- a/nova/tests/fake_libvirt_utils.py
+++ b/nova/tests/fake_libvirt_utils.py
@@ -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
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index cbe45ed2f..72c9f8802 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -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',
diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py
index de3906a12..2cda7a61e 100644
--- a/nova/tests/test_virt_drivers.py
+++ b/nova/tests/test_virt_drivers.py
@@ -225,6 +225,12 @@ class _VirtDriverTestCase(test.TestCase):
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()
self.connection.attach_volume({'driver_volume_type': 'fake'},
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index e726f7933..d87a7286a 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -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()
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 780d644eb..ff8da4724 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -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'}
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index f56e45df4..37ad07736 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -188,6 +188,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
@@ -425,6 +426,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')
@@ -724,7 +735,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)
@@ -753,7 +764,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)
diff --git a/nova/virt/libvirt/utils.py b/nova/virt/libvirt/utils.py
index 4409e6aa6..f96128124 100644
--- a/nova/virt/libvirt/utils.py
+++ b/nova/virt/libvirt/utils.py
@@ -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
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py
index c63f1e9d5..909969fac 100644
--- a/nova/virt/vmwareapi_conn.py
+++ b/nova/virt/vmwareapi_conn.py
@@ -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
diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py
index 236bd4762..26e36b911 100644
--- a/nova/virt/xenapi_conn.py
+++ b/nova/virt/xenapi_conn.py
@@ -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)
diff --git a/nova/volume/api.py b/nova/volume/api.py
index cc9cac019..d5042833e 100644
--- a/nova/volume/api.py
+++ b/nova/volume/api.py
@@ -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):
diff --git a/nova/volume/driver.py b/nova/volume/driver.py
index fa2893737..f00eb0800 100644
--- a/nova/volume/driver.py
+++ b/nova/volume/driver.py
@@ -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):
diff --git a/nova/volume/manager.py b/nova/volume/manager.py
index 7e739031b..15f26f79e 100644
--- a/nova/volume/manager.py
+++ b/nova/volume/manager.py
@@ -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, connector):
+ """Cleanup connection from host represented by connector.
- def terminate_connection(self, context, volume_id, address):
+ 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."""
diff --git a/nova/volume/xensm.py b/nova/volume/xensm.py
index bf219f273..a706b10be 100644
--- a/nova/volume/xensm.py
+++ b/nova/volume/xensm.py
@@ -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