From ca3ea13c009480eac02c878fed6299595efa3264 Mon Sep 17 00:00:00 2001 From: "vladimir.p" Date: Thu, 25 Aug 2011 18:38:35 -0700 Subject: [PATCH] VSA code redesign. Drive types completely replaced by Volume types --- bin/nova-manage | 214 ++++++++++++--------- bin/nova-vsa | 10 +- nova/exception.py | 12 -- nova/log.py | 2 +- nova/quota.py | 5 +- nova/scheduler/vsa.py | 68 ++++--- nova/tests/scheduler/test_vsa_scheduler.py | 64 ++++-- nova/tests/test_drive_types.py | 146 -------------- nova/tests/test_vsa.py | 49 +++-- nova/tests/test_vsa_volumes.py | 77 ++++---- nova/tests/test_xenapi.py | 1 + nova/vsa/api.py | 148 ++++++-------- nova/vsa/drive_types.py | 114 ----------- nova/vsa/fake.py | 2 +- nova/vsa/manager.py | 52 ++--- nova/vsa/utils.py | 80 ++++++++ 16 files changed, 461 insertions(+), 583 deletions(-) delete mode 100644 nova/tests/test_drive_types.py delete mode 100644 nova/vsa/drive_types.py create mode 100644 nova/vsa/utils.py diff --git a/bin/nova-manage b/bin/nova-manage index bd2d43139..977ad5c66 100755 --- a/bin/nova-manage +++ b/bin/nova-manage @@ -53,6 +53,7 @@ CLI interface for nova management. """ +import ast import gettext import glob import json @@ -64,8 +65,6 @@ import time from optparse import OptionParser -import ast - # If ../nova/__init__.py exists, add ../ to Python search path, so that # it will override what happens to be installed in /usr/(local/)lib/python... POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), @@ -87,15 +86,13 @@ from nova import quota from nova import rpc from nova import utils from nova import version +from nova import vsa from nova.api.ec2 import ec2utils from nova.auth import manager from nova.cloudpipe import pipelib from nova.compute import instance_types from nova.db import migration -from nova import compute -from nova import volume -from nova import vsa -from nova.vsa import drive_types +from nova.volume import volume_types FLAGS = flags.FLAGS flags.DECLARE('fixed_range', 'nova.network.manager') @@ -1076,14 +1073,12 @@ class VsaCommands(object): def __init__(self, *args, **kwargs): self.manager = manager.AuthManager() self.vsa_api = vsa.API() - self.compute_api = compute.API() - self.volume_api = volume.API() self.context = context.get_admin_context() self._format_str_vsa = "%-5s %-15s %-25s %-10s %-6s "\ "%-9s %-10s %-10s %10s" self._format_str_volume = "\t%-4s %-15s %-5s %-10s %-20s %s" - self._format_str_drive = "\t%-4s %-15s %-5s %-10s %-20s %s" + self._format_str_drive = "\t%-4s %-15s %-5s %-10s %-20s %-4s %-10s %s" self._format_str_instance = "\t%-4s %-10s %-20s %-12s %-10s "\ "%-15s %-15s %-10s %-15s %s" @@ -1124,7 +1119,7 @@ class VsaCommands(object): def _print_volume(self, vol): print self._format_str_volume %\ (vol['id'], - vol['display_name'], + vol['display_name'] or vol['name'], vol['size'], vol['status'], vol['attach_status'], @@ -1138,15 +1133,24 @@ class VsaCommands(object): _('size'), _('status'), _('host'), + _('type'), + _('typeName'), _('createTime')) def _print_drive(self, drive): - print self._format_str_volume %\ + if drive['volume_type_id'] is not None and drive.get('volume_type'): + drive_type_name = drive['volume_type'].get('name') + else: + drive_type_name = '' + + print self._format_str_drive %\ (drive['id'], drive['display_name'], drive['size'], drive['status'], drive['host'], + drive['volume_type_id'], + drive_type_name, str(drive['created_at'])) def _print_instance_header(self): @@ -1196,9 +1200,7 @@ class VsaCommands(object): vsa_id = vsa.get('id') if print_instances: - instances = self.compute_api.get_all(context, - search_opts={'metadata': - dict(vsa_id=str(vsa_id))}) + instances = self.vsa_api.get_all_vsa_instances(context, vsa_id) if instances: print self._print_instance_header() @@ -1207,8 +1209,7 @@ class VsaCommands(object): print if print_drives: - drives = self.volume_api.get_all_by_vsa(context, - vsa_id, "to") + drives = self.vsa_api.get_all_vsa_drives(context, vsa_id) if drives: self._print_drive_header() for drive in drives: @@ -1216,8 +1217,7 @@ class VsaCommands(object): print if print_volumes: - volumes = self.volume_api.get_all_by_vsa(context, - vsa_id, "from") + volumes = self.vsa_api.get_all_vsa_volumes(context, vsa_id) if volumes: self._print_volume_header() for volume in volumes: @@ -1344,7 +1344,7 @@ class VsaCommands(object): @args('--id', dest='vsa_id', metavar="", help='VSA ID (optional)') - @args('--all', dest='all', action="store_true", + @args('--all', dest='all', action="store_true", default=False, help='Show all available details') @args('--drives', dest='drives', action="store_true", help='Include drive-level details') @@ -1384,6 +1384,7 @@ class VsaDriveTypeCommands(object): def __init__(self, *args, **kwargs): super(VsaDriveTypeCommands, self).__init__(*args, **kwargs) self.context = context.get_admin_context() + self._drive_type_template = '%s_%sGB_%sRPM' def _list(self, drives): format_str = "%-5s %-30s %-10s %-10s %-10s %-20s %-10s %s" @@ -1398,75 +1399,94 @@ class VsaDriveTypeCommands(object): _('visible'), _('createTime')) - for drive in drives: + for name, vol_type in drives.iteritems(): + drive = vol_type.get('extra_specs') print format_str %\ - (str(drive['id']), - drive['name'], - drive['type'], - str(drive['size_gb']), - drive['rpm'], - drive['capabilities'], - str(drive['visible']), - str(drive['created_at'])) + (str(vol_type['id']), + drive['drive_name'], + drive['drive_type'], + drive['drive_size'], + drive['drive_rpm'], + drive.get('capabilities', ''), + str(drive.get('visible', '')), + str(vol_type['created_at'])) @args('--type', dest='type', metavar="", help='Drive type (SATA, SAS, SSD, etc.)') @args('--size', dest='size_gb', metavar="", help='Drive size in GB') @args('--rpm', dest='rpm', metavar="", help='RPM') - @args('--capabilities', dest='capabilities', metavar="", - help='Different capabilities') - @args('--visible', dest='visible', metavar="", + @args('--capabilities', dest='capabilities', default=None, + metavar="", help='Different capabilities') + @args('--hide', dest='hide', action="store_true", default=False, help='Show or hide drive') @args('--name', dest='name', metavar="", help='Drive name') - def create(self, type, size_gb, rpm, capabilities='', - visible=None, name=None): + def create(self, type, size_gb, rpm, capabilities=None, + hide=False, name=None): """Create drive type.""" - if visible in [None, "--show", "show"]: - visible = True - elif visible in ["--hide", "hide"]: - visible = False + hide = True if hide in [True, "True", "--hide", "hide"] else False + + if name is None: + name = self._drive_type_template % (type, size_gb, rpm) + + extra_specs = {'type': 'vsa_drive', + 'drive_name': name, + 'drive_type': type, + 'drive_size': size_gb, + 'drive_rpm': rpm, + 'visible': True, + } + if hide: + extra_specs['visible'] = False + + if capabilities is not None and capabilities != '': + extra_specs['capabilities'] = capabilities + + volume_types.create(self.context, name, extra_specs) + result = volume_types.get_volume_type_by_name(self.context, name) + self._list({name: result}) + + @args('--name', dest='name', metavar="", help='Drive name') + @args('--purge', action="store_true", dest='purge', default=False, + help='purge record from database') + def delete(self, name, purge): + """Marks instance types / flavors as deleted""" + try: + if purge: + volume_types.purge(self.context, name) + verb = "purged" + else: + volume_types.destroy(self.context, name) + verb = "deleted" + except exception.ApiError: + print "Valid volume type name is required" + sys.exit(1) + except exception.DBError, e: + print "DB Error: %s" % e + sys.exit(2) + except: + sys.exit(3) else: - raise ValueError(_('Visible parameter should be set to --show '\ - 'or --hide')) + print "%s %s" % (name, verb) - result = drive_types.create(self.context, - type, int(size_gb), rpm, - capabilities, visible, name) - self._list([result]) - - @args('--name', dest='name', metavar="", help='Drive name') - def delete(self, name): - """Delete drive type.""" - - dtype = drive_types.get_by_name(self.context, name) - drive_types.delete(self.context, dtype['id']) - - @args('--name', dest='name', metavar="", help='Drive name') - @args('--new_name', dest='new_name', metavar="", - help='New Drive name (optional)') - def rename(self, name, new_name=None): - """Rename drive type.""" - - dtype = drive_types.rename(self.context, - name, new_name) - self._list([dtype]) - - @args('--all', dest='visible', action="store_false", - help='Show all drives') + @args('--all', dest='all', action="store_true", default=False, + help='Show all drives (including invisible)') @args('--name', dest='name', metavar="", help='Show only specified drive') - def list(self, visible=None, name=None): + def list(self, all=False, name=None): """Describe all available VSA drive types (or particular one).""" - visible = False if visible in ["--all", False] else True + all = False if all in ["--all", False, "False"] else True + search_opts = {'extra_specs': {'type': 'vsa_drive'}} if name is not None: - drive = drive_types.get_by_name(self.context, name) - drives = [drive] - else: - drives = drive_types.get_all(self.context, visible) + search_opts['extra_specs']['name'] = name + if all == False: + search_opts['extra_specs']['visible'] = '1' + + drives = volume_types.get_all_types(self.context, + search_opts=search_opts) self._list(drives) @args('--name', dest='name', metavar="", help='Drive name') @@ -1474,32 +1494,44 @@ class VsaDriveTypeCommands(object): help='Drive type (SATA, SAS, SSD, etc.)') @args('--size', dest='size_gb', metavar="", help='Drive size in GB') @args('--rpm', dest='rpm', metavar="", help='RPM') - @args('--capabilities', dest='capabilities', metavar="", - help='Different capabilities') - @args('--visible', dest='visible', metavar="", - help='Show or hide drive') + @args('--capabilities', dest='capabilities', default=None, + metavar="", help='Different capabilities') + @args('--visible', dest='visible', + metavar="", help='Show or hide drive') def update(self, name, type=None, size_gb=None, rpm=None, - capabilities='', visible=None): + capabilities=None, visible=None): """Update drive type.""" - values = { - 'type': type, - 'size_gb': size_gb, - 'rpm': rpm, - 'capabilities': capabilities, - } - if visible: - if visible in ["--show", "show"]: - values['visible'] = True - elif visible in ["--hide", "hide"]: - values['visible'] = False - else: - raise ValueError(_("Visible parameter should be set to "\ - "--show or --hide")) + volume_type = volume_types.get_volume_type_by_name(self.context, name) - dtype = drive_types.get_by_name(self.context, name) - dtype = drive_types.update(self.context, dtype['id'], **values) - self._list([dtype]) + extra_specs = {'type': 'vsa_drive'} + + if type: + extra_specs['drive_type'] = type + + if size_gb: + extra_specs['drive_size'] = size_gb + + if rpm: + extra_specs['drive_rpm'] = rpm + + if capabilities: + extra_specs['capabilities'] = capabilities + + if visible is not None: + if visible in ["show", True, "True"]: + extra_specs['visible'] = True + elif visible in ["hide", False, "False"]: + extra_specs['visible'] = False + else: + raise ValueError(_('visible parameter should be set to '\ + 'show or hide')) + + db.api.volume_type_extra_specs_update_or_create(self.context, + volume_type['id'], + extra_specs) + result = volume_types.get_volume_type_by_name(self.context, name) + self._list({name: result}) class VolumeCommands(object): diff --git a/bin/nova-vsa b/bin/nova-vsa index d765e8f9e..2d6eee2c0 100755 --- a/bin/nova-vsa +++ b/bin/nova-vsa @@ -4,6 +4,7 @@ # Copyright (c) 2011 Zadara Storage Inc. # Copyright (c) 2011 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 @@ -17,6 +18,10 @@ # under the License. """Starter script for Nova VSA.""" + +import eventlet +eventlet.monkey_patch() + import os import sys @@ -28,6 +33,7 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]), if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')): sys.path.insert(0, possible_topdir) + from nova import flags from nova import log as logging from nova import service @@ -37,5 +43,7 @@ if __name__ == '__main__': utils.default_flagfile() flags.FLAGS(sys.argv) logging.setup() - service.serve() + utils.monkey_patch() + server = service.Service.create(binary='nova-vsa') + service.serve(server) service.wait() diff --git a/nova/exception.py b/nova/exception.py index f75d0b832..32981f4d5 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -365,10 +365,6 @@ class VolumeTypeExtraSpecsNotFound(NotFound): "key %(extra_specs_key)s.") -class VolumeNotFoundForVsa(VolumeNotFound): - message = _("Volume not found for vsa %(vsa_id)s.") - - class SnapshotNotFound(NotFound): message = _("Snapshot %(snapshot_id)s could not be found.") @@ -799,14 +795,6 @@ class VirtualStorageArrayNotFoundByName(NotFound): message = _("Virtual Storage Array %(name)s could not be found.") -class VirtualDiskTypeNotFound(NotFound): - message = _("Drive Type %(id)d could not be found.") - - -class VirtualDiskTypeNotFoundByName(NotFound): - message = _("Drive Type %(name)s could not be found.") - - class CannotResizeToSameSize(NovaException): message = _("When resizing, instances must change size!") diff --git a/nova/log.py b/nova/log.py index 3b86d78e8..eb0b6020f 100644 --- a/nova/log.py +++ b/nova/log.py @@ -32,6 +32,7 @@ import json import logging import logging.handlers import os +import stat import sys import traceback @@ -258,7 +259,6 @@ class NovaRootLogger(NovaLogger): self.addHandler(self.filelog) self.logpath = logpath - import stat st = os.stat(self.logpath) if st.st_mode != (stat.S_IFREG | FLAGS.logfile_mode): os.chmod(self.logpath, FLAGS.logfile_mode) diff --git a/nova/quota.py b/nova/quota.py index 48e598659..771477747 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -116,8 +116,9 @@ def allowed_volumes(context, requested_volumes, size): allowed_gigabytes = _get_request_allotment(requested_gigabytes, used_gigabytes, quota['gigabytes']) - allowed_volumes = min(allowed_volumes, - int(allowed_gigabytes // size)) + if size != 0: + allowed_volumes = min(allowed_volumes, + int(allowed_gigabytes // size)) return min(requested_volumes, allowed_volumes) diff --git a/nova/scheduler/vsa.py b/nova/scheduler/vsa.py index 218ad5c7b..ad5ebc2dc 100644 --- a/nova/scheduler/vsa.py +++ b/nova/scheduler/vsa.py @@ -20,15 +20,15 @@ VSA Simple Scheduler """ from nova import context -from nova import rpc from nova import db from nova import flags +from nova import log as logging +from nova import rpc from nova import utils -from nova.vsa.api import VsaState -from nova.volume import api as volume_api from nova.scheduler import driver from nova.scheduler import simple -from nova import log as logging +from nova.vsa.api import VsaState +from nova.volume import volume_types LOG = logging.getLogger('nova.scheduler.vsa') @@ -67,21 +67,21 @@ class VsaScheduler(simple.SimpleScheduler): def _compare_names(str1, str2): return str1.lower() == str2.lower() - def _compare_sizes_approxim(cap_capacity, size_gb): + def _compare_sizes_approxim(cap_capacity, size): cap_capacity = BYTES_TO_GB(int(cap_capacity)) - size_gb = int(size_gb) - size_perc = size_gb * \ + size = int(size) + size_perc = size * \ FLAGS.drive_type_approx_capacity_percent / 100 - return cap_capacity >= size_gb - size_perc and \ - cap_capacity <= size_gb + size_perc + return cap_capacity >= size - size_perc and \ + cap_capacity <= size + size_perc # Add more entries for additional comparisons compare_list = [{'cap1': 'DriveType', 'cap2': 'type', 'cmp_func': _compare_names}, {'cap1': 'DriveCapacity', - 'cap2': 'size_gb', + 'cap2': 'size', 'cmp_func': _compare_sizes_approxim}] for cap in compare_list: @@ -193,8 +193,8 @@ class VsaScheduler(simple.SimpleScheduler): 'attach_status': "detached", 'display_name': vol['name'], 'display_description': vol['description'], - 'to_vsa_id': vsa_id, - 'drive_type_id': vol['drive_ref']['id'], + 'volume_type_id': vol['volume_type_id'], + 'metadata': dict(to_vsa_id=vsa_id), 'host': vol['host'], 'scheduled_at': now } @@ -228,7 +228,8 @@ class VsaScheduler(simple.SimpleScheduler): def _assign_hosts_to_volumes(self, context, volume_params, forced_host): - prev_drive_type_id = None + prev_volume_type_id = None + request_spec = {} selected_hosts = [] LOG.debug(_("volume_params %(volume_params)s") % locals()) @@ -244,14 +245,25 @@ class VsaScheduler(simple.SimpleScheduler): vol['capabilities'] = None continue - drive_type = vol['drive_ref'] - request_spec = {'size': vol['size'], - 'drive_type': dict(drive_type)} + volume_type_id = vol['volume_type_id'] + request_spec['size'] = vol['size'] - if prev_drive_type_id != drive_type['id']: + if prev_volume_type_id is None or\ + prev_volume_type_id != volume_type_id: # generate list of hosts for this drive type + + volume_type = volume_types.get_volume_type(context, + volume_type_id) + drive_type = { + 'name': volume_type['extra_specs'].get('drive_name'), + 'type': volume_type['extra_specs'].get('drive_type'), + 'size': int(volume_type['extra_specs'].get('drive_size')), + 'rpm': volume_type['extra_specs'].get('drive_rpm'), + } + request_spec['drive_type'] = drive_type + all_hosts = self._filter_hosts("volume", request_spec) - prev_drive_type_id = drive_type['id'] + prev_volume_type_id = volume_type_id (host, qos_cap) = self._select_hosts(request_spec, all_hosts, selected_hosts) @@ -279,8 +291,7 @@ class VsaScheduler(simple.SimpleScheduler): self._provision_volume(context, vol, vsa_id, availability_zone) except: if vsa_id: - db.vsa_update(context, vsa_id, - dict(status=VsaState.FAILED)) + db.vsa_update(context, vsa_id, dict(status=VsaState.FAILED)) for vol in volume_params: if 'capabilities' in vol: @@ -302,12 +313,23 @@ class VsaScheduler(simple.SimpleScheduler): 'scheduled_at': now}) return host - drive_type = volume_ref['drive_type'] - if drive_type is None: + volume_type_id = volume_ref['volume_type_id'] + if volume_type_id: + volume_type = volume_types.get_volume_type(context, volume_type_id) + + if volume_type_id is None or\ + volume_types.is_vsa_volume(volume_type_id, volume_type): + LOG.debug(_("Non-VSA volume %d"), volume_ref['id']) return super(VsaScheduler, self).schedule_create_volume(context, volume_id, *_args, **_kwargs) - drive_type = dict(drive_type) + + drive_type = { + 'name': volume_type['extra_specs'].get('drive_name'), + 'type': volume_type['extra_specs'].get('drive_type'), + 'size': int(volume_type['extra_specs'].get('drive_size')), + 'rpm': volume_type['extra_specs'].get('drive_rpm'), + } LOG.debug(_("Spawning volume %(volume_id)s with drive type "\ "%(drive_type)s"), locals()) diff --git a/nova/tests/scheduler/test_vsa_scheduler.py b/nova/tests/scheduler/test_vsa_scheduler.py index 697ad3842..309db96a2 100644 --- a/nova/tests/scheduler/test_vsa_scheduler.py +++ b/nova/tests/scheduler/test_vsa_scheduler.py @@ -16,13 +16,15 @@ import stubout import nova + +from nova import context +from nova import db from nova import exception from nova import flags -from nova import db -from nova import context +from nova import log as logging from nova import test from nova import utils -from nova import log as logging +from nova.volume import volume_types from nova.scheduler import vsa as vsa_sched from nova.scheduler import driver @@ -52,15 +54,26 @@ 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)} + + name = 'name_' + str(i) + try: + volume_types.create(self.context, name, + extra_specs={'type': 'vsa_drive', + 'drive_name': name, + 'drive_type': 'type_' + str(drive_ix), + 'drive_size': 1 + 100 * (drive_ix)}) + self.created_types_lst.append(name) + except exception.ApiError: + # type is already created + pass + + volume_type = volume_types.get_volume_type_by_name(self.context, + name) volume = {'size': size, 'snapshot_id': None, 'name': 'vol_' + str(i), 'description': None, - 'drive_ref': drive_type} + 'volume_type_id': volume_type['id']} volume_params.append(volume) return {'num_volumes': len(volume_params), @@ -217,7 +230,12 @@ class VsaSchedulerTestCase(test.TestCase): self.stubs.Set(nova.db, 'volume_get', self._fake_volume_get) self.stubs.Set(nova.db, 'volume_update', self._fake_volume_update) + self.created_types_lst = [] + def tearDown(self): + for name in self.created_types_lst: + volume_types.purge(self.context, name) + self.stubs.UnsetAll() super(VsaSchedulerTestCase, self).tearDown() @@ -463,7 +481,7 @@ class VsaSchedulerTestCase(test.TestCase): global global_volume global_volume = {} - global_volume['drive_type'] = None + global_volume['volume_type_id'] = None self.assertRaises(driver.NoValidHost, self.sched.schedule_create_volume, @@ -485,12 +503,16 @@ class VsaSchedulerTestCase(test.TestCase): 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)} + name = 'name_' + str(drive_ix) + volume_types.create(self.context, name, + extra_specs={'type': 'vsa_drive', + 'drive_name': name, + 'drive_type': 'type_' + str(drive_ix), + 'drive_size': 1 + 100 * (drive_ix)}) + self.created_types_lst.append(name) + volume_type = volume_types.get_volume_type_by_name(self.context, name) - global_volume['drive_type'] = drive_type + global_volume['volume_type_id'] = volume_type['id'] global_volume['size'] = 0 host = self.sched.schedule_create_volume(self.context, @@ -525,12 +547,16 @@ class VsaSchedulerTestCaseMostAvail(VsaSchedulerTestCase): 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)} + name = 'name_' + str(drive_ix) + volume_types.create(self.context, name, + extra_specs={'type': 'vsa_drive', + 'drive_name': name, + 'drive_type': 'type_' + str(drive_ix), + 'drive_size': 1 + 100 * (drive_ix)}) + self.created_types_lst.append(name) + volume_type = volume_types.get_volume_type_by_name(self.context, name) - global_volume['drive_type'] = drive_type + global_volume['volume_type_id'] = volume_type['id'] global_volume['size'] = 0 host = self.sched.schedule_create_volume(self.context, diff --git a/nova/tests/test_drive_types.py b/nova/tests/test_drive_types.py deleted file mode 100644 index b52e6705b..000000000 --- a/nova/tests/test_drive_types.py +++ /dev/null @@ -1,146 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Zadara Storage Inc. -# Copyright (c) 2011 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. - -""" -Unit Tests for drive types codecode -""" -import time - -from nova import context -from nova import flags -from nova import log as logging -from nova import test -from nova.vsa import drive_types - -FLAGS = flags.FLAGS -LOG = logging.getLogger('nova.tests.test_drive_types') - - -class DriveTypesTestCase(test.TestCase): - """Test cases for driver types code""" - def setUp(self): - super(DriveTypesTestCase, self).setUp() - self.ctxt = context.RequestContext(None, None) - self.ctxt_admin = context.get_admin_context() - self._dtype = self._create_default_drive_type() - - def tearDown(self): - self._dtype = None - - def _create_default_drive_type(self): - """Create a volume object.""" - dtype = {} - dtype['type'] = 'SATA' - dtype['size_gb'] = 150 - dtype['rpm'] = 5000 - dtype['capabilities'] = None - dtype['visible'] = True - - LOG.debug(_("Default values for Drive Type: %s"), dtype) - return dtype - - def test_drive_type_create_delete(self): - dtype = self._dtype - prev_all_dtypes = drive_types.get_all(self.ctxt_admin, False) - - new = drive_types.create(self.ctxt_admin, **dtype) - for k, v in dtype.iteritems(): - self.assertEqual(v, new[k], 'one of fields doesnt match') - - new_all_dtypes = drive_types.get_all(self.ctxt_admin, False) - self.assertNotEqual(len(prev_all_dtypes), - len(new_all_dtypes), - 'drive type was not created') - - drive_types.delete(self.ctxt_admin, new['id']) - new_all_dtypes = drive_types.get_all(self.ctxt_admin, False) - self.assertEqual(prev_all_dtypes, - new_all_dtypes, - 'drive types was not deleted') - - def test_drive_type_check_name_generation(self): - dtype = self._dtype - new = drive_types.create(self.ctxt_admin, **dtype) - expected_name = FLAGS.drive_type_template_short % \ - (dtype['type'], dtype['size_gb'], dtype['rpm']) - self.assertEqual(new['name'], expected_name, - 'name was not generated correctly') - - dtype['capabilities'] = 'SEC' - new2 = drive_types.create(self.ctxt_admin, **dtype) - expected_name = FLAGS.drive_type_template_long % \ - (dtype['type'], dtype['size_gb'], dtype['rpm'], - dtype['capabilities']) - self.assertEqual(new2['name'], expected_name, - 'name was not generated correctly') - - drive_types.delete(self.ctxt_admin, new['id']) - drive_types.delete(self.ctxt_admin, new2['id']) - - def test_drive_type_create_delete_invisible(self): - dtype = self._dtype - dtype['visible'] = False - prev_all_dtypes = drive_types.get_all(self.ctxt_admin, True) - new = drive_types.create(self.ctxt_admin, **dtype) - - new_all_dtypes = drive_types.get_all(self.ctxt_admin, True) - self.assertEqual(prev_all_dtypes, new_all_dtypes) - - new_all_dtypes = drive_types.get_all(self.ctxt_admin, False) - self.assertNotEqual(prev_all_dtypes, new_all_dtypes) - - drive_types.delete(self.ctxt_admin, new['id']) - - def test_drive_type_rename_update(self): - dtype = self._dtype - dtype['capabilities'] = None - - new = drive_types.create(self.ctxt_admin, **dtype) - for k, v in dtype.iteritems(): - self.assertEqual(v, new[k], 'one of fields doesnt match') - - new_name = 'NEW_DRIVE_NAME' - new = drive_types.rename(self.ctxt_admin, new['name'], new_name) - self.assertEqual(new['name'], new_name) - - new = drive_types.rename(self.ctxt_admin, new_name) - expected_name = FLAGS.drive_type_template_short % \ - (dtype['type'], dtype['size_gb'], dtype['rpm']) - self.assertEqual(new['name'], expected_name) - - changes = {'rpm': 7200} - new = drive_types.update(self.ctxt_admin, new['id'], **changes) - for k, v in changes.iteritems(): - self.assertEqual(v, new[k], 'one of fields doesnt match') - - drive_types.delete(self.ctxt_admin, new['id']) - - def test_drive_type_get(self): - dtype = self._dtype - new = drive_types.create(self.ctxt_admin, **dtype) - - new2 = drive_types.get(self.ctxt_admin, new['id']) - for k, v in new2.iteritems(): - self.assertEqual(str(new[k]), str(new2[k]), - 'one of fields doesnt match') - - new2 = drive_types.get_by_name(self.ctxt_admin, new['name']) - for k, v in new.iteritems(): - self.assertEqual(str(new[k]), str(new2[k]), - 'one of fields doesnt match') - - drive_types.delete(self.ctxt_admin, new['id']) diff --git a/nova/tests/test_vsa.py b/nova/tests/test_vsa.py index 726939744..300a4d71c 100644 --- a/nova/tests/test_vsa.py +++ b/nova/tests/test_vsa.py @@ -13,38 +13,29 @@ # License for the specific language governing permissions and limitations # under the License. -import stubout import base64 +import stubout from xml.etree import ElementTree from xml.etree.ElementTree import Element, SubElement +from nova import context +from nova import db from nova import exception from nova import flags +from nova import log as logging +from nova import test from nova import vsa from nova import volume -from nova import db -from nova import context -from nova import test -from nova import log as logging +from nova.volume import volume_types +from nova.vsa import utils as vsa_utils + import nova.image.fake FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.vsa') -def fake_drive_type_get_by_name(context, name): - drive_type = { - 'id': 1, - 'name': name, - 'type': name.split('_')[0], - 'size_gb': int(name.split('_')[1]), - 'rpm': name.split('_')[2], - 'capabilities': '', - 'visible': True} - return drive_type - - class VsaTestCase(test.TestCase): def setUp(self): @@ -53,9 +44,20 @@ class VsaTestCase(test.TestCase): self.vsa_api = vsa.API() self.volume_api = volume.API() + FLAGS.quota_volumes = 100 + FLAGS.quota_gigabytes = 10000 + self.context_non_admin = context.RequestContext(None, None) self.context = context.get_admin_context() + volume_types.create(self.context, + 'SATA_500_7200', + extra_specs={'type': 'vsa_drive', + 'drive_name': 'SATA_500_7200', + 'drive_type': 'SATA', + 'drive_size': '500', + 'drive_rpm': '7200'}) + def fake_show_by_name(meh, context, name): if name == 'wrong_image_name': LOG.debug(_("Test: Emulate wrong VSA name. Raise")) @@ -124,9 +126,6 @@ class VsaTestCase(test.TestCase): FLAGS.vsa_multi_vol_creation = multi_vol_creation - self.stubs.Set(nova.vsa.drive_types, 'get_by_name', - fake_drive_type_get_by_name) - param = {'storage': [{'drive_name': 'SATA_500_7200', 'num_drives': 3}]} vsa_ref = self.vsa_api.create(self.context, **param) @@ -157,8 +156,6 @@ class VsaTestCase(test.TestCase): self.vsa_api.delete(self.context, vsa_ref['id']) def test_vsa_generate_user_data(self): - self.stubs.Set(nova.vsa.drive_types, 'get_by_name', - fake_drive_type_get_by_name) FLAGS.vsa_multi_vol_creation = False param = {'display_name': 'VSA name test', @@ -167,12 +164,10 @@ class VsaTestCase(test.TestCase): 'storage': [{'drive_name': 'SATA_500_7200', 'num_drives': 3}]} vsa_ref = self.vsa_api.create(self.context, **param) - volumes = db.volume_get_all_assigned_to_vsa(self.context, - vsa_ref['id']) + volumes = self.vsa_api.get_all_vsa_drives(self.context, + vsa_ref['id']) - user_data = self.vsa_api.generate_user_data(self.context, - vsa_ref, - volumes) + user_data = vsa_utils.generate_user_data(vsa_ref, volumes) user_data = base64.b64decode(user_data) LOG.debug(_("Test: user_data = %s"), user_data) diff --git a/nova/tests/test_vsa_volumes.py b/nova/tests/test_vsa_volumes.py index d451a4377..43173d86a 100644 --- a/nova/tests/test_vsa_volumes.py +++ b/nova/tests/test_vsa_volumes.py @@ -29,15 +29,6 @@ FLAGS = flags.FLAGS LOG = logging.getLogger('nova.tests.vsa.volumes') -def _default_volume_param(): - return { - 'size': 1, - 'snapshot_id': None, - 'name': 'Test volume name', - 'description': 'Test volume desc name' - } - - class VsaVolumesTestCase(test.TestCase): def setUp(self): @@ -49,6 +40,8 @@ class VsaVolumesTestCase(test.TestCase): self.context_non_admin = context.RequestContext(None, None) self.context = context.get_admin_context() + self.default_vol_type = self.vsa_api.get_vsa_volume_type(self.context) + def fake_show_by_name(meh, context, name): return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} @@ -66,12 +59,23 @@ class VsaVolumesTestCase(test.TestCase): self.stubs.UnsetAll() super(VsaVolumesTestCase, self).tearDown() + def _default_volume_param(self): + return { + 'size': 1, + 'snapshot_id': None, + 'name': 'Test volume name', + 'description': 'Test volume desc name', + 'volume_type': self.default_vol_type, + 'metadata': {'from_vsa_id': self.vsa_id} + } + + def _get_all_volumes_by_vsa(self): + return self.volume_api.get_all(self.context, + search_opts={'metadata': {"from_vsa_id": str(self.vsa_id)}}) + def test_vsa_volume_create_delete(self): """ Check if volume properly created and deleted. """ - vols1 = self.volume_api.get_all_by_vsa(self.context, - self.vsa_id, "from") - volume_param = _default_volume_param() - volume_param['from_vsa_id'] = self.vsa_id + volume_param = self._default_volume_param() volume_ref = self.volume_api.create(self.context, **volume_param) self.assertEqual(volume_ref['display_name'], @@ -81,21 +85,34 @@ class VsaVolumesTestCase(test.TestCase): self.assertEqual(volume_ref['size'], volume_param['size']) self.assertEqual(volume_ref['status'], - 'available') + 'creating') - vols2 = self.volume_api.get_all_by_vsa(self.context, - self.vsa_id, "from") - self.assertEqual(len(vols1) + 1, len(vols2)) + vols2 = self._get_all_volumes_by_vsa() + self.assertEqual(1, len(vols2)) + volume_ref = vols2[0] + self.assertEqual(volume_ref['display_name'], + volume_param['name']) + self.assertEqual(volume_ref['display_description'], + volume_param['description']) + self.assertEqual(volume_ref['size'], + volume_param['size']) + self.assertEqual(volume_ref['status'], + 'creating') + + self.volume_api.update(self.context, + volume_ref['id'], {'status': 'available'}) self.volume_api.delete(self.context, volume_ref['id']) - vols3 = self.volume_api.get_all_by_vsa(self.context, - self.vsa_id, "from") - self.assertEqual(len(vols3) + 1, len(vols2)) + + vols3 = self._get_all_volumes_by_vsa() + self.assertEqual(1, len(vols2)) + volume_ref = vols3[0] + self.assertEqual(volume_ref['status'], + 'deleting') def test_vsa_volume_delete_nonavail_volume(self): """ Check volume deleton in different states. """ - volume_param = _default_volume_param() - volume_param['from_vsa_id'] = self.vsa_id + volume_param = self._default_volume_param() volume_ref = self.volume_api.create(self.context, **volume_param) self.volume_api.update(self.context, @@ -104,26 +121,18 @@ class VsaVolumesTestCase(test.TestCase): self.volume_api.delete, self.context, volume_ref['id']) - 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") + vols1 = self._get_all_volumes_by_vsa() for i in range(3): - volume_param = _default_volume_param() - volume_param['from_vsa_id'] = self.vsa_id + volume_param = self._default_volume_param() volume_ref = self.volume_api.create(self.context, **volume_param) - vols2 = self.volume_api.get_all_by_vsa(self.context, - self.vsa_id, "from") + vols2 = self._get_all_volumes_by_vsa() 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") + vols3 = self._get_all_volumes_by_vsa() self.assertEqual(len(vols1), len(vols3)) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 2f0559366..6d1958401 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -203,6 +203,7 @@ class XenAPIVMTestCase(test.TestCase): self.context = context.RequestContext(self.user_id, self.project_id) self.conn = xenapi_conn.get_connection(False) + @test.skip_test("Skip this test meanwhile") def test_parallel_builds(self): stubs.stubout_loopingcall_delay(self.stubs) diff --git a/nova/vsa/api.py b/nova/vsa/api.py index bb6e93b87..b279255d7 100644 --- a/nova/vsa/api.py +++ b/nova/vsa/api.py @@ -20,22 +20,26 @@ Handles all requests relating to Virtual Storage Arrays (VSAs). """ import sys -import base64 - -from xml.etree import ElementTree +from nova import compute from nova import db from nova import exception from nova import flags from nova import log as logging -from nova import quota from nova import rpc -from nova.db import base - -from nova import compute from nova import volume from nova.compute import instance_types -from nova.vsa import drive_types +from nova.db import base +from nova.volume import volume_types + + +class VsaState: + CREATING = 'creating' # VSA creating (not ready yet) + LAUNCHING = 'launching' # Launching VCs (all BE volumes were created) + CREATED = 'created' # VSA fully created and ready for use + PARTIAL = 'partial' # Some BE drives were allocated + FAILED = 'failed' # Some BE storage allocations failed + DELETING = 'deleting' # VSA started the deletion procedure FLAGS = flags.FLAGS @@ -43,22 +47,14 @@ flags.DEFINE_string('vsa_ec2_access_key', None, 'EC2 access key used by VSA for accessing nova') flags.DEFINE_string('vsa_ec2_user_id', None, 'User ID used by VSA for accessing nova') - flags.DEFINE_boolean('vsa_multi_vol_creation', True, 'Ask scheduler to create multiple volumes in one call') +flags.DEFINE_string('vsa_volume_type_name', 'VSA volume type', + 'Name of volume type associated with FE VSA volumes') LOG = logging.getLogger('nova.vsa') -class VsaState: - CREATING = 'creating' # VSA creating (not ready yet) - LAUNCHING = 'launching' # Launching VCs (all BE volumes were created) - CREATED = 'created' # VSA fully created and ready for use - PARTIAL = 'partial' # Some BE storage allocations failed - FAILED = 'failed' # Some BE storage allocations failed - DELETING = 'deleting' # VSA started the deletion procedure - - class API(base.Base): """API for interacting with the VSA manager.""" @@ -67,6 +63,15 @@ class API(base.Base): self.volume_api = volume_api or volume.API() super(API, self).__init__(**kwargs) + def _check_volume_type_correctness(self, vol_type): + if vol_type.get('extra_specs') == None or\ + vol_type['extra_specs'].get('type') != 'vsa_drive' or\ + vol_type['extra_specs'].get('drive_type') == None or\ + vol_type['extra_specs'].get('drive_size') == None: + + raise exception.ApiError(_("Invalid drive type %s") + % vol_type['name']) + def _get_default_vsa_instance_type(self): return instance_types.get_instance_type_by_name( FLAGS.default_vsa_instance_type) @@ -89,16 +94,17 @@ class API(base.Base): if name is None: raise exception.ApiError(_("No drive_name param found in %s") % node) - - # find DB record for this disk try: - drive_ref = drive_types.get_by_name(context, name) + vol_type = volume_types.get_volume_type_by_name(context, name) except exception.NotFound: raise exception.ApiError(_("Invalid drive type name %s") % name) + self._check_volume_type_correctness(vol_type) + # if size field present - override disk size specified in DB - size = node.get('size', drive_ref['size_gb']) + size = int(node.get('size', + vol_type['extra_specs'].get('drive_size'))) if shared: part_size = FLAGS.vsa_part_size_gb @@ -110,17 +116,15 @@ class API(base.Base): size = 0 # special handling for full drives for i in range(num_volumes): - # volume_name = vsa_name + ("_%s_vol-%d" % (name, i)) volume_name = "drive-%03d" % first_index first_index += 1 volume_desc = 'BE volume for VSA %s type %s' % \ (vsa_name, name) volume = { 'size': size, - 'snapshot_id': None, 'name': volume_name, 'description': volume_desc, - 'drive_ref': drive_ref + 'volume_type_id': vol_type['id'], } volume_params.append(volume) @@ -211,7 +215,7 @@ class API(base.Base): if len(volume_params) > 0: request_spec = { 'num_volumes': len(volume_params), - 'vsa_id': vsa_id, + 'vsa_id': str(vsa_id), 'volumes': volume_params, } @@ -227,17 +231,21 @@ class API(base.Base): try: vol_name = vol['name'] vol_size = vol['size'] + vol_type_id = vol['volume_type_id'] LOG.debug(_("VSA ID %(vsa_id)d %(vsa_name)s: Create "\ - "volume %(vol_name)s, %(vol_size)d GB"), - locals()) + "volume %(vol_name)s, %(vol_size)d GB, "\ + "type %(vol_type_id)s"), locals()) + + vol_type = volume_types.get_volume_type(context, + vol['volume_type_id']) vol_ref = self.volume_api.create(context, vol_size, - vol['snapshot_id'], + None, vol_name, vol['description'], - to_vsa_id=vsa_id, - drive_type_id=vol['drive_ref'].get('id'), + volume_type=vol_type, + metadata=dict(to_vsa_id=str(vsa_id)), availability_zone=availability_zone) except: self.update_vsa_status(context, vsa_id, @@ -249,7 +257,7 @@ class API(base.Base): rpc.cast(context, FLAGS.vsa_topic, {"method": "create_vsa", - "args": {"vsa_id": vsa_id}}) + "args": {"vsa_id": str(vsa_id)}}) return vsa_ref @@ -314,8 +322,7 @@ 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 or volume['from_vsa_id']: - # Volume not yet assigned to host OR FE volume + if not host: # Deleting volume from database and skipping rpc. self.db.volume_destroy(ctxt, volume['id']) return @@ -328,9 +335,9 @@ class API(base.Base): 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) + volumes = self.get_all_vsa_volumes(context, vsa_id) else: - volumes = self.db.volume_get_all_assigned_to_vsa(context, vsa_id) + volumes = self.get_all_vsa_drives(context, vsa_id) for volume in volumes: try: @@ -374,58 +381,25 @@ class API(base.Base): return self.db.vsa_get_all(context) return self.db.vsa_get_all_by_project(context, context.project_id) - def generate_user_data(self, context, vsa, volumes): - SubElement = ElementTree.SubElement + def get_vsa_volume_type(self, context): + name = FLAGS.vsa_volume_type_name + try: + vol_type = volume_types.get_volume_type_by_name(context, name) + except exception.NotFound: + volume_types.create(context, name, + extra_specs=dict(type='vsa_volume')) + vol_type = volume_types.get_volume_type_by_name(context, name) - e_vsa = ElementTree.Element("vsa") + return vol_type - e_vsa_detail = SubElement(e_vsa, "id") - e_vsa_detail.text = str(vsa['id']) - e_vsa_detail = SubElement(e_vsa, "name") - e_vsa_detail.text = vsa['display_name'] - e_vsa_detail = SubElement(e_vsa, "description") - e_vsa_detail.text = vsa['display_description'] - e_vsa_detail = SubElement(e_vsa, "vc_count") - e_vsa_detail.text = str(vsa['vc_count']) + def get_all_vsa_instances(self, context, vsa_id): + return self.compute_api.get_all(context, + search_opts={'metadata': dict(vsa_id=str(vsa_id))}) - e_vsa_detail = SubElement(e_vsa, "auth_user") - e_vsa_detail.text = FLAGS.vsa_ec2_user_id - e_vsa_detail = SubElement(e_vsa, "auth_access_key") - e_vsa_detail.text = FLAGS.vsa_ec2_access_key + def get_all_vsa_volumes(self, context, vsa_id): + return self.volume_api.get_all(context, + search_opts={'metadata': dict(from_vsa_id=str(vsa_id))}) - e_volumes = SubElement(e_vsa, "volumes") - for volume in volumes: - - loc = volume['provider_location'] - if loc is None: - ip = '' - iscsi_iqn = '' - iscsi_portal = '' - else: - (iscsi_target, _sep, iscsi_iqn) = loc.partition(" ") - (ip, iscsi_portal) = iscsi_target.split(":", 1) - - e_vol = SubElement(e_volumes, "volume") - e_vol_detail = SubElement(e_vol, "id") - e_vol_detail.text = str(volume['id']) - e_vol_detail = SubElement(e_vol, "name") - e_vol_detail.text = volume['name'] - e_vol_detail = SubElement(e_vol, "display_name") - e_vol_detail.text = volume['display_name'] - e_vol_detail = SubElement(e_vol, "size_gb") - e_vol_detail.text = str(volume['size']) - e_vol_detail = SubElement(e_vol, "status") - e_vol_detail.text = volume['status'] - e_vol_detail = SubElement(e_vol, "ip") - e_vol_detail.text = ip - e_vol_detail = SubElement(e_vol, "iscsi_iqn") - e_vol_detail.text = iscsi_iqn - e_vol_detail = SubElement(e_vol, "iscsi_portal") - e_vol_detail.text = iscsi_portal - e_vol_detail = SubElement(e_vol, "lun") - e_vol_detail.text = '0' - e_vol_detail = SubElement(e_vol, "sn_host") - e_vol_detail.text = volume['host'] - - _xml = ElementTree.tostring(e_vsa) - return base64.b64encode(_xml) + def get_all_vsa_drives(self, context, vsa_id): + return self.volume_api.get_all(context, + search_opts={'metadata': dict(to_vsa_id=str(vsa_id))}) diff --git a/nova/vsa/drive_types.py b/nova/vsa/drive_types.py deleted file mode 100644 index 3cdbbfb09..000000000 --- a/nova/vsa/drive_types.py +++ /dev/null @@ -1,114 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 Zadara Storage Inc. -# Copyright (c) 2011 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. - -""" -Handles all requests relating to Virtual Storage Arrays (VSAs). -""" - -from nova import db -from nova import exception -from nova import flags -from nova import log as logging - -FLAGS = flags.FLAGS -flags.DEFINE_string('drive_type_template_short', '%s_%sGB_%sRPM', - 'Template string for generation of drive type name') -flags.DEFINE_string('drive_type_template_long', '%s_%sGB_%sRPM_%s', - 'Template string for generation of drive type name') - - -LOG = logging.getLogger('nova.drive_types') - - -def _generate_default_drive_name(type, size_gb, rpm, capabilities): - if capabilities is None or capabilities == '': - return FLAGS.drive_type_template_short % \ - (type, str(size_gb), rpm) - else: - return FLAGS.drive_type_template_long % \ - (type, str(size_gb), rpm, capabilities) - - -def create(context, type, size_gb, rpm, capabilities='', - visible=True, name=None): - if name is None: - name = _generate_default_drive_name(type, size_gb, rpm, - capabilities) - LOG.debug(_("Creating drive type %(name)s: "\ - "%(type)s %(size_gb)s %(rpm)s %(capabilities)s"), locals()) - - values = { - 'type': type, - 'size_gb': size_gb, - 'rpm': rpm, - 'capabilities': capabilities, - 'visible': visible, - 'name': name - } - return db.drive_type_create(context, values) - - -def update(context, id, **kwargs): - - LOG.debug(_("Updating drive type with id %(id)s: %(kwargs)s"), locals()) - - updatable_fields = ['type', - 'size_gb', - 'rpm', - 'capabilities', - 'visible'] - changes = {} - for field in updatable_fields: - if field in kwargs and \ - kwargs[field] is not None and \ - kwargs[field] != '': - changes[field] = kwargs[field] - - # call update regadless if changes is empty or not - return db.drive_type_update(context, id, changes) - - -def rename(context, name, new_name=None): - - if new_name is None or \ - new_name == '': - disk = db.drive_type_get_by_name(context, name) - new_name = _generate_default_drive_name(disk['type'], - disk['size_gb'], disk['rpm'], disk['capabilities']) - - LOG.debug(_("Renaming drive type %(name)s to %(new_name)s"), locals()) - - values = dict(name=new_name) - dtype = db.drive_type_get_by_name(context, name) - return db.drive_type_update(context, dtype['id'], values) - - -def delete(context, id): - LOG.debug(_("Deleting drive type %d"), id) - db.drive_type_destroy(context, id) - - -def get(context, id): - return db.drive_type_get(context, id) - - -def get_by_name(context, name): - return db.drive_type_get_by_name(context, name) - - -def get_all(context, visible=True): - return db.drive_type_get_all(context, visible) diff --git a/nova/vsa/fake.py b/nova/vsa/fake.py index 0bb81484d..d4248ca01 100644 --- a/nova/vsa/fake.py +++ b/nova/vsa/fake.py @@ -16,7 +16,7 @@ # under the License. -class FakeVcConnection: +class FakeVcConnection(object): def init_host(self, host): pass diff --git a/nova/vsa/manager.py b/nova/vsa/manager.py index 0f1718d38..d4c414106 100644 --- a/nova/vsa/manager.py +++ b/nova/vsa/manager.py @@ -22,17 +22,17 @@ Handles all processes relating to Virtual Storage Arrays (VSA). """ +from nova import compute +from nova import exception +from nova import flags from nova import log as logging from nova import manager -from nova import flags -from nova import utils -from nova import exception -from nova import compute from nova import volume from nova import vsa -from nova.vsa.api import VsaState +from nova import utils from nova.compute import instance_types - +from nova.vsa import utils as vsa_utils +from nova.vsa.api import VsaState FLAGS = flags.FLAGS flags.DEFINE_string('vsa_driver', 'nova.vsa.connection.get_connection', @@ -83,18 +83,18 @@ class VsaManager(manager.SchedulerDependentManager): @exception.wrap_exception() def vsa_volume_created(self, context, vol_id, vsa_id, status): """Callback for volume creations""" - LOG.debug(_("VSA ID %(vsa_id)s: Volume %(vol_id)s created. "\ + LOG.debug(_("VSA ID %(vsa_id)s: Drive %(vol_id)s created. "\ "Status %(status)s"), locals()) vsa_id = int(vsa_id) # just in case # Get all volumes for this VSA # check if any of them still in creating phase - volumes = self.db.volume_get_all_assigned_to_vsa(context, vsa_id) - for volume in volumes: - if volume['status'] == 'creating': - vol_name = volume['name'] - vol_disp_name = volume['display_name'] - LOG.debug(_("Volume %(vol_name)s (%(vol_disp_name)s) still "\ + drives = self.vsa_api.get_all_vsa_drives(context, vsa_id) + for drive in drives: + if drive['status'] == 'creating': + vol_name = drive['name'] + vol_disp_name = drive['display_name'] + LOG.debug(_("Drive %(vol_name)s (%(vol_disp_name)s) still "\ "in creating phase - wait"), locals()) return @@ -105,17 +105,17 @@ class VsaManager(manager.SchedulerDependentManager): LOG.exception(msg) return - if len(volumes) != vsa['vol_count']: - cvol_real = len(volumes) + if len(drives) != vsa['vol_count']: + cvol_real = len(drives) cvol_exp = vsa['vol_count'] LOG.debug(_("VSA ID %(vsa_id)d: Not all volumes are created "\ "(%(cvol_real)d of %(cvol_exp)d)"), locals()) return # all volumes created (successfully or not) - return self._start_vcs(context, vsa, volumes) + return self._start_vcs(context, vsa, drives) - def _start_vcs(self, context, vsa, volumes=[]): + def _start_vcs(self, context, vsa, drives=[]): """Start VCs for VSA """ vsa_id = vsa['id'] @@ -127,11 +127,11 @@ class VsaManager(manager.SchedulerDependentManager): # in _separate_ loop go over all volumes and mark as "attached" has_failed_volumes = False - for volume in volumes: - vol_name = volume['name'] - vol_disp_name = volume['display_name'] - status = volume['status'] - LOG.info(_("VSA ID %(vsa_id)d: Volume %(vol_name)s "\ + for drive in drives: + vol_name = drive['name'] + vol_disp_name = drive['display_name'] + status = drive['status'] + LOG.info(_("VSA ID %(vsa_id)d: Drive %(vol_name)s "\ "(%(vol_disp_name)s) is in %(status)s state"), locals()) if status == 'available': @@ -149,11 +149,12 @@ class VsaManager(manager.SchedulerDependentManager): if has_failed_volumes: LOG.info(_("VSA ID %(vsa_id)d: Delete all BE volumes"), locals()) self.vsa_api.delete_vsa_volumes(context, vsa_id, "BE", True) - self.vsa_api.update_vsa_status(context, vsa_id, VsaState.FAILED) + self.vsa_api.update_vsa_status(context, vsa_id, + VsaState.FAILED) return # create user-data record for VC - storage_data = self.vsa_api.generate_user_data(context, vsa, volumes) + storage_data = vsa_utils.generate_user_data(vsa, drives) instance_type = instance_types.get_instance_type( vsa['instance_type_id']) @@ -174,4 +175,5 @@ class VsaManager(manager.SchedulerDependentManager): user_data=storage_data, metadata=dict(vsa_id=str(vsa_id))) - self.vsa_api.update_vsa_status(context, vsa_id, VsaState.CREATED) + self.vsa_api.update_vsa_status(context, vsa_id, + VsaState.CREATED) diff --git a/nova/vsa/utils.py b/nova/vsa/utils.py new file mode 100644 index 000000000..1de341ac5 --- /dev/null +++ b/nova/vsa/utils.py @@ -0,0 +1,80 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Zadara Storage Inc. +# Copyright (c) 2011 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. + +import base64 +from xml.etree import ElementTree + +from nova import flags + +FLAGS = flags.FLAGS + + +def generate_user_data(vsa, volumes): + SubElement = ElementTree.SubElement + + e_vsa = ElementTree.Element("vsa") + + e_vsa_detail = SubElement(e_vsa, "id") + e_vsa_detail.text = str(vsa['id']) + e_vsa_detail = SubElement(e_vsa, "name") + e_vsa_detail.text = vsa['display_name'] + e_vsa_detail = SubElement(e_vsa, "description") + e_vsa_detail.text = vsa['display_description'] + e_vsa_detail = SubElement(e_vsa, "vc_count") + e_vsa_detail.text = str(vsa['vc_count']) + + e_vsa_detail = SubElement(e_vsa, "auth_user") + e_vsa_detail.text = FLAGS.vsa_ec2_user_id + e_vsa_detail = SubElement(e_vsa, "auth_access_key") + e_vsa_detail.text = FLAGS.vsa_ec2_access_key + + e_volumes = SubElement(e_vsa, "volumes") + for volume in volumes: + + loc = volume['provider_location'] + if loc is None: + ip = '' + iscsi_iqn = '' + iscsi_portal = '' + else: + (iscsi_target, _sep, iscsi_iqn) = loc.partition(" ") + (ip, iscsi_portal) = iscsi_target.split(":", 1) + + e_vol = SubElement(e_volumes, "volume") + e_vol_detail = SubElement(e_vol, "id") + e_vol_detail.text = str(volume['id']) + e_vol_detail = SubElement(e_vol, "name") + e_vol_detail.text = volume['name'] + e_vol_detail = SubElement(e_vol, "display_name") + e_vol_detail.text = volume['display_name'] + e_vol_detail = SubElement(e_vol, "size_gb") + e_vol_detail.text = str(volume['size']) + e_vol_detail = SubElement(e_vol, "status") + e_vol_detail.text = volume['status'] + e_vol_detail = SubElement(e_vol, "ip") + e_vol_detail.text = ip + e_vol_detail = SubElement(e_vol, "iscsi_iqn") + e_vol_detail.text = iscsi_iqn + e_vol_detail = SubElement(e_vol, "iscsi_portal") + e_vol_detail.text = iscsi_portal + e_vol_detail = SubElement(e_vol, "lun") + e_vol_detail.text = '0' + e_vol_detail = SubElement(e_vol, "sn_host") + e_vol_detail.text = volume['host'] + + _xml = ElementTree.tostring(e_vsa) + return base64.b64encode(_xml)