summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvladimir.p <vladimir@zadarastorage.com>2011-07-27 22:49:16 -0700
committervladimir.p <vladimir@zadarastorage.com>2011-07-27 22:49:16 -0700
commit401de172b86a13010885e70bc78351e72a7dfde3 (patch)
tree4a3a892c646234b41791d5c9c7c3c3caf502edbf
parent336b2703ef90fcd7b422434434c9967880b97204 (diff)
downloadnova-401de172b86a13010885e70bc78351e72a7dfde3.tar.gz
nova-401de172b86a13010885e70bc78351e72a7dfde3.tar.xz
nova-401de172b86a13010885e70bc78351e72a7dfde3.zip
prior to nova-1336 merge
-rw-r--r--nova/scheduler/vsa.py77
-rw-r--r--nova/tests/api/openstack/test_extensions.py7
-rw-r--r--nova/tests/scheduler/test_vsa_scheduler.py616
-rw-r--r--nova/tests/test_vsa.py2
-rw-r--r--nova/tests/test_vsa_volumes.py23
-rw-r--r--nova/volume/driver.py4
-rw-r--r--nova/vsa/api.py41
-rw-r--r--nova/vsa/manager.py2
8 files changed, 689 insertions, 83 deletions
diff --git a/nova/scheduler/vsa.py b/nova/scheduler/vsa.py
index 6931afc2b..f66ce989c 100644
--- a/nova/scheduler/vsa.py
+++ b/nova/scheduler/vsa.py
@@ -65,40 +65,29 @@ class VsaScheduler(simple.SimpleScheduler):
{"method": "notification",
"args": {"event": event}})
- def _compare_names(self, str1, str2):
- result = str1.lower() == str2.lower()
- # LOG.debug(_("Comparing %(str1)s and %(str2)s. "\
- # "Result %(result)s"), locals())
- return result
-
- def _compare_sizes_exact_match(self, cap_capacity, size_gb):
- cap_capacity = BYTES_TO_GB(int(cap_capacity))
- size_gb = int(size_gb)
- result = cap_capacity == size_gb
- # LOG.debug(_("Comparing %(cap_capacity)d and %(size_gb)d. "\
- # "Result %(result)s"), locals())
- return result
-
- def _compare_sizes_approxim(self, cap_capacity, size_gb):
- cap_capacity = BYTES_TO_GB(int(cap_capacity))
- size_gb = int(size_gb)
- size_perc = size_gb * FLAGS.drive_type_approx_capacity_percent / 100
-
- result = cap_capacity >= size_gb - size_perc and \
- cap_capacity <= size_gb + size_perc
- # LOG.debug(_("Comparing %(cap_capacity)d and %(size_gb)d. "\
- # "Result %(result)s"), locals())
- return result
-
def _qosgrp_match(self, drive_type, qos_values):
+ def _compare_names(str1, str2):
+ result = str1.lower() == str2.lower()
+ return result
+
+ def _compare_sizes_approxim(cap_capacity, size_gb):
+ cap_capacity = BYTES_TO_GB(int(cap_capacity))
+ size_gb = int(size_gb)
+ size_perc = size_gb * \
+ FLAGS.drive_type_approx_capacity_percent / 100
+
+ result = cap_capacity >= size_gb - size_perc and \
+ cap_capacity <= size_gb + size_perc
+ return result
+
# Add more entries for additional comparisons
compare_list = [{'cap1': 'DriveType',
'cap2': 'type',
- 'cmp_func': self._compare_names},
+ 'cmp_func': _compare_names},
{'cap1': 'DriveCapacity',
'cap2': 'size_gb',
- 'cmp_func': self._compare_sizes_approxim}]
+ 'cmp_func': _compare_sizes_approxim}]
for cap in compare_list:
if cap['cap1'] in qos_values.keys() and \
@@ -106,20 +95,23 @@ class VsaScheduler(simple.SimpleScheduler):
cap['cmp_func'] is not None and \
cap['cmp_func'](qos_values[cap['cap1']],
drive_type[cap['cap2']]):
- # LOG.debug(("One of required capabilities found: %s:%s"),
- # cap['cap1'], drive_type[cap['cap2']])
pass
else:
return False
return True
+ def _get_service_states(self):
+ return self.zone_manager.service_states
+
def _filter_hosts(self, topic, request_spec, host_list=None):
+ LOG.debug(_("_filter_hosts: %(request_spec)s"), locals())
+
drive_type = request_spec['drive_type']
LOG.debug(_("Filter hosts for drive type %s"), drive_type['name'])
if host_list is None:
- host_list = self.zone_manager.service_states.iteritems()
+ host_list = self._get_service_states().iteritems()
filtered_hosts = [] # returns list of (hostname, capability_dict)
for host, host_dict in host_list:
@@ -131,7 +123,6 @@ class VsaScheduler(simple.SimpleScheduler):
for qosgrp, qos_values in gos_info.iteritems():
if self._qosgrp_match(drive_type, qos_values):
if qos_values['AvailableCapacity'] > 0:
- # LOG.debug(_("Adding host %s to the list"), host)
filtered_hosts.append((host, gos_info))
else:
LOG.debug(_("Host %s has no free capacity. Skip"),
@@ -226,7 +217,7 @@ class VsaScheduler(simple.SimpleScheduler):
"args": {"volume_id": volume_ref['id'],
"snapshot_id": None}})
- def _check_host_enforcement(self, availability_zone):
+ def _check_host_enforcement(self, context, availability_zone):
if (availability_zone
and ':' in availability_zone
and context.is_admin):
@@ -273,16 +264,10 @@ class VsaScheduler(simple.SimpleScheduler):
vol['capabilities'] = qos_cap
self._consume_resource(qos_cap, vol['size'], -1)
- # LOG.debug(_("Volume %(name)s assigned to host %(host)s"),
- # locals())
-
def schedule_create_volumes(self, context, request_spec,
availability_zone, *_args, **_kwargs):
"""Picks hosts for hosting multiple volumes."""
- LOG.debug(_("Service states BEFORE %s"),
- self.zone_manager.service_states)
-
num_volumes = request_spec.get('num_volumes')
LOG.debug(_("Attempting to spawn %(num_volumes)d volume(s)") %
locals())
@@ -290,16 +275,13 @@ class VsaScheduler(simple.SimpleScheduler):
vsa_id = request_spec.get('vsa_id')
volume_params = request_spec.get('volumes')
- host = self._check_host_enforcement(availability_zone)
+ host = self._check_host_enforcement(context, availability_zone)
try:
self._assign_hosts_to_volumes(context, volume_params, host)
for vol in volume_params:
self._provision_volume(context, vol, vsa_id, availability_zone)
-
- LOG.debug(_("Service states AFTER %s"),
- self.zone_manager.service_states)
except:
if vsa_id:
db.vsa_update(context, vsa_id,
@@ -309,8 +291,6 @@ class VsaScheduler(simple.SimpleScheduler):
if 'capabilities' in vol:
self._consume_resource(vol['capabilities'],
vol['size'], 1)
- LOG.debug(_("Service states AFTER %s"),
- self.zone_manager.service_states)
raise
return None
@@ -319,7 +299,8 @@ class VsaScheduler(simple.SimpleScheduler):
"""Picks the best host based on requested drive type capability."""
volume_ref = db.volume_get(context, volume_id)
- host = self._check_host_enforcement(volume_ref['availability_zone'])
+ host = self._check_host_enforcement(context,
+ volume_ref['availability_zone'])
if host:
now = utils.utcnow()
db.volume_update(context, volume_id, {'host': host,
@@ -333,9 +314,6 @@ class VsaScheduler(simple.SimpleScheduler):
volume_id, *_args, **_kwargs)
drive_type = dict(drive_type)
- LOG.debug(_("Service states BEFORE %s"),
- self.zone_manager.service_states)
-
LOG.debug(_("Spawning volume %(volume_id)s with drive type "\
"%(drive_type)s"), locals())
@@ -358,9 +336,6 @@ class VsaScheduler(simple.SimpleScheduler):
db.volume_update(context, volume_id, {'host': host,
'scheduled_at': now})
self._consume_resource(qos_cap, volume_ref['size'], -1)
-
- LOG.debug(_("Service states AFTER %s"),
- self.zone_manager.service_states)
return host
def _consume_full_drive(self, qos_values, direction):
diff --git a/nova/tests/api/openstack/test_extensions.py b/nova/tests/api/openstack/test_extensions.py
index d459c694f..2febe50e5 100644
--- a/nova/tests/api/openstack/test_extensions.py
+++ b/nova/tests/api/openstack/test_extensions.py
@@ -97,8 +97,9 @@ class ExtensionControllerTest(unittest.TestCase):
data = json.loads(response.body)
names = [x['name'] for x in data['extensions']]
names.sort()
- self.assertEqual(names, ["FlavorExtraSpecs", "Floating_ips",
- "Fox In Socks", "Hosts", "Multinic", "Volumes"])
+ self.assertEqual(names, ["DriveTypes", "FlavorExtraSpecs",
+ "Floating_ips", "Fox In Socks", "Hosts", "Multinic", "VSAs",
+ "Volumes"])
# Make sure that at least Fox in Sox is correct.
(fox_ext,) = [
@@ -145,7 +146,7 @@ class ExtensionControllerTest(unittest.TestCase):
# Make sure we have all the extensions.
exts = root.findall('{0}extension'.format(NS))
- self.assertEqual(len(exts), 6)
+ self.assertEqual(len(exts), 8)
# Make sure that at least Fox in Sox is correct.
(fox_ext,) = [x for x in exts if x.get('alias') == 'FOXNSOX']
diff --git a/nova/tests/scheduler/test_vsa_scheduler.py b/nova/tests/scheduler/test_vsa_scheduler.py
new file mode 100644
index 000000000..697ad3842
--- /dev/null
+++ b/nova/tests/scheduler/test_vsa_scheduler.py
@@ -0,0 +1,616 @@
+# 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.
+
+import stubout
+
+import nova
+from nova import exception
+from nova import flags
+from nova import db
+from nova import context
+from nova import test
+from nova import utils
+from nova import log as logging
+
+from nova.scheduler import vsa as vsa_sched
+from nova.scheduler import driver
+
+FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.tests.scheduler.vsa')
+
+scheduled_volumes = []
+scheduled_volume = {}
+global_volume = {}
+
+
+class FakeVsaLeastUsedScheduler(
+ vsa_sched.VsaSchedulerLeastUsedHost):
+ # No need to stub anything at the moment
+ pass
+
+
+class FakeVsaMostAvailCapacityScheduler(
+ vsa_sched.VsaSchedulerMostAvailCapacity):
+ # No need to stub anything at the moment
+ pass
+
+
+class VsaSchedulerTestCase(test.TestCase):
+
+ def _get_vol_creation_request(self, num_vols, drive_ix, size=0):
+ volume_params = []
+ for i in range(num_vols):
+ drive_type = {'id': i,
+ 'name': 'name_' + str(drive_ix),
+ 'type': 'type_' + str(drive_ix),
+ 'size_gb': 1 + 100 * (drive_ix)}
+ volume = {'size': size,
+ 'snapshot_id': None,
+ 'name': 'vol_' + str(i),
+ 'description': None,
+ 'drive_ref': drive_type}
+ volume_params.append(volume)
+
+ return {'num_volumes': len(volume_params),
+ 'vsa_id': 123,
+ 'volumes': volume_params}
+
+ def _generate_default_service_states(self):
+ service_states = {}
+ for i in range(self.host_num):
+ host = {}
+ hostname = 'host_' + str(i)
+ if hostname in self.exclude_host_list:
+ continue
+
+ host['volume'] = {'timestamp': utils.utcnow(),
+ 'drive_qos_info': {}}
+
+ for j in range(self.drive_type_start_ix,
+ self.drive_type_start_ix + self.drive_type_num):
+ dtype = {}
+ dtype['Name'] = 'name_' + str(j)
+ dtype['DriveType'] = 'type_' + str(j)
+ dtype['TotalDrives'] = 2 * (self.init_num_drives + i)
+ dtype['DriveCapacity'] = vsa_sched.GB_TO_BYTES(1 + 100 * j)
+ dtype['TotalCapacity'] = dtype['TotalDrives'] * \
+ dtype['DriveCapacity']
+ dtype['AvailableCapacity'] = (dtype['TotalDrives'] - i) * \
+ dtype['DriveCapacity']
+ dtype['DriveRpm'] = 7200
+ dtype['DifCapable'] = 0
+ dtype['SedCapable'] = 0
+ dtype['PartitionDrive'] = {
+ 'PartitionSize': 0,
+ 'NumOccupiedPartitions': 0,
+ 'NumFreePartitions': 0}
+ dtype['FullDrive'] = {
+ 'NumFreeDrives': dtype['TotalDrives'] - i,
+ 'NumOccupiedDrives': i}
+ host['volume']['drive_qos_info'][dtype['Name']] = dtype
+
+ service_states[hostname] = host
+
+ return service_states
+
+ def _print_service_states(self):
+ for host, host_val in self.service_states.iteritems():
+ LOG.info(_("Host %s"), host)
+ total_used = 0
+ total_available = 0
+ qos = host_val['volume']['drive_qos_info']
+
+ for k, d in qos.iteritems():
+ LOG.info("\t%s: type %s: drives (used %2d, total %2d) "\
+ "size %3d, total %4d, used %4d, avail %d",
+ k, d['DriveType'],
+ d['FullDrive']['NumOccupiedDrives'], d['TotalDrives'],
+ vsa_sched.BYTES_TO_GB(d['DriveCapacity']),
+ vsa_sched.BYTES_TO_GB(d['TotalCapacity']),
+ vsa_sched.BYTES_TO_GB(d['TotalCapacity'] - \
+ d['AvailableCapacity']),
+ vsa_sched.BYTES_TO_GB(d['AvailableCapacity']))
+
+ total_used += vsa_sched.BYTES_TO_GB(d['TotalCapacity'] - \
+ d['AvailableCapacity'])
+ total_available += vsa_sched.BYTES_TO_GB(
+ d['AvailableCapacity'])
+ LOG.info("Host %s: used %d, avail %d",
+ host, total_used, total_available)
+
+ def _set_service_states(self, host_num,
+ drive_type_start_ix, drive_type_num,
+ init_num_drives=10,
+ exclude_host_list=[]):
+ self.host_num = host_num
+ self.drive_type_start_ix = drive_type_start_ix
+ self.drive_type_num = drive_type_num
+ self.exclude_host_list = exclude_host_list
+ self.init_num_drives = init_num_drives
+ self.service_states = self._generate_default_service_states()
+
+ def _get_service_states(self):
+ return self.service_states
+
+ def _fake_get_service_states(self):
+ return self._get_service_states()
+
+ def _fake_provision_volume(self, context, vol, vsa_id, availability_zone):
+ global scheduled_volumes
+ scheduled_volumes.append(dict(vol=vol,
+ vsa_id=vsa_id,
+ az=availability_zone))
+ name = vol['name']
+ host = vol['host']
+ LOG.debug(_("Test: provision vol %(name)s on host %(host)s"),
+ locals())
+ LOG.debug(_("\t vol=%(vol)s"), locals())
+ pass
+
+ def _fake_vsa_update(self, context, vsa_id, values):
+ LOG.debug(_("Test: VSA update request: vsa_id=%(vsa_id)s "\
+ "values=%(values)s"), locals())
+ pass
+
+ def _fake_volume_create(self, context, options):
+ LOG.debug(_("Test: Volume create: %s"), options)
+ options['id'] = 123
+ global global_volume
+ global_volume = options
+ return options
+
+ def _fake_volume_get(self, context, volume_id):
+ LOG.debug(_("Test: Volume get request: id=%(volume_id)s"), locals())
+ global global_volume
+ global_volume['id'] = volume_id
+ global_volume['availability_zone'] = None
+ return global_volume
+
+ def _fake_volume_update(self, context, volume_id, values):
+ LOG.debug(_("Test: Volume update request: id=%(volume_id)s "\
+ "values=%(values)s"), locals())
+ global scheduled_volume
+ scheduled_volume = {'id': volume_id, 'host': values['host']}
+ pass
+
+ def _fake_service_get_by_args(self, context, host, binary):
+ return "service"
+
+ def _fake_service_is_up_True(self, service):
+ return True
+
+ def _fake_service_is_up_False(self, service):
+ return False
+
+ def setUp(self, sched_class=None):
+ super(VsaSchedulerTestCase, self).setUp()
+ self.stubs = stubout.StubOutForTesting()
+ self.context_non_admin = context.RequestContext(None, None)
+ self.context = context.get_admin_context()
+
+ if sched_class is None:
+ self.sched = FakeVsaLeastUsedScheduler()
+ else:
+ self.sched = sched_class
+
+ self.host_num = 10
+ self.drive_type_num = 5
+
+ self.stubs.Set(self.sched,
+ '_get_service_states', self._fake_get_service_states)
+ self.stubs.Set(self.sched,
+ '_provision_volume', self._fake_provision_volume)
+ self.stubs.Set(nova.db, 'vsa_update', self._fake_vsa_update)
+
+ self.stubs.Set(nova.db, 'volume_get', self._fake_volume_get)
+ self.stubs.Set(nova.db, 'volume_update', self._fake_volume_update)
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ super(VsaSchedulerTestCase, self).tearDown()
+
+ def test_vsa_sched_create_volumes_simple(self):
+ global scheduled_volumes
+ scheduled_volumes = []
+ self._set_service_states(host_num=10,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=10,
+ exclude_host_list=['host_1', 'host_3'])
+ prev = self._generate_default_service_states()
+ request_spec = self._get_vol_creation_request(num_vols=3, drive_ix=2)
+
+ self.sched.schedule_create_volumes(self.context,
+ request_spec,
+ availability_zone=None)
+
+ self.assertEqual(len(scheduled_volumes), 3)
+ self.assertEqual(scheduled_volumes[0]['vol']['host'], 'host_0')
+ self.assertEqual(scheduled_volumes[1]['vol']['host'], 'host_2')
+ self.assertEqual(scheduled_volumes[2]['vol']['host'], 'host_4')
+
+ cur = self._get_service_states()
+ for host in ['host_0', 'host_2', 'host_4']:
+ cur_dtype = cur[host]['volume']['drive_qos_info']['name_2']
+ prev_dtype = prev[host]['volume']['drive_qos_info']['name_2']
+ self.assertEqual(cur_dtype['DriveType'], prev_dtype['DriveType'])
+ self.assertEqual(cur_dtype['FullDrive']['NumFreeDrives'],
+ prev_dtype['FullDrive']['NumFreeDrives'] - 1)
+ self.assertEqual(cur_dtype['FullDrive']['NumOccupiedDrives'],
+ prev_dtype['FullDrive']['NumOccupiedDrives'] + 1)
+
+ def test_vsa_sched_no_drive_type(self):
+ self._set_service_states(host_num=10,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=1)
+ request_spec = self._get_vol_creation_request(num_vols=1, drive_ix=6)
+ self.assertRaises(driver.WillNotSchedule,
+ self.sched.schedule_create_volumes,
+ self.context,
+ request_spec,
+ availability_zone=None)
+
+ def test_vsa_sched_no_enough_drives(self):
+ global scheduled_volumes
+ scheduled_volumes = []
+
+ self._set_service_states(host_num=3,
+ drive_type_start_ix=0,
+ drive_type_num=1,
+ init_num_drives=0)
+ prev = self._generate_default_service_states()
+ request_spec = self._get_vol_creation_request(num_vols=3, drive_ix=0)
+
+ self.assertRaises(driver.WillNotSchedule,
+ self.sched.schedule_create_volumes,
+ self.context,
+ request_spec,
+ availability_zone=None)
+
+ # check that everything was returned back
+ cur = self._get_service_states()
+ for k, v in prev.iteritems():
+ self.assertEqual(prev[k]['volume']['drive_qos_info'],
+ cur[k]['volume']['drive_qos_info'])
+
+ def test_vsa_sched_wrong_topic(self):
+ self._set_service_states(host_num=1,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=1)
+ states = self._get_service_states()
+ new_states = {}
+ new_states['host_0'] = {'compute': states['host_0']['volume']}
+ self.service_states = new_states
+ request_spec = self._get_vol_creation_request(num_vols=1, drive_ix=0)
+
+ self.assertRaises(driver.WillNotSchedule,
+ self.sched.schedule_create_volumes,
+ self.context,
+ request_spec,
+ availability_zone=None)
+
+ def test_vsa_sched_provision_volume(self):
+ global global_volume
+ global_volume = {}
+ self._set_service_states(host_num=1,
+ drive_type_start_ix=0,
+ drive_type_num=1,
+ init_num_drives=1)
+ request_spec = self._get_vol_creation_request(num_vols=1, drive_ix=0)
+
+ self.stubs.UnsetAll()
+ self.stubs.Set(self.sched,
+ '_get_service_states', self._fake_get_service_states)
+ self.stubs.Set(nova.db, 'volume_create', self._fake_volume_create)
+
+ self.sched.schedule_create_volumes(self.context,
+ request_spec,
+ availability_zone=None)
+
+ self.assertEqual(request_spec['volumes'][0]['name'],
+ global_volume['display_name'])
+
+ def test_vsa_sched_no_free_drives(self):
+ self._set_service_states(host_num=1,
+ drive_type_start_ix=0,
+ drive_type_num=1,
+ init_num_drives=1)
+ request_spec = self._get_vol_creation_request(num_vols=1, drive_ix=0)
+
+ self.sched.schedule_create_volumes(self.context,
+ request_spec,
+ availability_zone=None)
+
+ cur = self._get_service_states()
+ cur_dtype = cur['host_0']['volume']['drive_qos_info']['name_0']
+ self.assertEqual(cur_dtype['FullDrive']['NumFreeDrives'], 1)
+
+ new_request = self._get_vol_creation_request(num_vols=1, drive_ix=0)
+
+ self.sched.schedule_create_volumes(self.context,
+ request_spec,
+ availability_zone=None)
+ self._print_service_states()
+
+ self.assertRaises(driver.WillNotSchedule,
+ self.sched.schedule_create_volumes,
+ self.context,
+ new_request,
+ availability_zone=None)
+
+ def test_vsa_sched_forced_host(self):
+ global scheduled_volumes
+ scheduled_volumes = []
+
+ self._set_service_states(host_num=10,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=10)
+
+ request_spec = self._get_vol_creation_request(num_vols=3, drive_ix=2)
+
+ self.assertRaises(exception.HostBinaryNotFound,
+ self.sched.schedule_create_volumes,
+ self.context,
+ request_spec,
+ availability_zone="nova:host_5")
+
+ self.stubs.Set(nova.db,
+ 'service_get_by_args', self._fake_service_get_by_args)
+ self.stubs.Set(self.sched,
+ 'service_is_up', self._fake_service_is_up_False)
+
+ self.assertRaises(driver.WillNotSchedule,
+ self.sched.schedule_create_volumes,
+ self.context,
+ request_spec,
+ availability_zone="nova:host_5")
+
+ self.stubs.Set(self.sched,
+ 'service_is_up', self._fake_service_is_up_True)
+
+ self.sched.schedule_create_volumes(self.context,
+ request_spec,
+ availability_zone="nova:host_5")
+
+ self.assertEqual(len(scheduled_volumes), 3)
+ self.assertEqual(scheduled_volumes[0]['vol']['host'], 'host_5')
+ self.assertEqual(scheduled_volumes[1]['vol']['host'], 'host_5')
+ self.assertEqual(scheduled_volumes[2]['vol']['host'], 'host_5')
+
+ def test_vsa_sched_create_volumes_partition(self):
+ global scheduled_volumes
+ scheduled_volumes = []
+ self._set_service_states(host_num=5,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=1,
+ exclude_host_list=['host_0', 'host_2'])
+ prev = self._generate_default_service_states()
+ request_spec = self._get_vol_creation_request(num_vols=3,
+ drive_ix=3,
+ size=50)
+ self.sched.schedule_create_volumes(self.context,
+ request_spec,
+ availability_zone=None)
+
+ self.assertEqual(len(scheduled_volumes), 3)
+ self.assertEqual(scheduled_volumes[0]['vol']['host'], 'host_1')
+ self.assertEqual(scheduled_volumes[1]['vol']['host'], 'host_3')
+ self.assertEqual(scheduled_volumes[2]['vol']['host'], 'host_4')
+
+ cur = self._get_service_states()
+ for host in ['host_1', 'host_3', 'host_4']:
+ cur_dtype = cur[host]['volume']['drive_qos_info']['name_3']
+ prev_dtype = prev[host]['volume']['drive_qos_info']['name_3']
+
+ self.assertEqual(cur_dtype['DriveType'], prev_dtype['DriveType'])
+ self.assertEqual(cur_dtype['FullDrive']['NumFreeDrives'],
+ prev_dtype['FullDrive']['NumFreeDrives'] - 1)
+ self.assertEqual(cur_dtype['FullDrive']['NumOccupiedDrives'],
+ prev_dtype['FullDrive']['NumOccupiedDrives'] + 1)
+
+ self.assertEqual(prev_dtype['PartitionDrive']
+ ['NumOccupiedPartitions'], 0)
+ self.assertEqual(cur_dtype['PartitionDrive']
+ ['NumOccupiedPartitions'], 1)
+ self.assertEqual(cur_dtype['PartitionDrive']
+ ['NumFreePartitions'], 5)
+
+ self.assertEqual(prev_dtype['PartitionDrive']
+ ['NumFreePartitions'], 0)
+ self.assertEqual(prev_dtype['PartitionDrive']
+ ['PartitionSize'], 0)
+
+ def test_vsa_sched_create_single_volume_az(self):
+ global scheduled_volume
+ scheduled_volume = {}
+
+ def _fake_volume_get_az(context, volume_id):
+ LOG.debug(_("Test: Volume get: id=%(volume_id)s"), locals())
+ return {'id': volume_id, 'availability_zone': 'nova:host_3'}
+
+ self.stubs.Set(nova.db, 'volume_get', _fake_volume_get_az)
+ self.stubs.Set(nova.db,
+ 'service_get_by_args', self._fake_service_get_by_args)
+ self.stubs.Set(self.sched,
+ 'service_is_up', self._fake_service_is_up_True)
+
+ host = self.sched.schedule_create_volume(self.context,
+ 123, availability_zone=None)
+
+ self.assertEqual(host, 'host_3')
+ self.assertEqual(scheduled_volume['id'], 123)
+ self.assertEqual(scheduled_volume['host'], 'host_3')
+
+ def test_vsa_sched_create_single_non_vsa_volume(self):
+ global scheduled_volume
+ scheduled_volume = {}
+
+ global global_volume
+ global_volume = {}
+ global_volume['drive_type'] = None
+
+ self.assertRaises(driver.NoValidHost,
+ self.sched.schedule_create_volume,
+ self.context,
+ 123,
+ availability_zone=None)
+
+ def test_vsa_sched_create_single_volume(self):
+ global scheduled_volume
+ scheduled_volume = {}
+ self._set_service_states(host_num=10,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=10,
+ exclude_host_list=['host_0', 'host_1'])
+ prev = self._generate_default_service_states()
+
+ global global_volume
+ global_volume = {}
+
+ drive_ix = 2
+ drive_type = {'id': drive_ix,
+ 'name': 'name_' + str(drive_ix),
+ 'type': 'type_' + str(drive_ix),
+ 'size_gb': 1 + 100 * (drive_ix)}
+
+ global_volume['drive_type'] = drive_type
+ global_volume['size'] = 0
+
+ host = self.sched.schedule_create_volume(self.context,
+ 123, availability_zone=None)
+
+ self.assertEqual(host, 'host_2')
+ self.assertEqual(scheduled_volume['id'], 123)
+ self.assertEqual(scheduled_volume['host'], 'host_2')
+
+
+class VsaSchedulerTestCaseMostAvail(VsaSchedulerTestCase):
+
+ def setUp(self):
+ super(VsaSchedulerTestCaseMostAvail, self).setUp(
+ FakeVsaMostAvailCapacityScheduler())
+
+ def tearDown(self):
+ self.stubs.UnsetAll()
+ super(VsaSchedulerTestCaseMostAvail, self).tearDown()
+
+ def test_vsa_sched_create_single_volume(self):
+ global scheduled_volume
+ scheduled_volume = {}
+ self._set_service_states(host_num=10,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=10,
+ exclude_host_list=['host_0', 'host_1'])
+ prev = self._generate_default_service_states()
+
+ global global_volume
+ global_volume = {}
+
+ drive_ix = 2
+ drive_type = {'id': drive_ix,
+ 'name': 'name_' + str(drive_ix),
+ 'type': 'type_' + str(drive_ix),
+ 'size_gb': 1 + 100 * (drive_ix)}
+
+ global_volume['drive_type'] = drive_type
+ global_volume['size'] = 0
+
+ host = self.sched.schedule_create_volume(self.context,
+ 123, availability_zone=None)
+
+ self.assertEqual(host, 'host_9')
+ self.assertEqual(scheduled_volume['id'], 123)
+ self.assertEqual(scheduled_volume['host'], 'host_9')
+
+ def test_vsa_sched_create_volumes_simple(self):
+ global scheduled_volumes
+ scheduled_volumes = []
+ self._set_service_states(host_num=10,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=10,
+ exclude_host_list=['host_1', 'host_3'])
+ prev = self._generate_default_service_states()
+ request_spec = self._get_vol_creation_request(num_vols=3, drive_ix=2)
+
+ self._print_service_states()
+
+ self.sched.schedule_create_volumes(self.context,
+ request_spec,
+ availability_zone=None)
+
+ self.assertEqual(len(scheduled_volumes), 3)
+ self.assertEqual(scheduled_volumes[0]['vol']['host'], 'host_9')
+ self.assertEqual(scheduled_volumes[1]['vol']['host'], 'host_8')
+ self.assertEqual(scheduled_volumes[2]['vol']['host'], 'host_7')
+
+ cur = self._get_service_states()
+ for host in ['host_9', 'host_8', 'host_7']:
+ cur_dtype = cur[host]['volume']['drive_qos_info']['name_2']
+ prev_dtype = prev[host]['volume']['drive_qos_info']['name_2']
+ self.assertEqual(cur_dtype['DriveType'], prev_dtype['DriveType'])
+ self.assertEqual(cur_dtype['FullDrive']['NumFreeDrives'],
+ prev_dtype['FullDrive']['NumFreeDrives'] - 1)
+ self.assertEqual(cur_dtype['FullDrive']['NumOccupiedDrives'],
+ prev_dtype['FullDrive']['NumOccupiedDrives'] + 1)
+
+ def test_vsa_sched_create_volumes_partition(self):
+ global scheduled_volumes
+ scheduled_volumes = []
+ self._set_service_states(host_num=5,
+ drive_type_start_ix=0,
+ drive_type_num=5,
+ init_num_drives=1,
+ exclude_host_list=['host_0', 'host_2'])
+ prev = self._generate_default_service_states()
+ request_spec = self._get_vol_creation_request(num_vols=3,
+ drive_ix=3,
+ size=50)
+ self.sched.schedule_create_volumes(self.context,
+ request_spec,
+ availability_zone=None)
+
+ self.assertEqual(len(scheduled_volumes), 3)
+ self.assertEqual(scheduled_volumes[0]['vol']['host'], 'host_4')
+ self.assertEqual(scheduled_volumes[1]['vol']['host'], 'host_3')
+ self.assertEqual(scheduled_volumes[2]['vol']['host'], 'host_1')
+
+ cur = self._get_service_states()
+ for host in ['host_1', 'host_3', 'host_4']:
+ cur_dtype = cur[host]['volume']['drive_qos_info']['name_3']
+ prev_dtype = prev[host]['volume']['drive_qos_info']['name_3']
+
+ self.assertEqual(cur_dtype['DriveType'], prev_dtype['DriveType'])
+ self.assertEqual(cur_dtype['FullDrive']['NumFreeDrives'],
+ prev_dtype['FullDrive']['NumFreeDrives'] - 1)
+ self.assertEqual(cur_dtype['FullDrive']['NumOccupiedDrives'],
+ prev_dtype['FullDrive']['NumOccupiedDrives'] + 1)
+
+ self.assertEqual(prev_dtype['PartitionDrive']
+ ['NumOccupiedPartitions'], 0)
+ self.assertEqual(cur_dtype['PartitionDrive']
+ ['NumOccupiedPartitions'], 1)
+ self.assertEqual(cur_dtype['PartitionDrive']
+ ['NumFreePartitions'], 5)
+ self.assertEqual(prev_dtype['PartitionDrive']
+ ['NumFreePartitions'], 0)
+ self.assertEqual(prev_dtype['PartitionDrive']
+ ['PartitionSize'], 0)
diff --git a/nova/tests/test_vsa.py b/nova/tests/test_vsa.py
index 8e4d58960..cff23a800 100644
--- a/nova/tests/test_vsa.py
+++ b/nova/tests/test_vsa.py
@@ -22,6 +22,7 @@ from xml.etree.ElementTree import Element, SubElement
from nova import exception
from nova import flags
from nova import vsa
+from nova import volume
from nova import db
from nova import context
from nova import test
@@ -50,6 +51,7 @@ class VsaTestCase(test.TestCase):
super(VsaTestCase, self).setUp()
self.stubs = stubout.StubOutForTesting()
self.vsa_api = vsa.API()
+ self.volume_api = volume.API()
self.context_non_admin = context.RequestContext(None, None)
self.context = context.get_admin_context()
diff --git a/nova/tests/test_vsa_volumes.py b/nova/tests/test_vsa_volumes.py
index e1d4cd756..d451a4377 100644
--- a/nova/tests/test_vsa_volumes.py
+++ b/nova/tests/test_vsa_volumes.py
@@ -61,7 +61,8 @@ class VsaVolumesTestCase(test.TestCase):
self.vsa_id = vsa_ref['id']
def tearDown(self):
- self.vsa_api.delete(self.context, self.vsa_id)
+ if self.vsa_id:
+ self.vsa_api.delete(self.context, self.vsa_id)
self.stubs.UnsetAll()
super(VsaVolumesTestCase, self).tearDown()
@@ -106,3 +107,23 @@ class VsaVolumesTestCase(test.TestCase):
self.volume_api.update(self.context,
volume_ref['id'], {'status': 'error'})
self.volume_api.delete(self.context, volume_ref['id'])
+
+ def test_vsa_volume_delete_vsa_with_volumes(self):
+ """ Check volume deleton in different states. """
+
+ vols1 = self.volume_api.get_all_by_vsa(self.context,
+ self.vsa_id, "from")
+ for i in range(3):
+ volume_param = _default_volume_param()
+ volume_param['from_vsa_id'] = self.vsa_id
+ volume_ref = self.volume_api.create(self.context, **volume_param)
+
+ vols2 = self.volume_api.get_all_by_vsa(self.context,
+ self.vsa_id, "from")
+ self.assertEqual(len(vols1) + 3, len(vols2))
+
+ self.vsa_api.delete(self.context, self.vsa_id)
+
+ vols3 = self.volume_api.get_all_by_vsa(self.context,
+ self.vsa_id, "from")
+ self.assertEqual(len(vols1), len(vols3))
diff --git a/nova/volume/driver.py b/nova/volume/driver.py
index 2e3da57b2..98d115088 100644
--- a/nova/volume/driver.py
+++ b/nova/volume/driver.py
@@ -507,7 +507,7 @@ class ISCSIDriver(VolumeDriver):
iscsi_properties = self._get_iscsi_properties(volume)
if not iscsi_properties['target_discovered']:
- self._run_iscsiadm(iscsi_properties, ('--op', 'new'))
+ self._run_iscsiadm(iscsi_properties, '--op=new')
if iscsi_properties.get('auth_method'):
self._iscsiadm_update(iscsi_properties,
@@ -559,7 +559,7 @@ class ISCSIDriver(VolumeDriver):
iscsi_properties = self._get_iscsi_properties(volume)
self._iscsiadm_update(iscsi_properties, "node.startup", "manual")
self._run_iscsiadm(iscsi_properties, "--logout")
- self._run_iscsiadm(iscsi_properties, ('--op', 'delete'))
+ self._run_iscsiadm(iscsi_properties, '--op=delete')
def check_for_export(self, context, volume_id):
"""Make sure volume is exported."""
diff --git a/nova/vsa/api.py b/nova/vsa/api.py
index 9b2750d82..39f7d1431 100644
--- a/nova/vsa/api.py
+++ b/nova/vsa/api.py
@@ -312,9 +312,8 @@ class API(base.Base):
def _force_volume_delete(self, ctxt, volume):
"""Delete a volume, bypassing the check that it must be available."""
host = volume['host']
-
- if not host:
- # Volume not yet assigned to host
+ if not host or volume['from_vsa_id']:
+ # Volume not yet assigned to host OR FE volume
# Deleting volume from database and skipping rpc.
self.db.volume_destroy(ctxt, volume['id'])
return
@@ -324,41 +323,33 @@ class API(base.Base):
{"method": "delete_volume",
"args": {"volume_id": volume['id']}})
- def delete_be_volumes(self, context, vsa_id, force_delete=True):
+ def delete_vsa_volumes(self, context, vsa_id, direction,
+ force_delete=True):
+ if direction == "FE":
+ volumes = self.db.volume_get_all_assigned_from_vsa(context, vsa_id)
+ else:
+ volumes = self.db.volume_get_all_assigned_to_vsa(context, vsa_id)
- be_volumes = self.db.volume_get_all_assigned_to_vsa(context, vsa_id)
- for volume in be_volumes:
+ for volume in volumes:
try:
vol_name = volume['name']
- LOG.info(_("VSA ID %(vsa_id)s: Deleting BE volume "\
- "%(vol_name)s"), locals())
+ LOG.info(_("VSA ID %(vsa_id)s: Deleting %(direction)s "\
+ "volume %(vol_name)s"), locals())
self.volume_api.delete(context, volume['id'])
except exception.ApiError:
LOG.info(_("Unable to delete volume %s"), volume['name'])
if force_delete:
- LOG.info(_("VSA ID %(vsa_id)s: Forced delete. BE volume "\
- "%(vol_name)s"), locals())
+ LOG.info(_("VSA ID %(vsa_id)s: Forced delete. "\
+ "%(direction)s volume %(vol_name)s"), locals())
self._force_volume_delete(context, volume)
def delete(self, context, vsa_id):
"""Terminate a VSA instance."""
LOG.info(_("Going to try to terminate VSA ID %s"), vsa_id)
- # allow deletion of volumes in "abnormal" state
-
- # Delete all FE volumes
- fe_volumes = self.db.volume_get_all_assigned_from_vsa(context, vsa_id)
- for volume in fe_volumes:
- try:
- vol_name = volume['name']
- LOG.info(_("VSA ID %(vsa_id)s: Deleting FE volume "\
- "%(vol_name)s"), locals())
- self.volume_api.delete(context, volume['id'])
- except exception.ApiError:
- LOG.info(_("Unable to delete volume %s"), volume['name'])
-
- # Delete all BE volumes
- self.delete_be_volumes(context, vsa_id, force_delete=True)
+ # Delete all FrontEnd and BackEnd volumes
+ self.delete_vsa_volumes(context, vsa_id, "FE", force_delete=True)
+ self.delete_vsa_volumes(context, vsa_id, "BE", force_delete=True)
# Delete all VC instances
instances = self.db.instance_get_all_by_vsa(context, vsa_id)
diff --git a/nova/vsa/manager.py b/nova/vsa/manager.py
index 1390f8146..e963d26c5 100644
--- a/nova/vsa/manager.py
+++ b/nova/vsa/manager.py
@@ -145,7 +145,7 @@ class VsaManager(manager.SchedulerDependentManager):
if has_failed_volumes:
LOG.info(_("VSA ID %(vsa_id)d: Delete all BE volumes"), locals())
- self.vsa_api.delete_be_volumes(context, vsa_id, force_delete=True)
+ self.vsa_api.delete_vsa_volumes(context, vsa_id, "BE", True)
self.vsa_api.update_vsa_status(context, vsa_id, VsaState.FAILED)
return