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 2d7f401c4f
commit 4834b920e3
33 changed files with 695 additions and 1360 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: else:
raise ValueError(_('Visible parameter should be set to --show '\ volume_types.destroy(self.context, name)
'or --hide')) 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:
print "%s %s" % (name, verb)
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

@@ -1,143 +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.
""" The Drive Types extension for Virtual Storage Arrays"""
from webob import exc
from nova.vsa import drive_types
from nova import exception
from nova import log as logging
from nova.api.openstack import common
from nova.api.openstack import extensions
from nova.api.openstack import faults
LOG = logging.getLogger("nova.api.drive_types")
def _drive_type_view(drive):
"""Maps keys for drive types view."""
d = {}
d['id'] = drive['id']
d['displayName'] = drive['name']
d['type'] = drive['type']
d['size'] = drive['size_gb']
d['rpm'] = drive['rpm']
d['capabilities'] = drive['capabilities']
return d
class DriveTypeController(object):
"""The Drive Type API controller for the OpenStack API."""
_serialization_metadata = {
'application/xml': {
"attributes": {
"drive_type": [
"id",
"displayName",
"type",
"size",
"rpm",
"capabilities",
]}}}
def index(self, req):
"""Returns a list of drive types."""
context = req.environ['nova.context']
dtypes = drive_types.get_all(context)
limited_list = common.limited(dtypes, req)
res = [_drive_type_view(drive) for drive in limited_list]
return {'drive_types': res}
def show(self, req, id):
"""Return data about the given drive type."""
context = req.environ['nova.context']
try:
drive = drive_types.get(context, id)
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
return {'drive_type': _drive_type_view(drive)}
def create(self, req, body):
"""Creates a new drive type."""
context = req.environ['nova.context']
if not body:
return faults.Fault(exc.HTTPUnprocessableEntity())
drive = body['drive_type']
name = drive.get('displayName')
type = drive.get('type')
size = drive.get('size')
rpm = drive.get('rpm')
capabilities = drive.get('capabilities')
LOG.audit(_("Create drive type %(name)s for "\
"%(type)s:%(size)s:%(rpm)s"), locals(), context=context)
new_drive = drive_types.create(context,
type=type,
size_gb=size,
rpm=rpm,
capabilities=capabilities,
name=name)
return {'drive_type': _drive_type_view(new_drive)}
def delete(self, req, id):
"""Deletes a drive type."""
context = req.environ['nova.context']
LOG.audit(_("Delete drive type with id: %s"), id, context=context)
try:
drive_types.delete(context, id)
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
class Drive_types(extensions.ExtensionDescriptor):
def get_name(self):
return "DriveTypes"
def get_alias(self):
return "zadr-drive_types"
def get_description(self):
return "Drive Types support"
def get_namespace(self):
return "http://docs.openstack.org/ext/drive_types/api/v1.1"
def get_updated(self):
return "2011-06-29T00:00:00+00:00"
def get_resources(self):
resources = []
res = extensions.ResourceExtension(
'zadr-drive_types',
DriveTypeController())
resources.append(res)
return resources

View File

@@ -106,6 +106,10 @@ class VsaController(object):
self.network_api = network.API() self.network_api = network.API()
super(VsaController, self).__init__() super(VsaController, self).__init__()
def _get_instances_by_vsa_id(self, context, id):
return self.compute_api.get_all(context,
search_opts={'metadata': dict(vsa_id=str(id))})
def _items(self, req, details): def _items(self, req, details):
"""Return summary or detailed list of VSAs.""" """Return summary or detailed list of VSAs."""
context = req.environ['nova.context'] context = req.environ['nova.context']
@@ -114,8 +118,7 @@ class VsaController(object):
vsa_list = [] vsa_list = []
for vsa in limited_list: for vsa in limited_list:
instances = self.compute_api.get_all(context, instances = self._get_instances_by_vsa_id(context, vsa.get('id'))
search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))})
vsa_list.append(_vsa_view(context, vsa, details, instances)) vsa_list.append(_vsa_view(context, vsa, details, instances))
return {'vsaSet': vsa_list} return {'vsaSet': vsa_list}
@@ -136,9 +139,7 @@ class VsaController(object):
except exception.NotFound: except exception.NotFound:
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
instances = self.compute_api.get_all(context, instances = self._get_instances_by_vsa_id(context, vsa.get('id'))
search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))})
return {'vsa': _vsa_view(context, vsa, True, instances)} return {'vsa': _vsa_view(context, vsa, True, instances)}
def create(self, req, body): def create(self, req, body):
@@ -171,9 +172,7 @@ class VsaController(object):
vsa = self.vsa_api.create(context, **args) vsa = self.vsa_api.create(context, **args)
instances = self.compute_api.get_all(context, instances = self._get_instances_by_vsa_id(context, vsa.get('id'))
search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))})
return {'vsa': _vsa_view(context, vsa, True, instances)} return {'vsa': _vsa_view(context, vsa, True, instances)}
def delete(self, req, id): def delete(self, req, id):
@@ -202,14 +201,14 @@ class VsaController(object):
locals(), context=context) locals(), context=context)
try: try:
instances = self.compute_api.get_all(context, instances = self._get_instances_by_vsa_id(context, id)
search_opts={'metadata': dict(vsa_id=str(id))}) if instances is None or len(instances) == 0:
if instances is None or len(instances)==0:
return faults.Fault(exc.HTTPNotFound()) return faults.Fault(exc.HTTPNotFound())
for instance in instances: for instance in instances:
self.network_api.allocate_for_instance(context, instance, vpn=False) self.network_api.allocate_for_instance(context, instance,
vpn=False)
# Placeholder
return return
except exception.NotFound: except exception.NotFound:
@@ -228,6 +227,7 @@ class VsaController(object):
LOG.audit(_("Disassociate address from VSA %(id)s"), LOG.audit(_("Disassociate address from VSA %(id)s"),
locals(), context=context) locals(), context=context)
# Placeholder
class VsaVolumeDriveController(volumes.VolumeController): class VsaVolumeDriveController(volumes.VolumeController):
@@ -255,6 +255,7 @@ class VsaVolumeDriveController(volumes.VolumeController):
def __init__(self): def __init__(self):
self.volume_api = volume.API() self.volume_api = volume.API()
self.vsa_api = vsa.API()
super(VsaVolumeDriveController, self).__init__() super(VsaVolumeDriveController, self).__init__()
def _translation(self, context, vol, vsa_id, details): def _translation(self, context, vol, vsa_id, details):
@@ -264,7 +265,7 @@ class VsaVolumeDriveController(volumes.VolumeController):
translation = volumes.translate_volume_summary_view translation = volumes.translate_volume_summary_view
d = translation(context, vol) d = translation(context, vol)
d['vsaId'] = vol[self.direction] d['vsaId'] = vsa_id
d['name'] = vol['name'] d['name'] = vol['name']
return d return d
@@ -276,8 +277,9 @@ class VsaVolumeDriveController(volumes.VolumeController):
LOG.error(_("%(obj)s with ID %(id)s not found"), locals()) LOG.error(_("%(obj)s with ID %(id)s not found"), locals())
raise raise
own_vsa_id = volume_ref[self.direction] own_vsa_id = self.volume_api.get_volume_metadata_value(volume_ref,
if own_vsa_id != int(vsa_id): self.direction)
if own_vsa_id != vsa_id:
LOG.error(_("%(obj)s with ID %(id)s belongs to VSA %(own_vsa_id)s"\ LOG.error(_("%(obj)s with ID %(id)s belongs to VSA %(own_vsa_id)s"\
" and not to VSA %(vsa_id)s."), locals()) " and not to VSA %(vsa_id)s."), locals())
raise exception.Invalid() raise exception.Invalid()
@@ -286,8 +288,8 @@ class VsaVolumeDriveController(volumes.VolumeController):
"""Return summary or detailed list of volumes for particular VSA.""" """Return summary or detailed list of volumes for particular VSA."""
context = req.environ['nova.context'] context = req.environ['nova.context']
vols = self.volume_api.get_all_by_vsa(context, vsa_id, vols = self.volume_api.get_all(context,
self.direction.split('_')[0]) search_opts={'metadata': {self.direction: str(vsa_id)}})
limited_list = common.limited(vols, req) limited_list = common.limited(vols, req)
res = [self._translation(context, vol, vsa_id, details) \ res = [self._translation(context, vol, vsa_id, details) \
@@ -317,11 +319,19 @@ class VsaVolumeDriveController(volumes.VolumeController):
size = vol['size'] size = vol['size']
LOG.audit(_("Create volume of %(size)s GB from VSA ID %(vsa_id)s"), LOG.audit(_("Create volume of %(size)s GB from VSA ID %(vsa_id)s"),
locals(), context=context) locals(), context=context)
try:
# create is supported for volumes only (drives created through VSA)
volume_type = self.vsa_api.get_vsa_volume_type(context)
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
new_volume = self.volume_api.create(context, size, None, new_volume = self.volume_api.create(context,
size,
None,
vol.get('displayName'), vol.get('displayName'),
vol.get('displayDescription'), vol.get('displayDescription'),
from_vsa_id=vsa_id) volume_type=volume_type,
metadata=dict(from_vsa_id=str(vsa_id)))
return {self.object: self._translation(context, new_volume, return {self.object: self._translation(context, new_volume,
vsa_id, True)} vsa_id, True)}

View File

@@ -918,16 +918,6 @@ def volume_get_all_by_project(context, project_id):
return IMPL.volume_get_all_by_project(context, project_id) return IMPL.volume_get_all_by_project(context, project_id)
def volume_get_all_assigned_to_vsa(context, vsa_id):
"""Get all volumes assigned to particular VSA."""
return IMPL.volume_get_all_assigned_to_vsa(context, vsa_id)
def volume_get_all_assigned_from_vsa(context, vsa_id):
"""Get all volumes created from particular VSA."""
return IMPL.volume_get_all_assigned_from_vsa(context, vsa_id)
def volume_get_by_ec2_id(context, ec2_id): def volume_get_by_ec2_id(context, ec2_id):
"""Get a volume by ec2 id.""" """Get a volume by ec2 id."""
return IMPL.volume_get_by_ec2_id(context, ec2_id) return IMPL.volume_get_by_ec2_id(context, ec2_id)
@@ -1528,36 +1518,6 @@ def volume_type_extra_specs_update_or_create(context, volume_type_id,
#################### ####################
def drive_type_create(context, values):
"""Creates drive type record."""
return IMPL.drive_type_create(context, values)
def drive_type_update(context, drive_type_id, values):
"""Updates drive type record."""
return IMPL.drive_type_update(context, drive_type_id, values)
def drive_type_destroy(context, drive_type_id):
"""Deletes drive type record."""
return IMPL.drive_type_destroy(context, drive_type_id)
def drive_type_get(context, drive_type_id):
"""Get drive type record by id."""
return IMPL.drive_type_get(context, drive_type_id)
def drive_type_get_by_name(context, name):
"""Get drive type record by name."""
return IMPL.drive_type_get_by_name(context, name)
def drive_type_get_all(context, visible):
"""Returns all (or only visible) drive types."""
return IMPL.drive_type_get_all(context, visible)
def vsa_create(context, values): def vsa_create(context, values):
"""Creates Virtual Storage Array record.""" """Creates Virtual Storage Array record."""
return IMPL.vsa_create(context, values) return IMPL.vsa_create(context, values)
@@ -1586,8 +1546,3 @@ def vsa_get_all(context):
def vsa_get_all_by_project(context, project_id): def vsa_get_all_by_project(context, project_id):
"""Get all Virtual Storage Array records by project ID.""" """Get all Virtual Storage Array records by project ID."""
return IMPL.vsa_get_all_by_project(context, project_id) return IMPL.vsa_get_all_by_project(context, project_id)
def vsa_get_vc_ips_list(context, vsa_id):
"""Retrieves IPs of instances associated with Virtual Storage Array."""
return IMPL.vsa_get_vc_ips_list(context, vsa_id)

View File

@@ -2226,7 +2226,6 @@ def volume_get(context, volume_id, session=None):
options(joinedload('instance')).\ options(joinedload('instance')).\
options(joinedload('volume_metadata')).\ options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\ options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(id=volume_id).\ filter_by(id=volume_id).\
filter_by(deleted=can_read_deleted(context)).\ filter_by(deleted=can_read_deleted(context)).\
first() first()
@@ -2235,7 +2234,6 @@ def volume_get(context, volume_id, session=None):
options(joinedload('instance')).\ options(joinedload('instance')).\
options(joinedload('volume_metadata')).\ options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\ options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(project_id=context.project_id).\ filter_by(project_id=context.project_id).\
filter_by(id=volume_id).\ filter_by(id=volume_id).\
filter_by(deleted=False).\ filter_by(deleted=False).\
@@ -2253,7 +2251,6 @@ def volume_get_all(context):
options(joinedload('instance')).\ options(joinedload('instance')).\
options(joinedload('volume_metadata')).\ options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\ options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(deleted=can_read_deleted(context)).\ filter_by(deleted=can_read_deleted(context)).\
all() all()
@@ -2265,7 +2262,6 @@ def volume_get_all_by_host(context, host):
options(joinedload('instance')).\ options(joinedload('instance')).\
options(joinedload('volume_metadata')).\ options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\ options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(host=host).\ filter_by(host=host).\
filter_by(deleted=can_read_deleted(context)).\ filter_by(deleted=can_read_deleted(context)).\
all() all()
@@ -2277,7 +2273,6 @@ def volume_get_all_by_instance(context, instance_id):
result = session.query(models.Volume).\ result = session.query(models.Volume).\
options(joinedload('volume_metadata')).\ options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\ options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(instance_id=instance_id).\ filter_by(instance_id=instance_id).\
filter_by(deleted=False).\ filter_by(deleted=False).\
all() all()
@@ -2286,28 +2281,6 @@ def volume_get_all_by_instance(context, instance_id):
return result return result
@require_admin_context
def volume_get_all_assigned_to_vsa(context, vsa_id):
session = get_session()
result = session.query(models.Volume).\
options(joinedload('drive_type')).\
filter_by(to_vsa_id=vsa_id).\
filter_by(deleted=False).\
all()
return result
@require_admin_context
def volume_get_all_assigned_from_vsa(context, vsa_id):
session = get_session()
result = session.query(models.Volume).\
options(joinedload('drive_type')).\
filter_by(from_vsa_id=vsa_id).\
filter_by(deleted=False).\
all()
return result
@require_context @require_context
def volume_get_all_by_project(context, project_id): def volume_get_all_by_project(context, project_id):
authorize_project_context(context, project_id) authorize_project_context(context, project_id)
@@ -2317,7 +2290,6 @@ def volume_get_all_by_project(context, project_id):
options(joinedload('instance')).\ options(joinedload('instance')).\
options(joinedload('volume_metadata')).\ options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\ options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(project_id=project_id).\ filter_by(project_id=project_id).\
filter_by(deleted=can_read_deleted(context)).\ filter_by(deleted=can_read_deleted(context)).\
all() all()
@@ -2332,7 +2304,6 @@ def volume_get_instance(context, volume_id):
options(joinedload('instance')).\ options(joinedload('instance')).\
options(joinedload('volume_metadata')).\ options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\ options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
first() first()
if not result: if not result:
raise exception.VolumeNotFound(volume_id=volume_id) raise exception.VolumeNotFound(volume_id=volume_id)
@@ -2377,7 +2348,7 @@ def volume_update(context, volume_id, values):
volume_ref = volume_get(context, volume_id, session=session) volume_ref = volume_get(context, volume_id, session=session)
volume_ref.update(values) volume_ref.update(values)
volume_ref.save(session=session) volume_ref.save(session=session)
return volume_ref
#################### ####################
@@ -3871,106 +3842,6 @@ def volume_type_extra_specs_update_or_create(context, volume_type_id,
#################### ####################
@require_admin_context
def drive_type_create(context, values):
"""
Creates drive type record.
"""
try:
drive_type_ref = models.DriveTypes()
drive_type_ref.update(values)
drive_type_ref.save()
except Exception, e:
raise exception.DBError(e)
return drive_type_ref
@require_admin_context
def drive_type_update(context, drive_type_id, values):
"""
Updates drive type record.
"""
session = get_session()
with session.begin():
drive_type_ref = drive_type_get(context, drive_type_id,
session=session)
drive_type_ref.update(values)
drive_type_ref.save(session=session)
return drive_type_ref
@require_admin_context
def drive_type_destroy(context, drive_type_id):
"""
Deletes drive type record.
"""
session = get_session()
drive_type_ref = session.query(models.DriveTypes).\
filter_by(id=drive_type_id)
records = drive_type_ref.delete()
if records == 0:
raise exception.VirtualDiskTypeNotFound(id=drive_type_id)
@require_context
def drive_type_get(context, drive_type_id, session=None):
"""
Get drive type record by id.
"""
if not session:
session = get_session()
result = session.query(models.DriveTypes).\
filter_by(id=drive_type_id).\
filter_by(deleted=can_read_deleted(context)).\
first()
if not result:
raise exception.VirtualDiskTypeNotFound(id=drive_type_id)
return result
@require_context
def drive_type_get_by_name(context, name, session=None):
"""
Get drive type record by name.
"""
if not session:
session = get_session()
result = session.query(models.DriveTypes).\
filter_by(name=name).\
filter_by(deleted=can_read_deleted(context)).\
first()
if not result:
raise exception.VirtualDiskTypeNotFoundByName(name=name)
return result
@require_context
def drive_type_get_all(context, visible):
"""
Returns all (or only visible) drive types.
"""
session = get_session()
if visible:
drive_types = session.query(models.DriveTypes).\
filter_by(deleted=can_read_deleted(context)).\
filter_by(visible=True).\
order_by("name").\
all()
else:
drive_types = session.query(models.DriveTypes).\
filter_by(deleted=can_read_deleted(context)).\
order_by("name").\
all()
return drive_types
####################
@require_admin_context @require_admin_context
def vsa_create(context, values): def vsa_create(context, values):
""" """
@@ -4067,26 +3938,4 @@ def vsa_get_all_by_project(context, project_id):
all() all()
@require_context
def vsa_get_vc_ips_list(context, vsa_id):
"""
Retrieves IPs of instances associated with Virtual Storage Array.
"""
result = []
vc_instances = instance_get_all_by_filters(context,
search_opts={'metadata': dict(vsa_id=str(vsa_id))})
for vc_instance in vc_instances:
if vc_instance['fixed_ips']:
for fixed in vc_instance['fixed_ips']:
# insert the [floating,fixed] (if exists) in the head,
# otherwise append the [none,fixed] in the tail
ip = {}
ip['fixed'] = fixed['address']
if fixed['floating_ips']:
ip['floating'] = fixed['floating_ips'][0]['address']
result.append(ip)
return result
#################### ####################

View File

@@ -22,19 +22,7 @@ from nova import log as logging
meta = MetaData() meta = MetaData()
# Just for the ForeignKey and column creation to succeed, these are not the
# actual definitions of tables .
# #
volumes = Table('volumes', meta,
Column('id', Integer(), primary_key=True, nullable=False),
)
to_vsa_id = Column('to_vsa_id', Integer(), nullable=True)
from_vsa_id = Column('from_vsa_id', Integer(), nullable=True)
drive_type_id = Column('drive_type_id', Integer(), nullable=True)
# New Tables # New Tables
# #
@@ -67,67 +55,21 @@ virtual_storage_arrays = Table('virtual_storage_arrays', meta,
unicode_error=None, _warn_on_bytestring=False)), unicode_error=None, _warn_on_bytestring=False)),
) )
drive_types = Table('drive_types', meta,
Column('created_at', DateTime(timezone=False)),
Column('updated_at', DateTime(timezone=False)),
Column('deleted_at', DateTime(timezone=False)),
Column('deleted', Boolean(create_constraint=True, name=None)),
Column('id', Integer(), primary_key=True, nullable=False),
Column('name',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False),
unique=True),
Column('type',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('size_gb', Integer(), nullable=False),
Column('rpm',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('capabilities',
String(length=255, convert_unicode=False, assert_unicode=None,
unicode_error=None, _warn_on_bytestring=False)),
Column('visible', Boolean(create_constraint=True, name=None)),
)
new_tables = (virtual_storage_arrays, drive_types)
#
# Tables to alter
#
def upgrade(migrate_engine): def upgrade(migrate_engine):
from nova import context
from nova import db
from nova import flags
FLAGS = flags.FLAGS
# Upgrade operations go here. Don't create your own engine; # Upgrade operations go here. Don't create your own engine;
# bind migrate_engine to your metadata # bind migrate_engine to your metadata
meta.bind = migrate_engine meta.bind = migrate_engine
for table in new_tables:
try: try:
table.create() virtual_storage_arrays.create()
except Exception: except Exception:
logging.info(repr(table)) logging.info(repr(table))
logging.exception('Exception while creating table') logging.exception('Exception while creating table')
raise raise
volumes.create_column(to_vsa_id)
volumes.create_column(from_vsa_id)
volumes.create_column(drive_type_id)
def downgrade(migrate_engine): def downgrade(migrate_engine):
meta.bind = migrate_engine meta.bind = migrate_engine
volumes.drop_column(to_vsa_id) virtual_storage_arrays.drop()
volumes.drop_column(from_vsa_id)
volumes.drop_column(drive_type_id)
for table in new_tables:
table.drop()

View File

@@ -352,13 +352,6 @@ class Volume(BASE, NovaBase):
volume_type_id = Column(Integer) volume_type_id = Column(Integer)
to_vsa_id = Column(Integer,
ForeignKey('virtual_storage_arrays.id'), nullable=True)
from_vsa_id = Column(Integer,
ForeignKey('virtual_storage_arrays.id'), nullable=True)
drive_type_id = Column(Integer,
ForeignKey('drive_types.id'), nullable=True)
class VolumeMetadata(BASE, NovaBase): class VolumeMetadata(BASE, NovaBase):
"""Represents a metadata key/value pair for a volume""" """Represents a metadata key/value pair for a volume"""
@@ -402,38 +395,6 @@ class VolumeTypeExtraSpecs(BASE, NovaBase):
'VolumeTypeExtraSpecs.deleted == False)') 'VolumeTypeExtraSpecs.deleted == False)')
class DriveTypes(BASE, NovaBase):
"""Represents the known drive types (storage media)."""
__tablename__ = 'drive_types'
id = Column(Integer, primary_key=True, autoincrement=True)
"""
@property
def name(self):
if self.capabilities:
return FLAGS.drive_type_template_long % \
(self.type, str(self.size_gb), self.rpm, self.capabilities)
else:
return FLAGS.drive_type_template_short % \
(self.type, str(self.size_gb), self.rpm)
"""
name = Column(String(255), unique=True)
type = Column(String(255))
size_gb = Column(Integer)
rpm = Column(String(255))
capabilities = Column(String(255))
visible = Column(Boolean, default=True)
volumes = relationship(Volume,
backref=backref('drive_type', uselist=False),
foreign_keys=id,
primaryjoin='and_(Volume.drive_type_id == '
'DriveTypes.id)')
class Quota(BASE, NovaBase): class Quota(BASE, NovaBase):
"""Represents a single quota override for a project. """Represents a single quota override for a project.
@@ -918,7 +879,9 @@ def register_models():
Network, SecurityGroup, SecurityGroupIngressRule, Network, SecurityGroup, SecurityGroupIngressRule,
SecurityGroupInstanceAssociation, AuthToken, User, SecurityGroupInstanceAssociation, AuthToken, User,
Project, Certificate, ConsolePool, Console, Zone, Project, Certificate, ConsolePool, Console, Zone,
AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration) VolumeMetadata, VolumeTypes, VolumeTypeExtraSpecs,
AgentBuild, InstanceMetadata, InstanceTypeExtraSpecs, Migration,
VirtualStorageArray)
engine = create_engine(FLAGS.sql_connection, echo=False) engine = create_engine(FLAGS.sql_connection, echo=False)
for model in models: for model in models:
model.metadata.create_all(engine) model.metadata.create_all(engine)

View File

@@ -30,9 +30,11 @@ import nova.exception
import nova.flags import nova.flags
import nova.log import nova.log
FLAGS = nova.flags.FLAGS FLAGS = nova.flags.FLAGS
LOG = nova.log.getLogger("nova.db.sqlalchemy") LOG = nova.log.getLogger("nova.db.sqlalchemy")
try: try:
import MySQLdb import MySQLdb
except ImportError: except ImportError:

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

@@ -508,7 +508,6 @@ def get_dhcp_hosts(context, network_ref):
if network_ref['multi_host'] and FLAGS.host != host: if network_ref['multi_host'] and FLAGS.host != host:
continue continue
hosts.append(_host_dhcp(fixed_ref)) hosts.append(_host_dhcp(fixed_ref))
return '\n'.join(hosts) return '\n'.join(hosts)

View File

@@ -116,6 +116,7 @@ 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'])
if size != 0:
allowed_volumes = min(allowed_volumes, allowed_volumes = min(allowed_volumes,
int(allowed_gigabytes // size)) 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

@@ -1,192 +0,0 @@
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import json
import stubout
import webob
#from nova import compute
from nova.vsa import drive_types
from nova import exception
from nova import context
from nova import test
from nova import log as logging
from nova.tests.api.openstack import fakes
from nova.api.openstack.contrib.drive_types import _drive_type_view
LOG = logging.getLogger('nova.tests.api.openstack.drive_types')
last_param = {}
def _get_default_drive_type():
param = {
'name': 'Test drive type',
'type': 'SATA',
'size_gb': 123,
'rpm': '7200',
'capabilities': '',
'visible': True
}
return param
def _create(context, **param):
global last_param
LOG.debug(_("_create: %s"), param)
param['id'] = 123
last_param = param
return param
def _delete(context, id):
global last_param
last_param = dict(id=id)
LOG.debug(_("_delete: %s"), locals())
def _get(context, id):
global last_param
last_param = dict(id=id)
LOG.debug(_("_get: %s"), locals())
if id != '123':
raise exception.NotFound
dtype = _get_default_drive_type()
dtype['id'] = id
return dtype
def _get_all(context, visible=True):
LOG.debug(_("_get_all: %s"), locals())
dtype = _get_default_drive_type()
dtype['id'] = 123
return [dtype]
class DriveTypesApiTest(test.TestCase):
def setUp(self):
super(DriveTypesApiTest, self).setUp()
self.stubs = stubout.StubOutForTesting()
fakes.FakeAuthManager.reset_fake_data()
fakes.FakeAuthDatabase.data = {}
fakes.stub_out_networking(self.stubs)
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_auth(self.stubs)
self.stubs.Set(drive_types, "create", _create)
self.stubs.Set(drive_types, "delete", _delete)
self.stubs.Set(drive_types, "get", _get)
self.stubs.Set(drive_types, "get_all", _get_all)
self.context = context.get_admin_context()
def tearDown(self):
self.stubs.UnsetAll()
super(DriveTypesApiTest, self).tearDown()
def test_drive_types_api_create(self):
global last_param
last_param = {}
dtype = _get_default_drive_type()
dtype['id'] = 123
body = dict(drive_type=_drive_type_view(dtype))
req = webob.Request.blank('/v1.1/zadr-drive_types')
req.method = 'POST'
req.body = json.dumps(body)
req.headers['content-type'] = 'application/json'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
# Compare if parameters were correctly passed to stub
for k, v in last_param.iteritems():
self.assertEqual(last_param[k], dtype[k])
resp_dict = json.loads(resp.body)
# Compare response
self.assertTrue('drive_type' in resp_dict)
resp_dtype = resp_dict['drive_type']
self.assertEqual(resp_dtype, _drive_type_view(dtype))
def test_drive_types_api_delete(self):
global last_param
last_param = {}
dtype_id = 123
req = webob.Request.blank('/v1.1/zadr-drive_types/%d' % dtype_id)
req.method = 'DELETE'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
self.assertEqual(str(last_param['id']), str(dtype_id))
def test_drive_types_show(self):
global last_param
last_param = {}
dtype_id = 123
req = webob.Request.blank('/v1.1/zadr-drive_types/%d' % dtype_id)
req.method = 'GET'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
self.assertEqual(str(last_param['id']), str(dtype_id))
resp_dict = json.loads(resp.body)
# Compare response
self.assertTrue('drive_type' in resp_dict)
resp_dtype = resp_dict['drive_type']
exp_dtype = _get_default_drive_type()
exp_dtype['id'] = dtype_id
exp_dtype_view = _drive_type_view(exp_dtype)
for k, v in exp_dtype_view.iteritems():
self.assertEqual(str(resp_dtype[k]), str(exp_dtype_view[k]))
def test_drive_types_show_invalid_id(self):
global last_param
last_param = {}
dtype_id = 234
req = webob.Request.blank('/v1.1/zadr-drive_types/%d' % dtype_id)
req.method = 'GET'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 404)
self.assertEqual(str(last_param['id']), str(dtype_id))
def test_drive_types_index(self):
req = webob.Request.blank('/v1.1/zadr-drive_types')
req.method = 'GET'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
resp_dict = json.loads(resp.body)
self.assertTrue('drive_types' in resp_dict)
resp_dtypes = resp_dict['drive_types']
self.assertEqual(len(resp_dtypes), 1)
resp_dtype = resp_dtypes.pop()
exp_dtype = _get_default_drive_type()
exp_dtype['id'] = 123
exp_dtype_view = _drive_type_view(exp_dtype)
for k, v in exp_dtype_view.iteritems():
self.assertEqual(str(resp_dtype[k]), str(exp_dtype_view[k]))

View File

@@ -18,15 +18,14 @@ import stubout
import unittest import unittest
import webob import webob
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 vsa
from nova import db
from nova import volume
from nova import context
from nova import test
from nova import log as logging from nova import log as logging
from nova import test
from nova import volume
from nova import vsa
from nova.api import openstack from nova.api import openstack
from nova.tests.api.openstack import fakes from nova.tests.api.openstack import fakes
import nova.wsgi import nova.wsgi
@@ -120,7 +119,7 @@ class VSAApiTest(test.TestCase):
vsa = {"displayName": "VSA Test Name", vsa = {"displayName": "VSA Test Name",
"displayDescription": "VSA Test Desc"} "displayDescription": "VSA Test Desc"}
body = dict(vsa=vsa) body = dict(vsa=vsa)
req = webob.Request.blank('/v1.1/zadr-vsa') req = webob.Request.blank('/v1.1/777/zadr-vsa')
req.method = 'POST' req.method = 'POST'
req.body = json.dumps(body) req.body = json.dumps(body)
req.headers['content-type'] = 'application/json' req.headers['content-type'] = 'application/json'
@@ -139,7 +138,7 @@ class VSAApiTest(test.TestCase):
vsa['displayDescription']) vsa['displayDescription'])
def test_vsa_create_no_body(self): def test_vsa_create_no_body(self):
req = webob.Request.blank('/v1.1/zadr-vsa') req = webob.Request.blank('/v1.1/777/zadr-vsa')
req.method = 'POST' req.method = 'POST'
req.body = json.dumps({}) req.body = json.dumps({})
req.headers['content-type'] = 'application/json' req.headers['content-type'] = 'application/json'
@@ -152,7 +151,7 @@ class VSAApiTest(test.TestCase):
last_param = {} last_param = {}
vsa_id = 123 vsa_id = 123
req = webob.Request.blank('/v1.1/zadr-vsa/%d' % vsa_id) req = webob.Request.blank('/v1.1/777/zadr-vsa/%d' % vsa_id)
req.method = 'DELETE' req.method = 'DELETE'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
@@ -164,7 +163,7 @@ class VSAApiTest(test.TestCase):
last_param = {} last_param = {}
vsa_id = 234 vsa_id = 234
req = webob.Request.blank('/v1.1/zadr-vsa/%d' % vsa_id) req = webob.Request.blank('/v1.1/777/zadr-vsa/%d' % vsa_id)
req.method = 'DELETE' req.method = 'DELETE'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
@@ -176,7 +175,7 @@ class VSAApiTest(test.TestCase):
last_param = {} last_param = {}
vsa_id = 123 vsa_id = 123
req = webob.Request.blank('/v1.1/zadr-vsa/%d' % vsa_id) req = webob.Request.blank('/v1.1/777/zadr-vsa/%d' % vsa_id)
req.method = 'GET' req.method = 'GET'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
@@ -191,14 +190,14 @@ class VSAApiTest(test.TestCase):
last_param = {} last_param = {}
vsa_id = 234 vsa_id = 234
req = webob.Request.blank('/v1.1/zadr-vsa/%d' % vsa_id) req = webob.Request.blank('/v1.1/777/zadr-vsa/%d' % vsa_id)
req.method = 'GET' req.method = 'GET'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 404) self.assertEqual(resp.status_int, 404)
self.assertEqual(str(last_param['vsa_id']), str(vsa_id)) self.assertEqual(str(last_param['vsa_id']), str(vsa_id))
def test_vsa_index(self): def test_vsa_index(self):
req = webob.Request.blank('/v1.1/zadr-vsa') req = webob.Request.blank('/v1.1/777/zadr-vsa')
req.method = 'GET' req.method = 'GET'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
@@ -213,7 +212,7 @@ class VSAApiTest(test.TestCase):
self.assertEqual(resp_vsa['id'], 123) self.assertEqual(resp_vsa['id'], 123)
def test_vsa_detail(self): def test_vsa_detail(self):
req = webob.Request.blank('/v1.1/zadr-vsa/detail') req = webob.Request.blank('/v1.1/777/zadr-vsa/detail')
req.method = 'GET' req.method = 'GET'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
@@ -239,17 +238,21 @@ def _get_default_volume_param():
'name': 'vol name', 'name': 'vol name',
'display_name': 'Default vol name', 'display_name': 'Default vol name',
'display_description': 'Default vol description', 'display_description': 'Default vol description',
'from_vsa_id': None, 'volume_type_id': 1,
'to_vsa_id': None, 'volume_metadata': [],
} }
def stub_get_vsa_volume_type(self, context):
return {'id': 1,
'name': 'VSA volume type',
'extra_specs': {'type': 'vsa_volume'}}
def stub_volume_create(self, context, size, snapshot_id, name, description, def stub_volume_create(self, context, size, snapshot_id, name, description,
**param): **param):
LOG.debug(_("_create: param=%s"), size) LOG.debug(_("_create: param=%s"), size)
vol = _get_default_volume_param() vol = _get_default_volume_param()
for k, v in param.iteritems():
vol[k] = v
vol['size'] = size vol['size'] = size
vol['display_name'] = name vol['display_name'] = name
vol['display_description'] = description vol['display_description'] = description
@@ -270,10 +273,10 @@ def stub_volume_get(self, context, volume_id):
LOG.debug(_("_volume_get: volume_id=%s"), volume_id) LOG.debug(_("_volume_get: volume_id=%s"), volume_id)
vol = _get_default_volume_param() vol = _get_default_volume_param()
vol['id'] = volume_id vol['id'] = volume_id
if volume_id == '234': meta = {'key': 'from_vsa_id', 'value': '123'}
vol['from_vsa_id'] = 123
if volume_id == '345': if volume_id == '345':
vol['to_vsa_id'] = 123 meta = {'key': 'to_vsa_id', 'value': '123'}
vol['volume_metadata'].append(meta)
return vol return vol
@@ -281,9 +284,9 @@ def stub_volume_get_notfound(self, context, volume_id):
raise exception.NotFound raise exception.NotFound
def stub_volume_get_all_by_vsa(self, context, vsa_id, direction): def stub_volume_get_all(self, context, search_opts):
vol = stub_volume_get(self, context, '123') vol = stub_volume_get(self, context, '123')
vol['%s_vsa_id' % direction] = vsa_id vol['metadata'] = search_opts['metadata']
return [vol] return [vol]
@@ -302,13 +305,13 @@ class VSAVolumeApiTest(test.TestCase):
fakes.stub_out_rate_limiting(self.stubs) fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_auth(self.stubs) fakes.stub_out_auth(self.stubs)
self.stubs.Set(nova.db.api, 'vsa_get', return_vsa) self.stubs.Set(nova.db.api, 'vsa_get', return_vsa)
self.stubs.Set(vsa.api.API, "get_vsa_volume_type",
stub_get_vsa_volume_type)
self.stubs.Set(volume.api.API, "create", stub_volume_create)
self.stubs.Set(volume.api.API, "update", stub_volume_update) self.stubs.Set(volume.api.API, "update", stub_volume_update)
self.stubs.Set(volume.api.API, "delete", stub_volume_delete) self.stubs.Set(volume.api.API, "delete", stub_volume_delete)
self.stubs.Set(volume.api.API, "get_all_by_vsa",
stub_volume_get_all_by_vsa)
self.stubs.Set(volume.api.API, "get", stub_volume_get) self.stubs.Set(volume.api.API, "get", stub_volume_get)
self.stubs.Set(volume.api.API, "get_all", stub_volume_get_all)
self.context = context.get_admin_context() self.context = context.get_admin_context()
self.test_obj = test_obj if test_obj else "volume" self.test_obj = test_obj if test_obj else "volume"
@@ -319,11 +322,13 @@ class VSAVolumeApiTest(test.TestCase):
super(VSAVolumeApiTest, self).tearDown() super(VSAVolumeApiTest, self).tearDown()
def test_vsa_volume_create(self): def test_vsa_volume_create(self):
self.stubs.Set(volume.api.API, "create", stub_volume_create)
vol = {"size": 100, vol = {"size": 100,
"displayName": "VSA Volume Test Name", "displayName": "VSA Volume Test Name",
"displayDescription": "VSA Volume Test Desc"} "displayDescription": "VSA Volume Test Desc"}
body = {self.test_obj: vol} body = {self.test_obj: vol}
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s' % self.test_objs) req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s' % self.test_objs)
req.method = 'POST' req.method = 'POST'
req.body = json.dumps(body) req.body = json.dumps(body)
req.headers['content-type'] = 'application/json' req.headers['content-type'] = 'application/json'
@@ -344,7 +349,7 @@ class VSAVolumeApiTest(test.TestCase):
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
def test_vsa_volume_create_no_body(self): def test_vsa_volume_create_no_body(self):
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s' % self.test_objs) req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s' % self.test_objs)
req.method = 'POST' req.method = 'POST'
req.body = json.dumps({}) req.body = json.dumps({})
req.headers['content-type'] = 'application/json' req.headers['content-type'] = 'application/json'
@@ -356,25 +361,25 @@ class VSAVolumeApiTest(test.TestCase):
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
def test_vsa_volume_index(self): def test_vsa_volume_index(self):
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s' % self.test_objs) req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s' % self.test_objs)
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
def test_vsa_volume_detail(self): def test_vsa_volume_detail(self):
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s/detail' % \ req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s/detail' % \
self.test_objs) self.test_objs)
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
def test_vsa_volume_show(self): def test_vsa_volume_show(self):
obj_num = 234 if self.test_objs == "volumes" else 345 obj_num = 234 if self.test_objs == "volumes" else 345
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s/%s' % \ req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s/%s' % \
(self.test_objs, obj_num)) (self.test_objs, obj_num))
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200) self.assertEqual(resp.status_int, 200)
def test_vsa_volume_show_no_vsa_assignment(self): def test_vsa_volume_show_no_vsa_assignment(self):
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s/333' % \ req = webob.Request.blank('/v1.1/777/zadr-vsa/4/%s/333' % \
(self.test_objs)) (self.test_objs))
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
@@ -382,7 +387,7 @@ class VSAVolumeApiTest(test.TestCase):
def test_vsa_volume_show_no_volume(self): def test_vsa_volume_show_no_volume(self):
self.stubs.Set(volume.api.API, "get", stub_volume_get_notfound) self.stubs.Set(volume.api.API, "get", stub_volume_get_notfound)
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s/333' % \ req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s/333' % \
(self.test_objs)) (self.test_objs))
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 404) self.assertEqual(resp.status_int, 404)
@@ -392,7 +397,7 @@ class VSAVolumeApiTest(test.TestCase):
update = {"status": "available", update = {"status": "available",
"displayName": "Test Display name"} "displayName": "Test Display name"}
body = {self.test_obj: update} body = {self.test_obj: update}
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s/%s' % \ req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s/%s' % \
(self.test_objs, obj_num)) (self.test_objs, obj_num))
req.method = 'PUT' req.method = 'PUT'
req.body = json.dumps(body) req.body = json.dumps(body)
@@ -406,7 +411,7 @@ class VSAVolumeApiTest(test.TestCase):
def test_vsa_volume_delete(self): def test_vsa_volume_delete(self):
obj_num = 234 if self.test_objs == "volumes" else 345 obj_num = 234 if self.test_objs == "volumes" else 345
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s/%s' % \ req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s/%s' % \
(self.test_objs, obj_num)) (self.test_objs, obj_num))
req.method = 'DELETE' req.method = 'DELETE'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
@@ -416,7 +421,7 @@ class VSAVolumeApiTest(test.TestCase):
self.assertEqual(resp.status_int, 400) self.assertEqual(resp.status_int, 400)
def test_vsa_volume_delete_no_vsa_assignment(self): def test_vsa_volume_delete_no_vsa_assignment(self):
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s/333' % \ req = webob.Request.blank('/v1.1/777/zadr-vsa/4/%s/333' % \
(self.test_objs)) (self.test_objs))
req.method = 'DELETE' req.method = 'DELETE'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())
@@ -425,7 +430,7 @@ class VSAVolumeApiTest(test.TestCase):
def test_vsa_volume_delete_no_volume(self): def test_vsa_volume_delete_no_volume(self):
self.stubs.Set(volume.api.API, "get", stub_volume_get_notfound) self.stubs.Set(volume.api.API, "get", stub_volume_get_notfound)
req = webob.Request.blank('/v1.1/zadr-vsa/123/%s/333' % \ req = webob.Request.blank('/v1.1/777/zadr-vsa/123/%s/333' % \
(self.test_objs)) (self.test_objs))
req.method = 'DELETE' req.method = 'DELETE'
resp = req.get_response(fakes.wsgi_app()) resp = req.get_response(fakes.wsgi_app())

View File

@@ -85,7 +85,6 @@ class ExtensionControllerTest(test.TestCase):
ext_path = os.path.join(os.path.dirname(__file__), "extensions") ext_path = os.path.join(os.path.dirname(__file__), "extensions")
self.flags(osapi_extensions_path=ext_path) self.flags(osapi_extensions_path=ext_path)
self.ext_list = [ self.ext_list = [
"DriveTypes",
"Createserverext", "Createserverext",
"FlavorExtraSpecs", "FlavorExtraSpecs",
"Floating_ips", "Floating_ips",
@@ -96,8 +95,8 @@ class ExtensionControllerTest(test.TestCase):
"Quotas", "Quotas",
"Rescue", "Rescue",
"SecurityGroups", "SecurityGroups",
"VirtualInterfaces",
"VSAs", "VSAs",
"VirtualInterfaces",
"Volumes", "Volumes",
"VolumeTypes", "VolumeTypes",
] ]

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

@@ -135,8 +135,6 @@ flags.DEFINE_string('default_local_format',
None, None,
'The default format a local_volume will be formatted with ' 'The default format a local_volume will be formatted with '
'on creation.') 'on creation.')
flags.DEFINE_bool('libvirt_use_virtio_for_bridges', flags.DEFINE_bool('libvirt_use_virtio_for_bridges',
False, False,
'Use virtio for bridge interfaces') 'Use virtio for bridge interfaces')
@@ -1088,7 +1086,8 @@ class LibvirtConnection(driver.ComputeDriver):
'ebs_root': ebs_root, 'ebs_root': ebs_root,
'local_device': local_device, 'local_device': local_device,
'volumes': block_device_mapping, 'volumes': block_device_mapping,
'use_virtio_for_bridges': FLAGS.libvirt_use_virtio_for_bridges, 'use_virtio_for_bridges':
FLAGS.libvirt_use_virtio_for_bridges,
'ephemerals': ephemerals} 'ephemerals': ephemerals}
root_device_name = driver.block_device_info_get_root(block_device_info) root_device_name = driver.block_device_info_get_root(block_device_info)

View File

@@ -42,9 +42,7 @@ class API(base.Base):
"""API for interacting with the volume manager.""" """API for interacting with the volume manager."""
def create(self, context, size, snapshot_id, name, description, def create(self, context, size, snapshot_id, name, description,
volume_type=None, metadata=None, volume_type=None, metadata=None, availability_zone=None):
to_vsa_id=None, from_vsa_id=None, drive_type_id=None,
availability_zone=None):
if snapshot_id != None: if snapshot_id != None:
snapshot = self.get_snapshot(context, snapshot_id) snapshot = self.get_snapshot(context, snapshot_id)
if snapshot['status'] != "available": if snapshot['status'] != "available":
@@ -53,7 +51,6 @@ class API(base.Base):
if not size: if not size:
size = snapshot['volume_size'] size = snapshot['volume_size']
if to_vsa_id is None:
if quota.allowed_volumes(context, 1, size) < 1: if quota.allowed_volumes(context, 1, size) < 1:
pid = context.project_id pid = context.project_id
LOG.warn(_("Quota exceeded for %(pid)s, tried to create" LOG.warn(_("Quota exceeded for %(pid)s, tried to create"
@@ -81,19 +78,9 @@ class API(base.Base):
'display_description': description, 'display_description': description,
'volume_type_id': volume_type_id, 'volume_type_id': volume_type_id,
'metadata': metadata, 'metadata': metadata,
'to_vsa_id': to_vsa_id,
'from_vsa_id': from_vsa_id,
'drive_type_id': drive_type_id,
} }
volume = self.db.volume_create(context, options) volume = self.db.volume_create(context, options)
if from_vsa_id is not None: # for FE VSA volumes do nothing
now = utils.utcnow()
volume = self.db.volume_update(context,
volume['id'], {'status': 'available',
'launched_at': now})
return volume
rpc.cast(context, rpc.cast(context,
FLAGS.scheduler_topic, FLAGS.scheduler_topic,
{"method": "create_volume", {"method": "create_volume",
@@ -112,15 +99,6 @@ class API(base.Base):
def delete(self, context, volume_id): def delete(self, context, volume_id):
volume = self.get(context, volume_id) volume = self.get(context, volume_id)
if volume['from_vsa_id'] is not None:
if volume['status'] == "in-use":
raise exception.ApiError(_("Volume is in use. "\
"Detach it first"))
self.db.volume_destroy(context, volume['id'])
LOG.debug(_("volume %d: deleted successfully"), volume['id'])
return
if volume['status'] != "available": if volume['status'] != "available":
raise exception.ApiError(_("Volume status must be available")) raise exception.ApiError(_("Volume status must be available"))
now = utils.utcnow() now = utils.utcnow()
@@ -154,7 +132,7 @@ class API(base.Base):
for i in volume.get('volume_metadata'): for i in volume.get('volume_metadata'):
volume_metadata[i['key']] = i['value'] volume_metadata[i['key']] = i['value']
for k, v in searchdict: for k, v in searchdict.iteritems():
if k not in volume_metadata.keys()\ if k not in volume_metadata.keys()\
or volume_metadata[k] != v: or volume_metadata[k] != v:
return False return False
@@ -163,6 +141,7 @@ class API(base.Base):
# search_option to filter_name mapping. # search_option to filter_name mapping.
filter_mapping = {'metadata': _check_metadata_match} filter_mapping = {'metadata': _check_metadata_match}
result = []
for volume in volumes: for volume in volumes:
# go over all filters in the list # go over all filters in the list
for opt, values in search_opts.iteritems(): for opt, values in search_opts.iteritems():
@@ -172,21 +151,12 @@ class API(base.Base):
# no such filter - ignore it, go to next filter # no such filter - ignore it, go to next filter
continue continue
else: else:
if filter_func(volume, values) == False: if filter_func(volume, values):
# if one of conditions didn't match - remove result.append(volume)
volumes.remove(volume)
break break
volumes = result
return volumes return volumes
def get_all_by_vsa(self, context, vsa_id, direction):
if direction == "to":
return self.db.volume_get_all_assigned_to_vsa(context, vsa_id)
elif direction == "from":
return self.db.volume_get_all_assigned_from_vsa(context, vsa_id)
else:
raise exception.ApiError(_("Unsupported vol assignment type %s"),
direction)
def get_snapshot(self, context, snapshot_id): def get_snapshot(self, context, snapshot_id):
rv = self.db.snapshot_get(context, snapshot_id) rv = self.db.snapshot_get(context, snapshot_id)
return dict(rv.iteritems()) return dict(rv.iteritems())
@@ -286,3 +256,12 @@ class API(base.Base):
self.db.volume_metadata_update(context, volume_id, _metadata, True) self.db.volume_metadata_update(context, volume_id, _metadata, True)
return _metadata return _metadata
def get_volume_metadata_value(self, volume, key):
"""Get value of particular metadata key."""
metadata = volume.get('volume_metadata')
if metadata:
for i in volume['volume_metadata']:
if i['key'] == key:
return i['value']
return None

View File

@@ -28,6 +28,7 @@ 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 utils from nova import utils
from nova.volume import volume_types
LOG = logging.getLogger("nova.volume.driver") LOG = logging.getLogger("nova.volume.driver")
@@ -516,7 +517,7 @@ class ISCSIDriver(VolumeDriver):
iscsi_properties = self._get_iscsi_properties(volume) iscsi_properties = self._get_iscsi_properties(volume)
if not iscsi_properties['target_discovered']: if not iscsi_properties['target_discovered']:
self._run_iscsiadm(iscsi_properties, '--op=new') self._run_iscsiadm(iscsi_properties, ('--op', 'new'))
if iscsi_properties.get('auth_method'): if iscsi_properties.get('auth_method'):
self._iscsiadm_update(iscsi_properties, self._iscsiadm_update(iscsi_properties,
@@ -568,7 +569,7 @@ class ISCSIDriver(VolumeDriver):
iscsi_properties = self._get_iscsi_properties(volume) iscsi_properties = self._get_iscsi_properties(volume)
self._iscsiadm_update(iscsi_properties, "node.startup", "manual") self._iscsiadm_update(iscsi_properties, "node.startup", "manual")
self._run_iscsiadm(iscsi_properties, "--logout") self._run_iscsiadm(iscsi_properties, "--logout")
self._run_iscsiadm(iscsi_properties, '--op=delete') self._run_iscsiadm(iscsi_properties, ('--op', 'delete'))
def check_for_export(self, context, volume_id): def check_for_export(self, context, volume_id):
"""Make sure volume is exported.""" """Make sure volume is exported."""
@@ -813,9 +814,15 @@ class LoggingVolumeDriver(VolumeDriver):
class ZadaraBEDriver(ISCSIDriver): class ZadaraBEDriver(ISCSIDriver):
"""Performs actions to configure Zadara BE module.""" """Performs actions to configure Zadara BE module."""
def _not_vsa_be_volume(self, volume): def _is_vsa_volume(self, volume):
return volume_types.is_vsa_volume(volume['volume_type_id'])
def _is_vsa_drive(self, volume):
return volume_types.is_vsa_drive(volume['volume_type_id'])
def _not_vsa_volume_or_drive(self, volume):
"""Returns True if volume is not VSA BE volume.""" """Returns True if volume is not VSA BE volume."""
if volume['to_vsa_id'] is None: if not volume_types.is_vsa_object(volume['volume_type_id']):
LOG.debug(_("\tVolume %s is NOT VSA volume"), volume['name']) LOG.debug(_("\tVolume %s is NOT VSA volume"), volume['name'])
return True return True
else: else:
@@ -828,9 +835,14 @@ class ZadaraBEDriver(ISCSIDriver):
""" Volume Driver methods """ """ Volume Driver methods """
def create_volume(self, volume): def create_volume(self, volume):
"""Creates BE volume.""" """Creates BE volume."""
if self._not_vsa_be_volume(volume): if self._not_vsa_volume_or_drive(volume):
return super(ZadaraBEDriver, self).create_volume(volume) return super(ZadaraBEDriver, self).create_volume(volume)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s creation - do nothing"),
volume['name'])
return
if int(volume['size']) == 0: if int(volume['size']) == 0:
sizestr = '0' # indicates full-partition sizestr = '0' # indicates full-partition
else: else:
@@ -838,9 +850,16 @@ class ZadaraBEDriver(ISCSIDriver):
# Set the qos-str to default type sas # Set the qos-str to default type sas
qosstr = 'SAS_1000' qosstr = 'SAS_1000'
drive_type = volume.get('drive_type') LOG.debug(_("\tvolume_type_id=%s"), volume['volume_type_id'])
if drive_type is not None:
qosstr = drive_type['type'] + ("_%s" % drive_type['size_gb']) volume_type = volume_types.get_volume_type(None,
volume['volume_type_id'])
LOG.debug(_("\tvolume_type=%s"), volume_type)
if volume_type is not None:
qosstr = volume_type['extra_specs']['drive_type'] + \
("_%s" % volume_type['extra_specs']['drive_size'])
try: try:
self._sync_exec('/var/lib/zadara/bin/zadara_sncfg', self._sync_exec('/var/lib/zadara/bin/zadara_sncfg',
@@ -858,9 +877,14 @@ class ZadaraBEDriver(ISCSIDriver):
def delete_volume(self, volume): def delete_volume(self, volume):
"""Deletes BE volume.""" """Deletes BE volume."""
if self._not_vsa_be_volume(volume): if self._not_vsa_volume_or_drive(volume):
return super(ZadaraBEDriver, self).delete_volume(volume) return super(ZadaraBEDriver, self).delete_volume(volume)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s deletion - do nothing"),
volume['name'])
return
try: try:
self._sync_exec('/var/lib/zadara/bin/zadara_sncfg', self._sync_exec('/var/lib/zadara/bin/zadara_sncfg',
'delete_partition', 'delete_partition',
@@ -874,16 +898,26 @@ class ZadaraBEDriver(ISCSIDriver):
LOG.debug(_("VSA BE delete_volume for %s suceeded"), volume['name']) LOG.debug(_("VSA BE delete_volume for %s suceeded"), volume['name'])
def local_path(self, volume): def local_path(self, volume):
if self._not_vsa_be_volume(volume): if self._not_vsa_volume_or_drive(volume):
return super(ZadaraBEDriver, self).local_path(volume) return super(ZadaraBEDriver, self).local_path(volume)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s local path call - call discover"),
volume['name'])
return super(ZadaraBEDriver, self).discover_volume(None, volume)
raise exception.Error(_("local_path not supported")) raise exception.Error(_("local_path not supported"))
def ensure_export(self, context, volume): def ensure_export(self, context, volume):
"""ensure BE export for a volume""" """ensure BE export for a volume"""
if self._not_vsa_be_volume(volume): if self._not_vsa_volume_or_drive(volume):
return super(ZadaraBEDriver, self).ensure_export(context, volume) return super(ZadaraBEDriver, self).ensure_export(context, volume)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s ensure export - do nothing"),
volume['name'])
return
try: try:
iscsi_target = self.db.volume_get_iscsi_target_num(context, iscsi_target = self.db.volume_get_iscsi_target_num(context,
volume['id']) volume['id'])
@@ -900,9 +934,14 @@ class ZadaraBEDriver(ISCSIDriver):
def create_export(self, context, volume): def create_export(self, context, volume):
"""create BE export for a volume""" """create BE export for a volume"""
if self._not_vsa_be_volume(volume): if self._not_vsa_volume_or_drive(volume):
return super(ZadaraBEDriver, self).create_export(context, volume) return super(ZadaraBEDriver, self).create_export(context, volume)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s create export - do nothing"),
volume['name'])
return
self._ensure_iscsi_targets(context, volume['host']) self._ensure_iscsi_targets(context, volume['host'])
iscsi_target = self.db.volume_allocate_iscsi_target(context, iscsi_target = self.db.volume_allocate_iscsi_target(context,
volume['id'], volume['id'],
@@ -915,9 +954,14 @@ class ZadaraBEDriver(ISCSIDriver):
def remove_export(self, context, volume): def remove_export(self, context, volume):
"""Removes BE export for a volume.""" """Removes BE export for a volume."""
if self._not_vsa_be_volume(volume): if self._not_vsa_volume_or_drive(volume):
return super(ZadaraBEDriver, self).remove_export(context, volume) return super(ZadaraBEDriver, self).remove_export(context, volume)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s remove export - do nothing"),
volume['name'])
return
try: try:
iscsi_target = self.db.volume_get_iscsi_target_num(context, iscsi_target = self.db.volume_get_iscsi_target_num(context,
volume['id']) volume['id'])
@@ -939,14 +983,14 @@ class ZadaraBEDriver(ISCSIDriver):
def create_snapshot(self, snapshot): def create_snapshot(self, snapshot):
"""Nothing required for snapshot""" """Nothing required for snapshot"""
if self._not_vsa_be_volume(volume): if self._not_vsa_volume_or_drive(volume):
return super(ZadaraBEDriver, self).create_snapshot(volume) return super(ZadaraBEDriver, self).create_snapshot(volume)
pass pass
def delete_snapshot(self, snapshot): def delete_snapshot(self, snapshot):
"""Nothing required to delete a snapshot""" """Nothing required to delete a snapshot"""
if self._not_vsa_be_volume(volume): if self._not_vsa_volume_or_drive(volume):
return super(ZadaraBEDriver, self).delete_snapshot(volume) return super(ZadaraBEDriver, self).delete_snapshot(volume)
pass pass

View File

@@ -45,11 +45,12 @@ intact.
from nova import context from nova import context
from nova import exception from nova import exception
from nova import rpc
from nova import flags 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 rpc
from nova import utils from nova import utils
from nova.volume import volume_types
LOG = logging.getLogger('nova.volume.manager') LOG = logging.getLogger('nova.volume.manager')
@@ -144,12 +145,22 @@ class VolumeManager(manager.SchedulerDependentManager):
return volume_id return volume_id
def _notify_vsa(self, context, volume_ref, status): def _notify_vsa(self, context, volume_ref, status):
if volume_ref['to_vsa_id'] is not None: if volume_ref['volume_type_id'] is None:
return
if volume_types.is_vsa_drive(volume_ref['volume_type_id']):
vsa_id = None
for i in volume_ref.get('volume_metadata'):
if i['key'] == 'to_vsa_id':
vsa_id = int(i['value'])
break
if vsa_id:
rpc.cast(context, rpc.cast(context,
FLAGS.vsa_topic, FLAGS.vsa_topic,
{"method": "vsa_volume_created", {"method": "vsa_volume_created",
"args": {"vol_id": volume_ref['id'], "args": {"vol_id": volume_ref['id'],
"vsa_id": volume_ref['to_vsa_id'], "vsa_id": vsa_id,
"status": status}}) "status": status}})
def delete_volume(self, context, volume_id): def delete_volume(self, context, volume_id):

View File

@@ -64,14 +64,12 @@ class SanISCSIDriver(ISCSIDriver):
# discover_volume is still OK # discover_volume is still OK
# undiscover_volume is still OK # undiscover_volume is still OK
def _connect_to_ssh(self, san_ip=None): def _connect_to_ssh(self):
if san_ip is None:
san_ip = FLAGS.san_ip
ssh = paramiko.SSHClient() ssh = paramiko.SSHClient()
#TODO(justinsb): We need a better SSH key policy #TODO(justinsb): We need a better SSH key policy
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if FLAGS.san_password: if FLAGS.san_password:
ssh.connect(san_ip, ssh.connect(FLAGS.san_ip,
port=FLAGS.san_ssh_port, port=FLAGS.san_ssh_port,
username=FLAGS.san_login, username=FLAGS.san_login,
password=FLAGS.san_password) password=FLAGS.san_password)
@@ -79,7 +77,7 @@ class SanISCSIDriver(ISCSIDriver):
privatekeyfile = os.path.expanduser(FLAGS.san_privatekey) privatekeyfile = os.path.expanduser(FLAGS.san_privatekey)
# It sucks that paramiko doesn't support DSA keys # It sucks that paramiko doesn't support DSA keys
privatekey = paramiko.RSAKey.from_private_key_file(privatekeyfile) privatekey = paramiko.RSAKey.from_private_key_file(privatekeyfile)
ssh.connect(san_ip, ssh.connect(FLAGS.san_ip,
port=FLAGS.san_ssh_port, port=FLAGS.san_ssh_port,
username=FLAGS.san_login, username=FLAGS.san_login,
pkey=privatekey) pkey=privatekey)
@@ -87,9 +85,9 @@ class SanISCSIDriver(ISCSIDriver):
raise exception.Error(_("Specify san_password or san_privatekey")) raise exception.Error(_("Specify san_password or san_privatekey"))
return ssh return ssh
def _run_ssh(self, command, check_exit_code=True, san_ip=None): def _run_ssh(self, command, check_exit_code=True):
#TODO(justinsb): SSH connection caching (?) #TODO(justinsb): SSH connection caching (?)
ssh = self._connect_to_ssh(san_ip) ssh = self._connect_to_ssh()
#TODO(justinsb): Reintroduce the retry hack #TODO(justinsb): Reintroduce the retry hack
ret = ssh_execute(ssh, command, check_exit_code=check_exit_code) ret = ssh_execute(ssh, command, check_exit_code=check_exit_code)

View File

@@ -100,20 +100,22 @@ def get_all_types(context, inactive=0, search_opts={}):
continue continue
else: else:
if filter_func(type_args, values): if filter_func(type_args, values):
# if one of conditions didn't match - remove
result[type_name] = type_args result[type_name] = type_args
break break
vol_types = result vol_types = result
return vol_types return vol_types
def get_volume_type(context, id): def get_volume_type(ctxt, id):
"""Retrieves single volume type by id.""" """Retrieves single volume type by id."""
if id is None: if id is None:
raise exception.InvalidVolumeType(volume_type=id) raise exception.InvalidVolumeType(volume_type=id)
if ctxt is None:
ctxt = context.get_admin_context()
try: try:
return db.volume_type_get(context, id) return db.volume_type_get(ctxt, id)
except exception.DBError: except exception.DBError:
raise exception.ApiError(_("Unknown volume type: %s") % id) raise exception.ApiError(_("Unknown volume type: %s") % id)
@@ -127,3 +129,38 @@ def get_volume_type_by_name(context, name):
return db.volume_type_get_by_name(context, name) return db.volume_type_get_by_name(context, name)
except exception.DBError: except exception.DBError:
raise exception.ApiError(_("Unknown volume type: %s") % name) raise exception.ApiError(_("Unknown volume type: %s") % name)
def is_key_value_present(volume_type_id, key, value, volume_type=None):
if volume_type_id is None:
return False
if volume_type is None:
volume_type = get_volume_type(context.get_admin_context(),
volume_type_id)
if volume_type.get('extra_specs') is None or\
volume_type['extra_specs'].get(key) != value:
return False
else:
return True
def is_vsa_drive(volume_type_id, volume_type=None):
return is_key_value_present(volume_type_id,
'type', 'vsa_drive', volume_type)
def is_vsa_volume(volume_type_id, volume_type=None):
return is_key_value_present(volume_type_id,
'type', 'vsa_volume', volume_type)
def is_vsa_object(volume_type_id):
if volume_type_id is None:
return False
volume_type = get_volume_type(context.get_admin_context(),
volume_type_id)
return is_vsa_drive(volume_type_id, volume_type) or\
is_vsa_volume(volume_type_id, volume_type)

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)