summaryrefslogtreecommitdiffstats
path: root/nova
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2012-05-02 23:00:29 +0000
committerGerrit Code Review <review@openstack.org>2012-05-02 23:00:29 +0000
commit4db018636dcc2c083f5dc92a7dd993766337c86c (patch)
treeae3e8197ce150af71c69be6500cd94e0860f73e4 /nova
parentfba02baf8dbf6d3d9a7ed58a498670157004bce5 (diff)
parentd14ac4bf38f23e429572f210f7b0560493968b15 (diff)
downloadnova-4db018636dcc2c083f5dc92a7dd993766337c86c.tar.gz
nova-4db018636dcc2c083f5dc92a7dd993766337c86c.tar.xz
nova-4db018636dcc2c083f5dc92a7dd993766337c86c.zip
Merge "Adding 'host' info to volume-compute connection information."
Diffstat (limited to 'nova')
-rw-r--r--nova/tests/test_libvirt.py8
-rw-r--r--nova/tests/test_virt_drivers.py1
-rw-r--r--nova/tests/volume/__init__.py13
-rw-r--r--nova/tests/volume/test_HpSanISCSIDriver.py212
-rw-r--r--nova/virt/driver.py5
-rw-r--r--nova/virt/fake.py2
-rw-r--r--nova/virt/libvirt/connection.py1
-rw-r--r--nova/virt/vmwareapi_conn.py5
-rw-r--r--nova/virt/xenapi/connection.py11
-rw-r--r--nova/volume/san.py89
10 files changed, 286 insertions, 61 deletions
diff --git a/nova/tests/test_libvirt.py b/nova/tests/test_libvirt.py
index 9a459545c..ee17abc4f 100644
--- a/nova/tests/test_libvirt.py
+++ b/nova/tests/test_libvirt.py
@@ -142,7 +142,8 @@ class LibvirtVolumeTestCase(test.TestCase):
self.fake_conn = FakeLibvirtConnection()
self.connr = {
'ip': '127.0.0.1',
- 'initiator': 'fake_initiator'
+ 'initiator': 'fake_initiator',
+ 'host': 'fake_host'
}
def test_libvirt_iscsi_driver(self):
@@ -478,12 +479,15 @@ class LibvirtConnTestCase(test.TestCase):
def test_get_connector(self):
initiator = 'fake.initiator.iqn'
ip = 'fakeip'
+ host = 'fakehost'
self.flags(my_ip=ip)
+ self.flags(host=host)
conn = connection.LibvirtConnection(True)
expected = {
'ip': ip,
- 'initiator': initiator
+ 'initiator': initiator,
+ 'host': host
}
volume = {
'id': 'fake'
diff --git a/nova/tests/test_virt_drivers.py b/nova/tests/test_virt_drivers.py
index 592926597..f8ed0f2ea 100644
--- a/nova/tests/test_virt_drivers.py
+++ b/nova/tests/test_virt_drivers.py
@@ -231,6 +231,7 @@ class _VirtDriverTestCase(test.TestCase):
result = self.connection.get_volume_connector({'id': 'fake'})
self.assertTrue('ip' in result)
self.assertTrue('initiator' in result)
+ self.assertTrue('host' in result)
@catch_notimplementederror
def test_attach_detach_volume(self):
diff --git a/nova/tests/volume/__init__.py b/nova/tests/volume/__init__.py
new file mode 100644
index 000000000..4549abf92
--- /dev/null
+++ b/nova/tests/volume/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2012 OpenStack LLC
+#
+# 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.
diff --git a/nova/tests/volume/test_HpSanISCSIDriver.py b/nova/tests/volume/test_HpSanISCSIDriver.py
new file mode 100644
index 000000000..467c5d728
--- /dev/null
+++ b/nova/tests/volume/test_HpSanISCSIDriver.py
@@ -0,0 +1,212 @@
+# Copyright 2012 OpenStack LLC
+#
+# 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.
+from nova import exception
+from nova import log as logging
+from nova.volume import san
+from nova import test
+
+LOG = logging.getLogger(__name__)
+
+
+class HpSanISCSITestCase(test.TestCase):
+
+ def setUp(self):
+ super(HpSanISCSITestCase, self).setUp()
+ self.stubs.Set(san.HpSanISCSIDriver, "_cliq_run",
+ self._fake_cliq_run)
+ self.stubs.Set(san.HpSanISCSIDriver, "_get_iscsi_properties",
+ self._fake_get_iscsi_properties)
+ self.driver = san.HpSanISCSIDriver()
+ self.volume_name = "fakevolume"
+ self.connector = {'ip': '10.0.0.2',
+ 'initiator': 'iqn.1993-08.org.debian:01:222',
+ 'host': 'fakehost'}
+ self.properties = {'target_discoverd': True,
+ 'target_portal': '10.0.1.6:3260',
+ 'target_iqn':
+ 'iqn.2003-10.com.lefthandnetworks:group01:25366:fakev',
+ 'volume_id': 1}
+
+ def tearDown(self):
+ super(HpSanISCSITestCase, self).tearDown()
+
+ def _fake_get_iscsi_properties(self, volume):
+ return self.properties
+
+ def _fake_cliq_run(self, verb, cliq_args):
+ """Return fake results for the various methods."""
+
+ def create_volume(cliq_args):
+ """
+ input = "createVolume description="fake description"
+ clusterName=Cluster01 volumeName=fakevolume
+ thinProvision=0 output=XML size=1GB"
+ """
+ output = """<gauche version="1.0">
+ <response description="Operation succeeded."
+ name="CliqSuccess" processingTime="181" result="0"/>
+ </gauche>"""
+ self.assertEqual(cliq_args['volumeName'], self.volume_name)
+ self.assertEqual(cliq_args['thinProvision'], '1')
+ self.assertEqual(cliq_args['size'], '1GB')
+ return output, None
+
+ def delete_volume(cliq_args):
+ """
+ input = "deleteVolume volumeName=fakevolume prompt=false
+ output=XML"
+ """
+ output = """<gauche version="1.0">
+ <response description="Operation succeeded."
+ name="CliqSuccess" processingTime="164" result="0"/>
+ </gauche>"""
+ self.assertEqual(cliq_args['volumeName'], self.volume_name)
+ self.assertEqual(cliq_args['prompt'], 'false')
+ return output, None
+
+ def assign_volume(cliq_args):
+ """
+ input = "assignVolumeToServer volumeName=fakevolume
+ serverName=fakehost
+ output=XML"
+ """
+ output = """<gauche version="1.0">
+ <response description="Operation succeeded."
+ name="CliqSuccess" processingTime="174" result="0"/>
+ </gauche>"""
+ self.assertEqual(cliq_args['volumeName'], self.volume_name)
+ self.assertEqual(cliq_args['serverName'], self.connector['host'])
+ return output, None
+
+ def unassign_volume(cliq_args):
+ """
+ input = "unassignVolumeToServer volumeName=fakevolume
+ serverName=fakehost output=XML
+ """
+ output = """<gauche version="1.0">
+ <response description="Operation succeeded."
+ name="CliqSuccess" processingTime="205" result="0"/>
+ </gauche>"""
+ self.assertEqual(cliq_args['volumeName'], self.volume_name)
+ self.assertEqual(cliq_args['serverName'], self.connector['host'])
+ return output, None
+
+ def get_cluster_info(cliq_args):
+ """
+ input = "getClusterInfo clusterName=Cluster01 searchDepth=1
+ verbose=0 output=XML"
+ """
+ output = """<gauche version="1.0">
+ <response description="Operation succeeded." name="CliqSuccess"
+ processingTime="1164" result="0">
+ <cluster blockSize="1024" description=""
+ maxVolumeSizeReplication1="622957690"
+ maxVolumeSizeReplication2="311480287"
+ minVolumeSize="262144" name="Cluster01"
+ pageSize="262144" spaceTotal="633697992"
+ storageNodeCount="2" unprovisionedSpace="622960574"
+ useVip="true">
+ <nsm ipAddress="10.0.1.7" name="111-vsa"/>
+ <nsm ipAddress="10.0.1.8" name="112-vsa"/>
+ <vip ipAddress="10.0.1.6" subnetMask="255.255.255.0"/>
+ </cluster></response></gauche>"""
+ return output, None
+
+ def get_volume_info(cliq_args):
+ """
+ input = "getVolumeInfo volumeName=fakevolume output=XML"
+ """
+ output = """<gauche version="1.0">
+ <response description="Operation succeeded." name="CliqSuccess"
+ processingTime="87" result="0">
+ <volume autogrowPages="4" availability="online"
+ blockSize="1024" bytesWritten="0" checkSum="false"
+ clusterName="Cluster01" created="2011-02-08T19:56:53Z"
+ deleting="false" description="" groupName="Group01"
+ initialQuota="536870912" isPrimary="true"
+ iscsiIqn="iqn.2003-10.com.lefthandnetworks:group01:25366:fakev"
+ maxSize="6865387257856" md5="9fa5c8b2cca54b2948a63d833097e1ca"
+ minReplication="1" name="vol-b" parity="0" replication="2"
+ reserveQuota="536870912" scratchQuota="4194304"
+ serialNumber="9fa5c8b2cca54b2948a63d8"
+ size="1073741824" stridePages="32" thinProvision="true">
+ <status description="OK" value="2"/>
+ <permission access="rw" authGroup="api-1"
+ chapName="chapusername" chapRequired="true"
+ id="25369" initiatorSecret="" iqn=""
+ iscsiEnabled="true" loadBalance="true"
+ targetSecret="supersecret"/>
+ </volume></response></gauche>"""
+ return output, None
+
+ def test_error(cliq_args):
+ output = """<gauche version="1.0">
+ <response description="Volume '134234' not found."
+ name="CliqVolumeNotFound" processingTime="1083"
+ result="8000100c"/>
+ </gauche>"""
+ return output, None
+
+ self.assertEqual(cliq_args['output'], 'XML')
+ try:
+ verbs = {'createVolume': create_volume,
+ 'deleteVolume': delete_volume,
+ 'assignVolumeToServer': assign_volume,
+ 'unassignVolumeToServer': unassign_volume,
+ 'getClusterInfo': get_cluster_info,
+ 'getVolumeInfo': get_volume_info,
+ 'testError': test_error}
+ except KeyError:
+ raise NotImplementedError()
+
+ return verbs[verb](cliq_args)
+
+ def test_create_volume(self):
+ volume = {'name': self.volume_name, 'size': 1}
+ model_update = self.driver.create_volume(volume)
+ expected_iqn = "iqn.2003-10.com.lefthandnetworks:group01:25366:fakev"
+ expected_location = "10.0.1.6:3260,1 %s" % expected_iqn
+ self.assertEqual(model_update['provider_location'], expected_location)
+
+ def test_delete_volume(self):
+ volume = {'name': self.volume_name}
+ self.driver.delete_volume(volume)
+
+ def test_initialize_connection(self):
+ volume = {'name': self.volume_name}
+ result = self.driver.initialize_connection(volume, self.connector)
+ self.assertEqual(result['driver_volume_type'], 'iscsi')
+ self.assertDictMatch(result['data'], self.properties)
+
+ def test_terminate_connection(self):
+ volume = {'name': self.volume_name}
+ self.driver.terminate_connection(volume, self.connector)
+
+ def test_create_snapshot(self):
+ try:
+ self.driver.create_snapshot("")
+ except NotImplementedError:
+ pass
+
+ def test_create_volume_from_snapshot(self):
+ try:
+ self.driver.create_volume_from_snapshot("", "")
+ except NotImplementedError:
+ pass
+
+ def test_cliq_error(self):
+ try:
+ self.driver._cliq_run_xml("testError", {})
+ except exception.Error:
+ pass
diff --git a/nova/virt/driver.py b/nova/virt/driver.py
index db004de80..68cbea340 100644
--- a/nova/virt/driver.py
+++ b/nova/virt/driver.py
@@ -676,12 +676,13 @@ class ComputeDriver(object):
"""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::
+ machine that will be making the connection, the name of the iscsi
+ initiator and the hostname of the machine as follows::
{
'ip': ip,
'initiator': initiator,
+ 'host': hostname
}
"""
raise NotImplementedError()
diff --git a/nova/virt/fake.py b/nova/virt/fake.py
index 3b0dbfd4c..b08c7661d 100644
--- a/nova/virt/fake.py
+++ b/nova/virt/fake.py
@@ -335,4 +335,4 @@ class FakeConnection(driver.ComputeDriver):
pass
def get_volume_connector(self, instance):
- return {'ip': '127.0.0.1', 'initiator': 'fake'}
+ return {'ip': '127.0.0.1', 'initiator': 'fake', 'host': 'fakehost'}
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
index 6d97ce170..4f7a15a86 100644
--- a/nova/virt/libvirt/connection.py
+++ b/nova/virt/libvirt/connection.py
@@ -489,6 +489,7 @@ class LibvirtConnection(driver.ComputeDriver):
return {
'ip': FLAGS.my_ip,
'initiator': self._initiator,
+ 'host': FLAGS.host
}
def _cleanup_resize(self, instance):
diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py
index 444fc785a..a5f771eee 100644
--- a/nova/virt/vmwareapi_conn.py
+++ b/nova/virt/vmwareapi_conn.py
@@ -177,10 +177,11 @@ class VMWareESXConnection(driver.ComputeDriver):
def get_volume_connector(self, _instance):
"""Return volume connector information"""
# TODO(vish): When volume attaching is supported, return the
- # proper initiator iqn.
+ # proper initiator iqn and host.
return {
'ip': FLAGS.vmwareapi_host_ip,
- 'initiator': None
+ 'initiator': None,
+ 'host': None
}
def attach_volume(self, connection_info, instance_name, mountpoint):
diff --git a/nova/virt/xenapi/connection.py b/nova/virt/xenapi/connection.py
index 2fa5516d3..c02f3581d 100644
--- a/nova/virt/xenapi/connection.py
+++ b/nova/virt/xenapi/connection.py
@@ -156,6 +156,7 @@ class XenAPIConnection(driver.ComputeDriver):
self._host = host.Host(self._session)
self._vmops = vmops.VMOps(self._session)
self._initiator = None
+ self._hypervisor_hostname = None
self._pool = pool.ResourcePool(self._session)
@property
@@ -336,17 +337,19 @@ class XenAPIConnection(driver.ComputeDriver):
def get_volume_connector(self, instance):
"""Return volume connector information"""
- if not self._initiator:
+ if not self._initiator or not self._hypervisor_hostname:
stats = self.get_host_stats(refresh=True)
try:
self._initiator = stats['host_other-config']['iscsi_iqn']
- except (TypeError, KeyError):
- LOG.warn(_('Could not determine iscsi initiator name'),
+ self._hypervisor_hostname = stats['host_hostname']
+ except (TypeError, KeyError) as err:
+ LOG.warn(_('Could not determine key: %s') % err,
instance=instance)
self._initiator = None
return {
'ip': self.get_host_ip_addr(),
- 'initiator': self._initiator
+ 'initiator': self._initiator,
+ 'host': self._hypervisor_hostname
}
@staticmethod
diff --git a/nova/volume/san.py b/nova/volume/san.py
index aecb9a0b5..9d83864d0 100644
--- a/nova/volume/san.py
+++ b/nova/volume/san.py
@@ -582,6 +582,14 @@ class HpSanISCSIDriver(SanISCSIDriver):
return model_update
+ def create_volume_from_snapshot(self, volume, snapshot):
+ """Creates a volume from a snapshot."""
+ raise NotImplementedError()
+
+ def create_snapshot(self, snapshot):
+ """Creates a snapshot."""
+ raise NotImplementedError()
+
def delete_volume(self, volume):
"""Deletes a volume."""
cliq_args = {}
@@ -594,64 +602,45 @@ class HpSanISCSIDriver(SanISCSIDriver):
# TODO(justinsb): Is this needed here?
raise exception.Error(_("local_path not supported"))
- def ensure_export(self, context, volume):
- """Synchronously recreates an export for a logical volume."""
- return self._do_export(context, volume, force_create=False)
-
- def create_export(self, context, volume):
- return self._do_export(context, volume, force_create=True)
+ def initialize_connection(self, volume, connector):
+ """Assigns the volume to a server.
- def _do_export(self, context, volume, force_create):
- """Supports ensure_export and create_export"""
- volume_info = self._cliq_get_volume_info(volume['name'])
+ Assign any created volume to a compute node/host so that it can be
+ used from that host. HP VSA requires a volume to be assigned
+ to a server.
- is_shared = 'permission.authGroup' in volume_info
+ This driver returns a driver_volume_type of 'iscsi'.
+ The format of the driver data is defined in _get_iscsi_properties.
+ Example return value::
- model_update = {}
+ {
+ 'driver_volume_type': 'iscsi'
+ 'data': {
+ 'target_discovered': True,
+ 'target_iqn': 'iqn.2010-10.org.openstack:volume-00000001',
+ 'target_portal': '127.0.0.0.1:3260',
+ 'volume_id': 1,
+ }
+ }
- should_export = False
-
- if force_create or not is_shared:
- should_export = True
- # Check that we have a project_id
- project_id = volume['project_id']
- if not project_id:
- project_id = context.project_id
-
- if project_id:
- #TODO(justinsb): Use a real per-project password here
- chap_username = 'proj_' + project_id
- # HP/Lefthand requires that the password be >= 12 characters
- chap_password = 'project_secret_' + project_id
- else:
- msg = (_("Could not determine project for volume %s, "
- "can't export") %
- (volume['name']))
- if force_create:
- raise exception.Error(msg)
- else:
- LOG.warn(msg)
- should_export = False
-
- if should_export:
- cliq_args = {}
- cliq_args['volumeName'] = volume['name']
- cliq_args['chapName'] = chap_username
- cliq_args['targetSecret'] = chap_password
-
- self._cliq_run_xml("assignVolumeChap", cliq_args)
-
- model_update['provider_auth'] = ("CHAP %s %s" %
- (chap_username, chap_password))
+ """
+ cliq_args = {}
+ cliq_args['volumeName'] = volume['name']
+ cliq_args['serverName'] = connector['host']
+ self._cliq_run_xml("assignVolumeToServer", cliq_args)
- return model_update
+ iscsi_properties = self._get_iscsi_properties(volume)
+ return {
+ 'driver_volume_type': 'iscsi',
+ 'data': iscsi_properties
+ }
- def remove_export(self, context, volume):
- """Removes an export for a logical volume."""
+ def terminate_connection(self, volume, connector):
+ """Unassign the volume from the host."""
cliq_args = {}
cliq_args['volumeName'] = volume['name']
-
- self._cliq_run_xml("unassignVolume", cliq_args)
+ cliq_args['serverName'] = connector['host']
+ self._cliq_run_xml("unassignVolumeToServer", cliq_args)
class SolidFireSanISCSIDriver(SanISCSIDriver):