Merge "Adding 'host' info to volume-compute connection information."
This commit is contained in:
commit
4db018636d
@ -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'
|
||||
|
@ -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):
|
||||
|
13
nova/tests/volume/__init__.py
Normal file
13
nova/tests/volume/__init__.py
Normal file
@ -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.
|
212
nova/tests/volume/test_HpSanISCSIDriver.py
Normal file
212
nova/tests/volume/test_HpSanISCSIDriver.py
Normal file
@ -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
|
@ -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()
|
||||
|
@ -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'}
|
||||
|
@ -489,6 +489,7 @@ class LibvirtConnection(driver.ComputeDriver):
|
||||
return {
|
||||
'ip': FLAGS.my_ip,
|
||||
'initiator': self._initiator,
|
||||
'host': FLAGS.host
|
||||
}
|
||||
|
||||
def _cleanup_resize(self, instance):
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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 initialize_connection(self, volume, connector):
|
||||
"""Assigns the volume to a server.
|
||||
|
||||
def create_export(self, context, volume):
|
||||
return self._do_export(context, volume, force_create=True)
|
||||
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.
|
||||
|
||||
def _do_export(self, context, volume, force_create):
|
||||
"""Supports ensure_export and create_export"""
|
||||
volume_info = self._cliq_get_volume_info(volume['name'])
|
||||
This driver returns a driver_volume_type of 'iscsi'.
|
||||
The format of the driver data is defined in _get_iscsi_properties.
|
||||
Example return value::
|
||||
|
||||
is_shared = 'permission.authGroup' in volume_info
|
||||
{
|
||||
'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,
|
||||
}
|
||||
}
|
||||
|
||||
model_update = {}
|
||||
|
||||
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))
|
||||
|
||||
return model_update
|
||||
|
||||
def remove_export(self, context, volume):
|
||||
"""Removes an export for a logical volume."""
|
||||
"""
|
||||
cliq_args = {}
|
||||
cliq_args['volumeName'] = volume['name']
|
||||
cliq_args['serverName'] = connector['host']
|
||||
self._cliq_run_xml("assignVolumeToServer", cliq_args)
|
||||
|
||||
self._cliq_run_xml("unassignVolume", cliq_args)
|
||||
iscsi_properties = self._get_iscsi_properties(volume)
|
||||
return {
|
||||
'driver_volume_type': 'iscsi',
|
||||
'data': iscsi_properties
|
||||
}
|
||||
|
||||
def terminate_connection(self, volume, connector):
|
||||
"""Unassign the volume from the host."""
|
||||
cliq_args = {}
|
||||
cliq_args['volumeName'] = volume['name']
|
||||
cliq_args['serverName'] = connector['host']
|
||||
self._cliq_run_xml("unassignVolumeToServer", cliq_args)
|
||||
|
||||
|
||||
class SolidFireSanISCSIDriver(SanISCSIDriver):
|
||||
|
Loading…
Reference in New Issue
Block a user