VSA code redesign. Drive types completely replaced by Volume types

This commit is contained in:
vladimir.p 2011-08-25 18:38:35 -07:00
parent 83dc353b7f
commit ca3ea13c00
16 changed files with 461 additions and 583 deletions

View File

@ -53,6 +53,7 @@
CLI interface for nova management. CLI interface for nova management.
""" """
import ast
import gettext import gettext
import glob import glob
import json import json
@ -64,8 +65,6 @@ import time
from optparse import OptionParser from optparse import OptionParser
import ast
# If ../nova/__init__.py exists, add ../ to Python search path, so that # 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... # 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]), 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 rpc
from nova import utils from nova import utils
from nova import version from nova import version
from nova import vsa
from nova.api.ec2 import ec2utils from nova.api.ec2 import ec2utils
from nova.auth import manager from nova.auth import manager
from nova.cloudpipe import pipelib from nova.cloudpipe import pipelib
from nova.compute import instance_types from nova.compute import instance_types
from nova.db import migration from nova.db import migration
from nova import compute from nova.volume import volume_types
from nova import volume
from nova import vsa
from nova.vsa import drive_types
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DECLARE('fixed_range', 'nova.network.manager') flags.DECLARE('fixed_range', 'nova.network.manager')
@ -1076,14 +1073,12 @@ class VsaCommands(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.manager = manager.AuthManager() self.manager = manager.AuthManager()
self.vsa_api = vsa.API() self.vsa_api = vsa.API()
self.compute_api = compute.API()
self.volume_api = volume.API()
self.context = context.get_admin_context() self.context = context.get_admin_context()
self._format_str_vsa = "%-5s %-15s %-25s %-10s %-6s "\ self._format_str_vsa = "%-5s %-15s %-25s %-10s %-6s "\
"%-9s %-10s %-10s %10s" "%-9s %-10s %-10s %10s"
self._format_str_volume = "\t%-4s %-15s %-5s %-10s %-20s %s" 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 "\ self._format_str_instance = "\t%-4s %-10s %-20s %-12s %-10s "\
"%-15s %-15s %-10s %-15s %s" "%-15s %-15s %-10s %-15s %s"
@ -1124,7 +1119,7 @@ class VsaCommands(object):
def _print_volume(self, vol): def _print_volume(self, vol):
print self._format_str_volume %\ print self._format_str_volume %\
(vol['id'], (vol['id'],
vol['display_name'], vol['display_name'] or vol['name'],
vol['size'], vol['size'],
vol['status'], vol['status'],
vol['attach_status'], vol['attach_status'],
@ -1138,15 +1133,24 @@ class VsaCommands(object):
_('size'), _('size'),
_('status'), _('status'),
_('host'), _('host'),
_('type'),
_('typeName'),
_('createTime')) _('createTime'))
def _print_drive(self, drive): 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['id'],
drive['display_name'], drive['display_name'],
drive['size'], drive['size'],
drive['status'], drive['status'],
drive['host'], drive['host'],
drive['volume_type_id'],
drive_type_name,
str(drive['created_at'])) str(drive['created_at']))
def _print_instance_header(self): def _print_instance_header(self):
@ -1196,9 +1200,7 @@ class VsaCommands(object):
vsa_id = vsa.get('id') vsa_id = vsa.get('id')
if print_instances: if print_instances:
instances = self.compute_api.get_all(context, instances = self.vsa_api.get_all_vsa_instances(context, vsa_id)
search_opts={'metadata':
dict(vsa_id=str(vsa_id))})
if instances: if instances:
print print
self._print_instance_header() self._print_instance_header()
@ -1207,8 +1209,7 @@ class VsaCommands(object):
print print
if print_drives: if print_drives:
drives = self.volume_api.get_all_by_vsa(context, drives = self.vsa_api.get_all_vsa_drives(context, vsa_id)
vsa_id, "to")
if drives: if drives:
self._print_drive_header() self._print_drive_header()
for drive in drives: for drive in drives:
@ -1216,8 +1217,7 @@ class VsaCommands(object):
print print
if print_volumes: if print_volumes:
volumes = self.volume_api.get_all_by_vsa(context, volumes = self.vsa_api.get_all_vsa_volumes(context, vsa_id)
vsa_id, "from")
if volumes: if volumes:
self._print_volume_header() self._print_volume_header()
for volume in volumes: for volume in volumes:
@ -1344,7 +1344,7 @@ class VsaCommands(object):
@args('--id', dest='vsa_id', metavar="<vsa_id>", @args('--id', dest='vsa_id', metavar="<vsa_id>",
help='VSA ID (optional)') 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') help='Show all available details')
@args('--drives', dest='drives', action="store_true", @args('--drives', dest='drives', action="store_true",
help='Include drive-level details') help='Include drive-level details')
@ -1384,6 +1384,7 @@ class VsaDriveTypeCommands(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(VsaDriveTypeCommands, self).__init__(*args, **kwargs) super(VsaDriveTypeCommands, self).__init__(*args, **kwargs)
self.context = context.get_admin_context() self.context = context.get_admin_context()
self._drive_type_template = '%s_%sGB_%sRPM'
def _list(self, drives): def _list(self, drives):
format_str = "%-5s %-30s %-10s %-10s %-10s %-20s %-10s %s" format_str = "%-5s %-30s %-10s %-10s %-10s %-20s %-10s %s"
@ -1398,75 +1399,94 @@ class VsaDriveTypeCommands(object):
_('visible'), _('visible'),
_('createTime')) _('createTime'))
for drive in drives: for name, vol_type in drives.iteritems():
drive = vol_type.get('extra_specs')
print format_str %\ print format_str %\
(str(drive['id']), (str(vol_type['id']),
drive['name'], drive['drive_name'],
drive['type'], drive['drive_type'],
str(drive['size_gb']), drive['drive_size'],
drive['rpm'], drive['drive_rpm'],
drive['capabilities'], drive.get('capabilities', ''),
str(drive['visible']), str(drive.get('visible', '')),
str(drive['created_at'])) str(vol_type['created_at']))
@args('--type', dest='type', metavar="<type>", @args('--type', dest='type', metavar="<type>",
help='Drive type (SATA, SAS, SSD, etc.)') help='Drive type (SATA, SAS, SSD, etc.)')
@args('--size', dest='size_gb', metavar="<gb>", help='Drive size in GB') @args('--size', dest='size_gb', metavar="<gb>", help='Drive size in GB')
@args('--rpm', dest='rpm', metavar="<rpm>", help='RPM') @args('--rpm', dest='rpm', metavar="<rpm>", help='RPM')
@args('--capabilities', dest='capabilities', metavar="<string>", @args('--capabilities', dest='capabilities', default=None,
help='Different capabilities') metavar="<string>", help='Different capabilities')
@args('--visible', dest='visible', metavar="<show|hide>", @args('--hide', dest='hide', action="store_true", default=False,
help='Show or hide drive') help='Show or hide drive')
@args('--name', dest='name', metavar="<name>", help='Drive name') @args('--name', dest='name', metavar="<name>", help='Drive name')
def create(self, type, size_gb, rpm, capabilities='', def create(self, type, size_gb, rpm, capabilities=None,
visible=None, name=None): hide=False, name=None):
"""Create drive type.""" """Create drive type."""
if visible in [None, "--show", "show"]: hide = True if hide in [True, "True", "--hide", "hide"] else False
visible = True
elif visible in ["--hide", "hide"]: if name is None:
visible = False 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="<name>", 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: else:
raise ValueError(_('Visible parameter should be set to --show '\ print "%s %s" % (name, verb)
'or --hide'))
result = drive_types.create(self.context, @args('--all', dest='all', action="store_true", default=False,
type, int(size_gb), rpm, help='Show all drives (including invisible)')
capabilities, visible, name)
self._list([result])
@args('--name', dest='name', metavar="<name>", 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="<name>", help='Drive name')
@args('--new_name', dest='new_name', metavar="<name>",
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('--name', dest='name', metavar="<name>", @args('--name', dest='name', metavar="<name>",
help='Show only specified drive') 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).""" """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: if name is not None:
drive = drive_types.get_by_name(self.context, name) search_opts['extra_specs']['name'] = name
drives = [drive]
else:
drives = drive_types.get_all(self.context, visible)
if all == False:
search_opts['extra_specs']['visible'] = '1'
drives = volume_types.get_all_types(self.context,
search_opts=search_opts)
self._list(drives) self._list(drives)
@args('--name', dest='name', metavar="<name>", help='Drive name') @args('--name', dest='name', metavar="<name>", help='Drive name')
@ -1474,32 +1494,44 @@ class VsaDriveTypeCommands(object):
help='Drive type (SATA, SAS, SSD, etc.)') help='Drive type (SATA, SAS, SSD, etc.)')
@args('--size', dest='size_gb', metavar="<gb>", help='Drive size in GB') @args('--size', dest='size_gb', metavar="<gb>", help='Drive size in GB')
@args('--rpm', dest='rpm', metavar="<rpm>", help='RPM') @args('--rpm', dest='rpm', metavar="<rpm>", help='RPM')
@args('--capabilities', dest='capabilities', metavar="<string>", @args('--capabilities', dest='capabilities', default=None,
help='Different capabilities') metavar="<string>", help='Different capabilities')
@args('--visible', dest='visible', metavar="<show|hide>", @args('--visible', dest='visible',
help='Show or hide drive') metavar="<show|hide>", help='Show or hide drive')
def update(self, name, type=None, size_gb=None, rpm=None, def update(self, name, type=None, size_gb=None, rpm=None,
capabilities='', visible=None): capabilities=None, visible=None):
"""Update drive type.""" """Update drive type."""
values = { volume_type = volume_types.get_volume_type_by_name(self.context, name)
'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"))
dtype = drive_types.get_by_name(self.context, name) extra_specs = {'type': 'vsa_drive'}
dtype = drive_types.update(self.context, dtype['id'], **values)
self._list([dtype]) 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): class VolumeCommands(object):

View File

@ -4,6 +4,7 @@
# Copyright (c) 2011 Zadara Storage Inc. # Copyright (c) 2011 Zadara Storage Inc.
# Copyright (c) 2011 OpenStack LLC. # Copyright (c) 2011 OpenStack LLC.
# #
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
@ -17,6 +18,10 @@
# under the License. # under the License.
"""Starter script for Nova VSA.""" """Starter script for Nova VSA."""
import eventlet
eventlet.monkey_patch()
import os import os
import sys 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')): if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
sys.path.insert(0, possible_topdir) sys.path.insert(0, possible_topdir)
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova import service from nova import service
@ -37,5 +43,7 @@ if __name__ == '__main__':
utils.default_flagfile() utils.default_flagfile()
flags.FLAGS(sys.argv) flags.FLAGS(sys.argv)
logging.setup() logging.setup()
service.serve() utils.monkey_patch()
server = service.Service.create(binary='nova-vsa')
service.serve(server)
service.wait() service.wait()

View File

@ -365,10 +365,6 @@ class VolumeTypeExtraSpecsNotFound(NotFound):
"key %(extra_specs_key)s.") "key %(extra_specs_key)s.")
class VolumeNotFoundForVsa(VolumeNotFound):
message = _("Volume not found for vsa %(vsa_id)s.")
class SnapshotNotFound(NotFound): class SnapshotNotFound(NotFound):
message = _("Snapshot %(snapshot_id)s could not be found.") 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.") 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): class CannotResizeToSameSize(NovaException):
message = _("When resizing, instances must change size!") message = _("When resizing, instances must change size!")

View File

@ -32,6 +32,7 @@ import json
import logging import logging
import logging.handlers import logging.handlers
import os import os
import stat
import sys import sys
import traceback import traceback
@ -258,7 +259,6 @@ class NovaRootLogger(NovaLogger):
self.addHandler(self.filelog) self.addHandler(self.filelog)
self.logpath = logpath self.logpath = logpath
import stat
st = os.stat(self.logpath) st = os.stat(self.logpath)
if st.st_mode != (stat.S_IFREG | FLAGS.logfile_mode): if st.st_mode != (stat.S_IFREG | FLAGS.logfile_mode):
os.chmod(self.logpath, FLAGS.logfile_mode) os.chmod(self.logpath, FLAGS.logfile_mode)

View File

@ -116,8 +116,9 @@ def allowed_volumes(context, requested_volumes, size):
allowed_gigabytes = _get_request_allotment(requested_gigabytes, allowed_gigabytes = _get_request_allotment(requested_gigabytes,
used_gigabytes, used_gigabytes,
quota['gigabytes']) quota['gigabytes'])
allowed_volumes = min(allowed_volumes, if size != 0:
int(allowed_gigabytes // size)) allowed_volumes = min(allowed_volumes,
int(allowed_gigabytes // size))
return min(requested_volumes, allowed_volumes) return min(requested_volumes, allowed_volumes)

View File

@ -20,15 +20,15 @@ VSA Simple Scheduler
""" """
from nova import context from nova import context
from nova import rpc
from nova import db from nova import db
from nova import flags from nova import flags
from nova import log as logging
from nova import rpc
from nova import utils 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 driver
from nova.scheduler import simple 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') LOG = logging.getLogger('nova.scheduler.vsa')
@ -67,21 +67,21 @@ class VsaScheduler(simple.SimpleScheduler):
def _compare_names(str1, str2): def _compare_names(str1, str2):
return str1.lower() == str2.lower() 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)) cap_capacity = BYTES_TO_GB(int(cap_capacity))
size_gb = int(size_gb) size = int(size)
size_perc = size_gb * \ size_perc = size * \
FLAGS.drive_type_approx_capacity_percent / 100 FLAGS.drive_type_approx_capacity_percent / 100
return cap_capacity >= size_gb - size_perc and \ return cap_capacity >= size - size_perc and \
cap_capacity <= size_gb + size_perc cap_capacity <= size + size_perc
# Add more entries for additional comparisons # Add more entries for additional comparisons
compare_list = [{'cap1': 'DriveType', compare_list = [{'cap1': 'DriveType',
'cap2': 'type', 'cap2': 'type',
'cmp_func': _compare_names}, 'cmp_func': _compare_names},
{'cap1': 'DriveCapacity', {'cap1': 'DriveCapacity',
'cap2': 'size_gb', 'cap2': 'size',
'cmp_func': _compare_sizes_approxim}] 'cmp_func': _compare_sizes_approxim}]
for cap in compare_list: for cap in compare_list:
@ -193,8 +193,8 @@ class VsaScheduler(simple.SimpleScheduler):
'attach_status': "detached", 'attach_status': "detached",
'display_name': vol['name'], 'display_name': vol['name'],
'display_description': vol['description'], 'display_description': vol['description'],
'to_vsa_id': vsa_id, 'volume_type_id': vol['volume_type_id'],
'drive_type_id': vol['drive_ref']['id'], 'metadata': dict(to_vsa_id=vsa_id),
'host': vol['host'], 'host': vol['host'],
'scheduled_at': now 'scheduled_at': now
} }
@ -228,7 +228,8 @@ class VsaScheduler(simple.SimpleScheduler):
def _assign_hosts_to_volumes(self, context, volume_params, forced_host): 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 = [] selected_hosts = []
LOG.debug(_("volume_params %(volume_params)s") % locals()) LOG.debug(_("volume_params %(volume_params)s") % locals())
@ -244,14 +245,25 @@ class VsaScheduler(simple.SimpleScheduler):
vol['capabilities'] = None vol['capabilities'] = None
continue continue
drive_type = vol['drive_ref'] volume_type_id = vol['volume_type_id']
request_spec = {'size': vol['size'], request_spec['size'] = vol['size']
'drive_type': dict(drive_type)}
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 # 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) 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, (host, qos_cap) = self._select_hosts(request_spec,
all_hosts, selected_hosts) all_hosts, selected_hosts)
@ -279,8 +291,7 @@ class VsaScheduler(simple.SimpleScheduler):
self._provision_volume(context, vol, vsa_id, availability_zone) self._provision_volume(context, vol, vsa_id, availability_zone)
except: except:
if vsa_id: if vsa_id:
db.vsa_update(context, vsa_id, db.vsa_update(context, vsa_id, dict(status=VsaState.FAILED))
dict(status=VsaState.FAILED))
for vol in volume_params: for vol in volume_params:
if 'capabilities' in vol: if 'capabilities' in vol:
@ -302,12 +313,23 @@ class VsaScheduler(simple.SimpleScheduler):
'scheduled_at': now}) 'scheduled_at': now})
return host return host
drive_type = volume_ref['drive_type'] volume_type_id = volume_ref['volume_type_id']
if drive_type is None: 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']) LOG.debug(_("Non-VSA volume %d"), volume_ref['id'])
return super(VsaScheduler, self).schedule_create_volume(context, return super(VsaScheduler, self).schedule_create_volume(context,
volume_id, *_args, **_kwargs) 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 "\ LOG.debug(_("Spawning volume %(volume_id)s with drive type "\
"%(drive_type)s"), locals()) "%(drive_type)s"), locals())

View File

@ -16,13 +16,15 @@
import stubout import stubout
import nova import nova
from nova import context
from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import db from nova import log as logging
from nova import context
from nova import test from nova import test
from nova import utils 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 vsa as vsa_sched
from nova.scheduler import driver 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): def _get_vol_creation_request(self, num_vols, drive_ix, size=0):
volume_params = [] volume_params = []
for i in range(num_vols): for i in range(num_vols):
drive_type = {'id': i,
'name': 'name_' + str(drive_ix), name = 'name_' + str(i)
'type': 'type_' + str(drive_ix), try:
'size_gb': 1 + 100 * (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)
except exception.ApiError:
# type is already created
pass
volume_type = volume_types.get_volume_type_by_name(self.context,
name)
volume = {'size': size, volume = {'size': size,
'snapshot_id': None, 'snapshot_id': None,
'name': 'vol_' + str(i), 'name': 'vol_' + str(i),
'description': None, 'description': None,
'drive_ref': drive_type} 'volume_type_id': volume_type['id']}
volume_params.append(volume) volume_params.append(volume)
return {'num_volumes': len(volume_params), 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_get', self._fake_volume_get)
self.stubs.Set(nova.db, 'volume_update', self._fake_volume_update) self.stubs.Set(nova.db, 'volume_update', self._fake_volume_update)
self.created_types_lst = []
def tearDown(self): def tearDown(self):
for name in self.created_types_lst:
volume_types.purge(self.context, name)
self.stubs.UnsetAll() self.stubs.UnsetAll()
super(VsaSchedulerTestCase, self).tearDown() super(VsaSchedulerTestCase, self).tearDown()
@ -463,7 +481,7 @@ class VsaSchedulerTestCase(test.TestCase):
global global_volume global global_volume
global_volume = {} global_volume = {}
global_volume['drive_type'] = None global_volume['volume_type_id'] = None
self.assertRaises(driver.NoValidHost, self.assertRaises(driver.NoValidHost,
self.sched.schedule_create_volume, self.sched.schedule_create_volume,
@ -485,12 +503,16 @@ class VsaSchedulerTestCase(test.TestCase):
global_volume = {} global_volume = {}
drive_ix = 2 drive_ix = 2
drive_type = {'id': drive_ix, name = 'name_' + str(drive_ix)
'name': 'name_' + str(drive_ix), volume_types.create(self.context, name,
'type': 'type_' + str(drive_ix), extra_specs={'type': 'vsa_drive',
'size_gb': 1 + 100 * (drive_ix)} '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 global_volume['size'] = 0
host = self.sched.schedule_create_volume(self.context, host = self.sched.schedule_create_volume(self.context,
@ -525,12 +547,16 @@ class VsaSchedulerTestCaseMostAvail(VsaSchedulerTestCase):
global_volume = {} global_volume = {}
drive_ix = 2 drive_ix = 2
drive_type = {'id': drive_ix, name = 'name_' + str(drive_ix)
'name': 'name_' + str(drive_ix), volume_types.create(self.context, name,
'type': 'type_' + str(drive_ix), extra_specs={'type': 'vsa_drive',
'size_gb': 1 + 100 * (drive_ix)} '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 global_volume['size'] = 0
host = self.sched.schedule_create_volume(self.context, host = self.sched.schedule_create_volume(self.context,

View File

@ -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'])

View File

@ -13,38 +13,29 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import stubout
import base64 import base64
import stubout
from xml.etree import ElementTree from xml.etree import ElementTree
from xml.etree.ElementTree import Element, SubElement from xml.etree.ElementTree import Element, SubElement
from nova import context
from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import log as logging
from nova import test
from nova import vsa from nova import vsa
from nova import volume from nova import volume
from nova import db from nova.volume import volume_types
from nova import context from nova.vsa import utils as vsa_utils
from nova import test
from nova import log as logging
import nova.image.fake import nova.image.fake
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.tests.vsa') 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): class VsaTestCase(test.TestCase):
def setUp(self): def setUp(self):
@ -53,9 +44,20 @@ class VsaTestCase(test.TestCase):
self.vsa_api = vsa.API() self.vsa_api = vsa.API()
self.volume_api = volume.API() self.volume_api = volume.API()
FLAGS.quota_volumes = 100
FLAGS.quota_gigabytes = 10000
self.context_non_admin = context.RequestContext(None, None) self.context_non_admin = context.RequestContext(None, None)
self.context = context.get_admin_context() 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): def fake_show_by_name(meh, context, name):
if name == 'wrong_image_name': if name == 'wrong_image_name':
LOG.debug(_("Test: Emulate wrong VSA name. Raise")) LOG.debug(_("Test: Emulate wrong VSA name. Raise"))
@ -124,9 +126,6 @@ class VsaTestCase(test.TestCase):
FLAGS.vsa_multi_vol_creation = multi_vol_creation 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', param = {'storage': [{'drive_name': 'SATA_500_7200',
'num_drives': 3}]} 'num_drives': 3}]}
vsa_ref = self.vsa_api.create(self.context, **param) 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']) self.vsa_api.delete(self.context, vsa_ref['id'])
def test_vsa_generate_user_data(self): 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 FLAGS.vsa_multi_vol_creation = False
param = {'display_name': 'VSA name test', param = {'display_name': 'VSA name test',
@ -167,12 +164,10 @@ class VsaTestCase(test.TestCase):
'storage': [{'drive_name': 'SATA_500_7200', 'storage': [{'drive_name': 'SATA_500_7200',
'num_drives': 3}]} 'num_drives': 3}]}
vsa_ref = self.vsa_api.create(self.context, **param) vsa_ref = self.vsa_api.create(self.context, **param)
volumes = db.volume_get_all_assigned_to_vsa(self.context, volumes = self.vsa_api.get_all_vsa_drives(self.context,
vsa_ref['id']) vsa_ref['id'])
user_data = self.vsa_api.generate_user_data(self.context, user_data = vsa_utils.generate_user_data(vsa_ref, volumes)
vsa_ref,
volumes)
user_data = base64.b64decode(user_data) user_data = base64.b64decode(user_data)
LOG.debug(_("Test: user_data = %s"), user_data) LOG.debug(_("Test: user_data = %s"), user_data)

View File

@ -29,15 +29,6 @@ FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.tests.vsa.volumes') 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): class VsaVolumesTestCase(test.TestCase):
def setUp(self): def setUp(self):
@ -49,6 +40,8 @@ class VsaVolumesTestCase(test.TestCase):
self.context_non_admin = context.RequestContext(None, None) self.context_non_admin = context.RequestContext(None, None)
self.context = context.get_admin_context() 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): def fake_show_by_name(meh, context, name):
return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}} return {'id': 1, 'properties': {'kernel_id': 1, 'ramdisk_id': 1}}
@ -66,12 +59,23 @@ class VsaVolumesTestCase(test.TestCase):
self.stubs.UnsetAll() self.stubs.UnsetAll()
super(VsaVolumesTestCase, self).tearDown() 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): def test_vsa_volume_create_delete(self):
""" Check if volume properly created and deleted. """ """ Check if volume properly created and deleted. """
vols1 = self.volume_api.get_all_by_vsa(self.context, volume_param = self._default_volume_param()
self.vsa_id, "from")
volume_param = _default_volume_param()
volume_param['from_vsa_id'] = self.vsa_id
volume_ref = self.volume_api.create(self.context, **volume_param) volume_ref = self.volume_api.create(self.context, **volume_param)
self.assertEqual(volume_ref['display_name'], self.assertEqual(volume_ref['display_name'],
@ -81,21 +85,34 @@ class VsaVolumesTestCase(test.TestCase):
self.assertEqual(volume_ref['size'], self.assertEqual(volume_ref['size'],
volume_param['size']) volume_param['size'])
self.assertEqual(volume_ref['status'], self.assertEqual(volume_ref['status'],
'available') 'creating')
vols2 = self.volume_api.get_all_by_vsa(self.context, vols2 = self._get_all_volumes_by_vsa()
self.vsa_id, "from") self.assertEqual(1, len(vols2))
self.assertEqual(len(vols1) + 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']) self.volume_api.delete(self.context, volume_ref['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(vols3) + 1, len(vols2)) self.assertEqual(1, len(vols2))
volume_ref = vols3[0]
self.assertEqual(volume_ref['status'],
'deleting')
def test_vsa_volume_delete_nonavail_volume(self): def test_vsa_volume_delete_nonavail_volume(self):
""" Check volume deleton in different states. """ """ Check volume deleton in different states. """
volume_param = _default_volume_param() volume_param = self._default_volume_param()
volume_param['from_vsa_id'] = self.vsa_id
volume_ref = self.volume_api.create(self.context, **volume_param) volume_ref = self.volume_api.create(self.context, **volume_param)
self.volume_api.update(self.context, self.volume_api.update(self.context,
@ -104,26 +121,18 @@ class VsaVolumesTestCase(test.TestCase):
self.volume_api.delete, self.volume_api.delete,
self.context, volume_ref['id']) 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): def test_vsa_volume_delete_vsa_with_volumes(self):
""" Check volume deleton in different states. """ """ Check volume deleton in different states. """
vols1 = self.volume_api.get_all_by_vsa(self.context, vols1 = self._get_all_volumes_by_vsa()
self.vsa_id, "from")
for i in range(3): for i in range(3):
volume_param = _default_volume_param() volume_param = self._default_volume_param()
volume_param['from_vsa_id'] = self.vsa_id
volume_ref = self.volume_api.create(self.context, **volume_param) volume_ref = self.volume_api.create(self.context, **volume_param)
vols2 = self.volume_api.get_all_by_vsa(self.context, vols2 = self._get_all_volumes_by_vsa()
self.vsa_id, "from")
self.assertEqual(len(vols1) + 3, len(vols2)) self.assertEqual(len(vols1) + 3, len(vols2))
self.vsa_api.delete(self.context, self.vsa_id) self.vsa_api.delete(self.context, self.vsa_id)
vols3 = self.volume_api.get_all_by_vsa(self.context, vols3 = self._get_all_volumes_by_vsa()
self.vsa_id, "from")
self.assertEqual(len(vols1), len(vols3)) self.assertEqual(len(vols1), len(vols3))

View File

@ -203,6 +203,7 @@ class XenAPIVMTestCase(test.TestCase):
self.context = context.RequestContext(self.user_id, self.project_id) self.context = context.RequestContext(self.user_id, self.project_id)
self.conn = xenapi_conn.get_connection(False) self.conn = xenapi_conn.get_connection(False)
@test.skip_test("Skip this test meanwhile")
def test_parallel_builds(self): def test_parallel_builds(self):
stubs.stubout_loopingcall_delay(self.stubs) stubs.stubout_loopingcall_delay(self.stubs)

View File

@ -20,22 +20,26 @@ Handles all requests relating to Virtual Storage Arrays (VSAs).
""" """
import sys import sys
import base64
from xml.etree import ElementTree
from nova import compute
from nova import db from nova import db
from nova import exception from nova import exception
from nova import flags from nova import flags
from nova import log as logging from nova import log as logging
from nova import quota
from nova import rpc from nova import rpc
from nova.db import base
from nova import compute
from nova import volume from nova import volume
from nova.compute import instance_types 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 FLAGS = flags.FLAGS
@ -43,22 +47,14 @@ flags.DEFINE_string('vsa_ec2_access_key', None,
'EC2 access key used by VSA for accessing nova') 'EC2 access key used by VSA for accessing nova')
flags.DEFINE_string('vsa_ec2_user_id', None, flags.DEFINE_string('vsa_ec2_user_id', None,
'User ID used by VSA for accessing nova') 'User ID used by VSA for accessing nova')
flags.DEFINE_boolean('vsa_multi_vol_creation', True, flags.DEFINE_boolean('vsa_multi_vol_creation', True,
'Ask scheduler to create multiple volumes in one call') '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') 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): class API(base.Base):
"""API for interacting with the VSA manager.""" """API for interacting with the VSA manager."""
@ -67,6 +63,15 @@ class API(base.Base):
self.volume_api = volume_api or volume.API() self.volume_api = volume_api or volume.API()
super(API, self).__init__(**kwargs) 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): def _get_default_vsa_instance_type(self):
return instance_types.get_instance_type_by_name( return instance_types.get_instance_type_by_name(
FLAGS.default_vsa_instance_type) FLAGS.default_vsa_instance_type)
@ -89,16 +94,17 @@ class API(base.Base):
if name is None: if name is None:
raise exception.ApiError(_("No drive_name param found in %s") raise exception.ApiError(_("No drive_name param found in %s")
% node) % node)
# find DB record for this disk
try: try:
drive_ref = drive_types.get_by_name(context, name) vol_type = volume_types.get_volume_type_by_name(context, name)
except exception.NotFound: except exception.NotFound:
raise exception.ApiError(_("Invalid drive type name %s") raise exception.ApiError(_("Invalid drive type name %s")
% name) % name)
self._check_volume_type_correctness(vol_type)
# if size field present - override disk size specified in DB # 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: if shared:
part_size = FLAGS.vsa_part_size_gb part_size = FLAGS.vsa_part_size_gb
@ -110,17 +116,15 @@ class API(base.Base):
size = 0 # special handling for full drives size = 0 # special handling for full drives
for i in range(num_volumes): for i in range(num_volumes):
# volume_name = vsa_name + ("_%s_vol-%d" % (name, i))
volume_name = "drive-%03d" % first_index volume_name = "drive-%03d" % first_index
first_index += 1 first_index += 1
volume_desc = 'BE volume for VSA %s type %s' % \ volume_desc = 'BE volume for VSA %s type %s' % \
(vsa_name, name) (vsa_name, name)
volume = { volume = {
'size': size, 'size': size,
'snapshot_id': None,
'name': volume_name, 'name': volume_name,
'description': volume_desc, 'description': volume_desc,
'drive_ref': drive_ref 'volume_type_id': vol_type['id'],
} }
volume_params.append(volume) volume_params.append(volume)
@ -211,7 +215,7 @@ class API(base.Base):
if len(volume_params) > 0: if len(volume_params) > 0:
request_spec = { request_spec = {
'num_volumes': len(volume_params), 'num_volumes': len(volume_params),
'vsa_id': vsa_id, 'vsa_id': str(vsa_id),
'volumes': volume_params, 'volumes': volume_params,
} }
@ -227,17 +231,21 @@ class API(base.Base):
try: try:
vol_name = vol['name'] vol_name = vol['name']
vol_size = vol['size'] vol_size = vol['size']
vol_type_id = vol['volume_type_id']
LOG.debug(_("VSA ID %(vsa_id)d %(vsa_name)s: Create "\ LOG.debug(_("VSA ID %(vsa_id)d %(vsa_name)s: Create "\
"volume %(vol_name)s, %(vol_size)d GB"), "volume %(vol_name)s, %(vol_size)d GB, "\
locals()) "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_ref = self.volume_api.create(context,
vol_size, vol_size,
vol['snapshot_id'], None,
vol_name, vol_name,
vol['description'], vol['description'],
to_vsa_id=vsa_id, volume_type=vol_type,
drive_type_id=vol['drive_ref'].get('id'), metadata=dict(to_vsa_id=str(vsa_id)),
availability_zone=availability_zone) availability_zone=availability_zone)
except: except:
self.update_vsa_status(context, vsa_id, self.update_vsa_status(context, vsa_id,
@ -249,7 +257,7 @@ class API(base.Base):
rpc.cast(context, rpc.cast(context,
FLAGS.vsa_topic, FLAGS.vsa_topic,
{"method": "create_vsa", {"method": "create_vsa",
"args": {"vsa_id": vsa_id}}) "args": {"vsa_id": str(vsa_id)}})
return vsa_ref return vsa_ref
@ -314,8 +322,7 @@ class API(base.Base):
def _force_volume_delete(self, ctxt, volume): def _force_volume_delete(self, ctxt, volume):
"""Delete a volume, bypassing the check that it must be available.""" """Delete a volume, bypassing the check that it must be available."""
host = volume['host'] host = volume['host']
if not host or volume['from_vsa_id']: if not host:
# Volume not yet assigned to host OR FE volume
# Deleting volume from database and skipping rpc. # Deleting volume from database and skipping rpc.
self.db.volume_destroy(ctxt, volume['id']) self.db.volume_destroy(ctxt, volume['id'])
return return
@ -328,9 +335,9 @@ class API(base.Base):
def delete_vsa_volumes(self, context, vsa_id, direction, def delete_vsa_volumes(self, context, vsa_id, direction,
force_delete=True): force_delete=True):
if direction == "FE": 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: 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: for volume in volumes:
try: try:
@ -374,58 +381,25 @@ class API(base.Base):
return self.db.vsa_get_all(context) return self.db.vsa_get_all(context)
return self.db.vsa_get_all_by_project(context, context.project_id) return self.db.vsa_get_all_by_project(context, context.project_id)
def generate_user_data(self, context, vsa, volumes): def get_vsa_volume_type(self, context):
SubElement = ElementTree.SubElement 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") def get_all_vsa_instances(self, context, vsa_id):
e_vsa_detail.text = str(vsa['id']) return self.compute_api.get_all(context,
e_vsa_detail = SubElement(e_vsa, "name") search_opts={'metadata': dict(vsa_id=str(vsa_id))})
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") def get_all_vsa_volumes(self, context, vsa_id):
e_vsa_detail.text = FLAGS.vsa_ec2_user_id return self.volume_api.get_all(context,
e_vsa_detail = SubElement(e_vsa, "auth_access_key") search_opts={'metadata': dict(from_vsa_id=str(vsa_id))})
e_vsa_detail.text = FLAGS.vsa_ec2_access_key
e_volumes = SubElement(e_vsa, "volumes") def get_all_vsa_drives(self, context, vsa_id):
for volume in volumes: return self.volume_api.get_all(context,
search_opts={'metadata': dict(to_vsa_id=str(vsa_id))})
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)

View File

@ -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)

View File

@ -16,7 +16,7 @@
# under the License. # under the License.
class FakeVcConnection: class FakeVcConnection(object):
def init_host(self, host): def init_host(self, host):
pass pass

View File

@ -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 log as logging
from nova import manager 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 volume
from nova import vsa from nova import vsa
from nova.vsa.api import VsaState from nova import utils
from nova.compute import instance_types from nova.compute import instance_types
from nova.vsa import utils as vsa_utils
from nova.vsa.api import VsaState
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
flags.DEFINE_string('vsa_driver', 'nova.vsa.connection.get_connection', flags.DEFINE_string('vsa_driver', 'nova.vsa.connection.get_connection',
@ -83,18 +83,18 @@ class VsaManager(manager.SchedulerDependentManager):
@exception.wrap_exception() @exception.wrap_exception()
def vsa_volume_created(self, context, vol_id, vsa_id, status): def vsa_volume_created(self, context, vol_id, vsa_id, status):
"""Callback for volume creations""" """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()) "Status %(status)s"), locals())
vsa_id = int(vsa_id) # just in case vsa_id = int(vsa_id) # just in case
# Get all volumes for this VSA # Get all volumes for this VSA
# check if any of them still in creating phase # check if any of them still in creating phase
volumes = self.db.volume_get_all_assigned_to_vsa(context, vsa_id) drives = self.vsa_api.get_all_vsa_drives(context, vsa_id)
for volume in volumes: for drive in drives:
if volume['status'] == 'creating': if drive['status'] == 'creating':
vol_name = volume['name'] vol_name = drive['name']
vol_disp_name = volume['display_name'] vol_disp_name = drive['display_name']
LOG.debug(_("Volume %(vol_name)s (%(vol_disp_name)s) still "\ LOG.debug(_("Drive %(vol_name)s (%(vol_disp_name)s) still "\
"in creating phase - wait"), locals()) "in creating phase - wait"), locals())
return return
@ -105,17 +105,17 @@ class VsaManager(manager.SchedulerDependentManager):
LOG.exception(msg) LOG.exception(msg)
return return
if len(volumes) != vsa['vol_count']: if len(drives) != vsa['vol_count']:
cvol_real = len(volumes) cvol_real = len(drives)
cvol_exp = vsa['vol_count'] cvol_exp = vsa['vol_count']
LOG.debug(_("VSA ID %(vsa_id)d: Not all volumes are created "\ LOG.debug(_("VSA ID %(vsa_id)d: Not all volumes are created "\
"(%(cvol_real)d of %(cvol_exp)d)"), locals()) "(%(cvol_real)d of %(cvol_exp)d)"), locals())
return return
# all volumes created (successfully or not) # 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 """ """Start VCs for VSA """
vsa_id = vsa['id'] vsa_id = vsa['id']
@ -127,11 +127,11 @@ class VsaManager(manager.SchedulerDependentManager):
# in _separate_ loop go over all volumes and mark as "attached" # in _separate_ loop go over all volumes and mark as "attached"
has_failed_volumes = False has_failed_volumes = False
for volume in volumes: for drive in drives:
vol_name = volume['name'] vol_name = drive['name']
vol_disp_name = volume['display_name'] vol_disp_name = drive['display_name']
status = volume['status'] status = drive['status']
LOG.info(_("VSA ID %(vsa_id)d: Volume %(vol_name)s "\ LOG.info(_("VSA ID %(vsa_id)d: Drive %(vol_name)s "\
"(%(vol_disp_name)s) is in %(status)s state"), "(%(vol_disp_name)s) is in %(status)s state"),
locals()) locals())
if status == 'available': if status == 'available':
@ -149,11 +149,12 @@ class VsaManager(manager.SchedulerDependentManager):
if has_failed_volumes: if has_failed_volumes:
LOG.info(_("VSA ID %(vsa_id)d: Delete all BE volumes"), locals()) 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.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 return
# create user-data record for VC # 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( instance_type = instance_types.get_instance_type(
vsa['instance_type_id']) vsa['instance_type_id'])
@ -174,4 +175,5 @@ class VsaManager(manager.SchedulerDependentManager):
user_data=storage_data, user_data=storage_data,
metadata=dict(vsa_id=str(vsa_id))) 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)

80
nova/vsa/utils.py Normal file
View File

@ -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)