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

View File

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

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()
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):
"""Return summary or detailed list of VSAs."""
context = req.environ['nova.context']
@@ -114,8 +118,7 @@ class VsaController(object):
vsa_list = []
for vsa in limited_list:
instances = self.compute_api.get_all(context,
search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))})
instances = self._get_instances_by_vsa_id(context, vsa.get('id'))
vsa_list.append(_vsa_view(context, vsa, details, instances))
return {'vsaSet': vsa_list}
@@ -136,9 +139,7 @@ class VsaController(object):
except exception.NotFound:
return faults.Fault(exc.HTTPNotFound())
instances = self.compute_api.get_all(context,
search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))})
instances = self._get_instances_by_vsa_id(context, vsa.get('id'))
return {'vsa': _vsa_view(context, vsa, True, instances)}
def create(self, req, body):
@@ -171,9 +172,7 @@ class VsaController(object):
vsa = self.vsa_api.create(context, **args)
instances = self.compute_api.get_all(context,
search_opts={'metadata': dict(vsa_id=str(vsa.get('id')))})
instances = self._get_instances_by_vsa_id(context, vsa.get('id'))
return {'vsa': _vsa_view(context, vsa, True, instances)}
def delete(self, req, id):
@@ -202,14 +201,14 @@ class VsaController(object):
locals(), context=context)
try:
instances = self.compute_api.get_all(context,
search_opts={'metadata': dict(vsa_id=str(id))})
if instances is None or len(instances)==0:
instances = self._get_instances_by_vsa_id(context, id)
if instances is None or len(instances) == 0:
return faults.Fault(exc.HTTPNotFound())
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
except exception.NotFound:
@@ -228,6 +227,7 @@ class VsaController(object):
LOG.audit(_("Disassociate address from VSA %(id)s"),
locals(), context=context)
# Placeholder
class VsaVolumeDriveController(volumes.VolumeController):
@@ -255,6 +255,7 @@ class VsaVolumeDriveController(volumes.VolumeController):
def __init__(self):
self.volume_api = volume.API()
self.vsa_api = vsa.API()
super(VsaVolumeDriveController, self).__init__()
def _translation(self, context, vol, vsa_id, details):
@@ -264,7 +265,7 @@ class VsaVolumeDriveController(volumes.VolumeController):
translation = volumes.translate_volume_summary_view
d = translation(context, vol)
d['vsaId'] = vol[self.direction]
d['vsaId'] = vsa_id
d['name'] = vol['name']
return d
@@ -276,8 +277,9 @@ class VsaVolumeDriveController(volumes.VolumeController):
LOG.error(_("%(obj)s with ID %(id)s not found"), locals())
raise
own_vsa_id = volume_ref[self.direction]
if own_vsa_id != int(vsa_id):
own_vsa_id = self.volume_api.get_volume_metadata_value(volume_ref,
self.direction)
if own_vsa_id != vsa_id:
LOG.error(_("%(obj)s with ID %(id)s belongs to VSA %(own_vsa_id)s"\
" and not to VSA %(vsa_id)s."), locals())
raise exception.Invalid()
@@ -286,8 +288,8 @@ class VsaVolumeDriveController(volumes.VolumeController):
"""Return summary or detailed list of volumes for particular VSA."""
context = req.environ['nova.context']
vols = self.volume_api.get_all_by_vsa(context, vsa_id,
self.direction.split('_')[0])
vols = self.volume_api.get_all(context,
search_opts={'metadata': {self.direction: str(vsa_id)}})
limited_list = common.limited(vols, req)
res = [self._translation(context, vol, vsa_id, details) \
@@ -317,11 +319,19 @@ class VsaVolumeDriveController(volumes.VolumeController):
size = vol['size']
LOG.audit(_("Create volume of %(size)s GB from VSA ID %(vsa_id)s"),
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,
vol.get('displayName'),
vol.get('displayDescription'),
from_vsa_id=vsa_id)
new_volume = self.volume_api.create(context,
size,
None,
vol.get('displayName'),
vol.get('displayDescription'),
volume_type=volume_type,
metadata=dict(from_vsa_id=str(vsa_id)))
return {self.object: self._translation(context, new_volume,
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)
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):
"""Get a volume by 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):
"""Creates Virtual Storage Array record."""
return IMPL.vsa_create(context, values)
@@ -1586,8 +1546,3 @@ def vsa_get_all(context):
def vsa_get_all_by_project(context, project_id):
"""Get all Virtual Storage Array records by 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('volume_metadata')).\
options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(id=volume_id).\
filter_by(deleted=can_read_deleted(context)).\
first()
@@ -2235,7 +2234,6 @@ def volume_get(context, volume_id, session=None):
options(joinedload('instance')).\
options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(project_id=context.project_id).\
filter_by(id=volume_id).\
filter_by(deleted=False).\
@@ -2253,7 +2251,6 @@ def volume_get_all(context):
options(joinedload('instance')).\
options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2265,7 +2262,6 @@ def volume_get_all_by_host(context, host):
options(joinedload('instance')).\
options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(host=host).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2277,7 +2273,6 @@ def volume_get_all_by_instance(context, instance_id):
result = session.query(models.Volume).\
options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(instance_id=instance_id).\
filter_by(deleted=False).\
all()
@@ -2286,28 +2281,6 @@ def volume_get_all_by_instance(context, instance_id):
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
def volume_get_all_by_project(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('volume_metadata')).\
options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
filter_by(project_id=project_id).\
filter_by(deleted=can_read_deleted(context)).\
all()
@@ -2332,7 +2304,6 @@ def volume_get_instance(context, volume_id):
options(joinedload('instance')).\
options(joinedload('volume_metadata')).\
options(joinedload('volume_type')).\
options(joinedload('drive_type')).\
first()
if not result:
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.update(values)
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
def vsa_create(context, values):
"""
@@ -4067,26 +3938,4 @@ def vsa_get_all_by_project(context, project_id):
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()
# 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
#
@@ -67,67 +55,21 @@ virtual_storage_arrays = Table('virtual_storage_arrays', meta,
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):
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;
# bind migrate_engine to your metadata
meta.bind = migrate_engine
for table in new_tables:
try:
table.create()
except Exception:
logging.info(repr(table))
logging.exception('Exception while creating table')
raise
volumes.create_column(to_vsa_id)
volumes.create_column(from_vsa_id)
volumes.create_column(drive_type_id)
try:
virtual_storage_arrays.create()
except Exception:
logging.info(repr(table))
logging.exception('Exception while creating table')
raise
def downgrade(migrate_engine):
meta.bind = migrate_engine
volumes.drop_column(to_vsa_id)
volumes.drop_column(from_vsa_id)
volumes.drop_column(drive_type_id)
for table in new_tables:
table.drop()
virtual_storage_arrays.drop()

View File

@@ -352,13 +352,6 @@ class Volume(BASE, NovaBase):
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):
"""Represents a metadata key/value pair for a volume"""
@@ -402,38 +395,6 @@ class VolumeTypeExtraSpecs(BASE, NovaBase):
'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):
"""Represents a single quota override for a project.
@@ -918,7 +879,9 @@ def register_models():
Network, SecurityGroup, SecurityGroupIngressRule,
SecurityGroupInstanceAssociation, AuthToken, User,
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)
for model in models:
model.metadata.create_all(engine)

View File

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

View File

@@ -365,10 +365,6 @@ class VolumeTypeExtraSpecsNotFound(NotFound):
"key %(extra_specs_key)s.")
class VolumeNotFoundForVsa(VolumeNotFound):
message = _("Volume not found for vsa %(vsa_id)s.")
class SnapshotNotFound(NotFound):
message = _("Snapshot %(snapshot_id)s could not be found.")
@@ -799,14 +795,6 @@ class VirtualStorageArrayNotFoundByName(NotFound):
message = _("Virtual Storage Array %(name)s could not be found.")
class VirtualDiskTypeNotFound(NotFound):
message = _("Drive Type %(id)d could not be found.")
class VirtualDiskTypeNotFoundByName(NotFound):
message = _("Drive Type %(name)s could not be found.")
class CannotResizeToSameSize(NovaException):
message = _("When resizing, instances must change size!")

View File

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

View File

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

View File

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

View File

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

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 webob
from nova import context
from nova import db
from nova import exception
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 test
from nova import volume
from nova import vsa
from nova.api import openstack
from nova.tests.api.openstack import fakes
import nova.wsgi
@@ -120,7 +119,7 @@ class VSAApiTest(test.TestCase):
vsa = {"displayName": "VSA Test Name",
"displayDescription": "VSA Test Desc"}
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.body = json.dumps(body)
req.headers['content-type'] = 'application/json'
@@ -139,7 +138,7 @@ class VSAApiTest(test.TestCase):
vsa['displayDescription'])
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.body = json.dumps({})
req.headers['content-type'] = 'application/json'
@@ -152,7 +151,7 @@ class VSAApiTest(test.TestCase):
last_param = {}
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'
resp = req.get_response(fakes.wsgi_app())
@@ -164,7 +163,7 @@ class VSAApiTest(test.TestCase):
last_param = {}
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'
resp = req.get_response(fakes.wsgi_app())
@@ -176,7 +175,7 @@ class VSAApiTest(test.TestCase):
last_param = {}
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'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
@@ -191,14 +190,14 @@ class VSAApiTest(test.TestCase):
last_param = {}
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'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 404)
self.assertEqual(str(last_param['vsa_id']), str(vsa_id))
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'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
@@ -213,7 +212,7 @@ class VSAApiTest(test.TestCase):
self.assertEqual(resp_vsa['id'], 123)
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'
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
@@ -239,17 +238,21 @@ def _get_default_volume_param():
'name': 'vol name',
'display_name': 'Default vol name',
'display_description': 'Default vol description',
'from_vsa_id': None,
'to_vsa_id': None,
'volume_type_id': 1,
'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,
**param):
LOG.debug(_("_create: param=%s"), size)
vol = _get_default_volume_param()
for k, v in param.iteritems():
vol[k] = v
vol['size'] = size
vol['display_name'] = name
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)
vol = _get_default_volume_param()
vol['id'] = volume_id
if volume_id == '234':
vol['from_vsa_id'] = 123
meta = {'key': 'from_vsa_id', 'value': '123'}
if volume_id == '345':
vol['to_vsa_id'] = 123
meta = {'key': 'to_vsa_id', 'value': '123'}
vol['volume_metadata'].append(meta)
return vol
@@ -281,9 +284,9 @@ def stub_volume_get_notfound(self, context, volume_id):
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['%s_vsa_id' % direction] = vsa_id
vol['metadata'] = search_opts['metadata']
return [vol]
@@ -302,13 +305,13 @@ class VSAVolumeApiTest(test.TestCase):
fakes.stub_out_rate_limiting(self.stubs)
fakes.stub_out_auth(self.stubs)
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, "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_all", stub_volume_get_all)
self.context = context.get_admin_context()
self.test_obj = test_obj if test_obj else "volume"
@@ -319,11 +322,13 @@ class VSAVolumeApiTest(test.TestCase):
super(VSAVolumeApiTest, self).tearDown()
def test_vsa_volume_create(self):
self.stubs.Set(volume.api.API, "create", stub_volume_create)
vol = {"size": 100,
"displayName": "VSA Volume Test Name",
"displayDescription": "VSA Volume Test Desc"}
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.body = json.dumps(body)
req.headers['content-type'] = 'application/json'
@@ -344,7 +349,7 @@ class VSAVolumeApiTest(test.TestCase):
self.assertEqual(resp.status_int, 400)
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.body = json.dumps({})
req.headers['content-type'] = 'application/json'
@@ -356,25 +361,25 @@ class VSAVolumeApiTest(test.TestCase):
self.assertEqual(resp.status_int, 400)
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())
self.assertEqual(resp.status_int, 200)
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)
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
def test_vsa_volume_show(self):
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))
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 200)
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))
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 400)
@@ -382,7 +387,7 @@ class VSAVolumeApiTest(test.TestCase):
def test_vsa_volume_show_no_volume(self):
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))
resp = req.get_response(fakes.wsgi_app())
self.assertEqual(resp.status_int, 404)
@@ -392,7 +397,7 @@ class VSAVolumeApiTest(test.TestCase):
update = {"status": "available",
"displayName": "Test Display name"}
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))
req.method = 'PUT'
req.body = json.dumps(body)
@@ -406,7 +411,7 @@ class VSAVolumeApiTest(test.TestCase):
def test_vsa_volume_delete(self):
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))
req.method = 'DELETE'
resp = req.get_response(fakes.wsgi_app())
@@ -416,7 +421,7 @@ class VSAVolumeApiTest(test.TestCase):
self.assertEqual(resp.status_int, 400)
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))
req.method = 'DELETE'
resp = req.get_response(fakes.wsgi_app())
@@ -425,7 +430,7 @@ class VSAVolumeApiTest(test.TestCase):
def test_vsa_volume_delete_no_volume(self):
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))
req.method = 'DELETE'
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")
self.flags(osapi_extensions_path=ext_path)
self.ext_list = [
"DriveTypes",
"Createserverext",
"FlavorExtraSpecs",
"Floating_ips",
@@ -96,8 +95,8 @@ class ExtensionControllerTest(test.TestCase):
"Quotas",
"Rescue",
"SecurityGroups",
"VirtualInterfaces",
"VSAs",
"VirtualInterfaces",
"Volumes",
"VolumeTypes",
]

View File

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

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
# under the License.
import stubout
import base64
import stubout
from xml.etree import ElementTree
from xml.etree.ElementTree import Element, SubElement
from nova import context
from nova import db
from nova import exception
from nova import flags
from nova import log as logging
from nova import test
from nova import vsa
from nova import volume
from nova import db
from nova import context
from nova import test
from nova import log as logging
from nova.volume import volume_types
from nova.vsa import utils as vsa_utils
import nova.image.fake
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.tests.vsa')
def fake_drive_type_get_by_name(context, name):
drive_type = {
'id': 1,
'name': name,
'type': name.split('_')[0],
'size_gb': int(name.split('_')[1]),
'rpm': name.split('_')[2],
'capabilities': '',
'visible': True}
return drive_type
class VsaTestCase(test.TestCase):
def setUp(self):
@@ -53,9 +44,20 @@ class VsaTestCase(test.TestCase):
self.vsa_api = vsa.API()
self.volume_api = volume.API()
FLAGS.quota_volumes = 100
FLAGS.quota_gigabytes = 10000
self.context_non_admin = context.RequestContext(None, None)
self.context = context.get_admin_context()
volume_types.create(self.context,
'SATA_500_7200',
extra_specs={'type': 'vsa_drive',
'drive_name': 'SATA_500_7200',
'drive_type': 'SATA',
'drive_size': '500',
'drive_rpm': '7200'})
def fake_show_by_name(meh, context, name):
if name == 'wrong_image_name':
LOG.debug(_("Test: Emulate wrong VSA name. Raise"))
@@ -124,9 +126,6 @@ class VsaTestCase(test.TestCase):
FLAGS.vsa_multi_vol_creation = multi_vol_creation
self.stubs.Set(nova.vsa.drive_types, 'get_by_name',
fake_drive_type_get_by_name)
param = {'storage': [{'drive_name': 'SATA_500_7200',
'num_drives': 3}]}
vsa_ref = self.vsa_api.create(self.context, **param)
@@ -157,8 +156,6 @@ class VsaTestCase(test.TestCase):
self.vsa_api.delete(self.context, vsa_ref['id'])
def test_vsa_generate_user_data(self):
self.stubs.Set(nova.vsa.drive_types, 'get_by_name',
fake_drive_type_get_by_name)
FLAGS.vsa_multi_vol_creation = False
param = {'display_name': 'VSA name test',
@@ -167,12 +164,10 @@ class VsaTestCase(test.TestCase):
'storage': [{'drive_name': 'SATA_500_7200',
'num_drives': 3}]}
vsa_ref = self.vsa_api.create(self.context, **param)
volumes = db.volume_get_all_assigned_to_vsa(self.context,
vsa_ref['id'])
volumes = self.vsa_api.get_all_vsa_drives(self.context,
vsa_ref['id'])
user_data = self.vsa_api.generate_user_data(self.context,
vsa_ref,
volumes)
user_data = vsa_utils.generate_user_data(vsa_ref, volumes)
user_data = base64.b64decode(user_data)
LOG.debug(_("Test: user_data = %s"), user_data)

View File

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

View File

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

View File

@@ -135,8 +135,6 @@ flags.DEFINE_string('default_local_format',
None,
'The default format a local_volume will be formatted with '
'on creation.')
flags.DEFINE_bool('libvirt_use_virtio_for_bridges',
False,
'Use virtio for bridge interfaces')
@@ -1088,7 +1086,8 @@ class LibvirtConnection(driver.ComputeDriver):
'ebs_root': ebs_root,
'local_device': local_device,
'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}
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."""
def create(self, context, size, snapshot_id, name, description,
volume_type=None, metadata=None,
to_vsa_id=None, from_vsa_id=None, drive_type_id=None,
availability_zone=None):
volume_type=None, metadata=None, availability_zone=None):
if snapshot_id != None:
snapshot = self.get_snapshot(context, snapshot_id)
if snapshot['status'] != "available":
@@ -53,13 +51,12 @@ class API(base.Base):
if not size:
size = snapshot['volume_size']
if to_vsa_id is None:
if quota.allowed_volumes(context, 1, size) < 1:
pid = context.project_id
LOG.warn(_("Quota exceeded for %(pid)s, tried to create"
" %(size)sG volume") % locals())
raise quota.QuotaError(_("Volume quota exceeded. You cannot "
"create a volume of size %sG") % size)
if quota.allowed_volumes(context, 1, size) < 1:
pid = context.project_id
LOG.warn(_("Quota exceeded for %(pid)s, tried to create"
" %(size)sG volume") % locals())
raise quota.QuotaError(_("Volume quota exceeded. You cannot "
"create a volume of size %sG") % size)
if availability_zone is None:
availability_zone = FLAGS.storage_availability_zone
@@ -81,19 +78,9 @@ class API(base.Base):
'display_description': description,
'volume_type_id': volume_type_id,
'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)
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,
FLAGS.scheduler_topic,
{"method": "create_volume",
@@ -112,15 +99,6 @@ class API(base.Base):
def delete(self, 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":
raise exception.ApiError(_("Volume status must be available"))
now = utils.utcnow()
@@ -154,7 +132,7 @@ class API(base.Base):
for i in volume.get('volume_metadata'):
volume_metadata[i['key']] = i['value']
for k, v in searchdict:
for k, v in searchdict.iteritems():
if k not in volume_metadata.keys()\
or volume_metadata[k] != v:
return False
@@ -163,6 +141,7 @@ class API(base.Base):
# search_option to filter_name mapping.
filter_mapping = {'metadata': _check_metadata_match}
result = []
for volume in volumes:
# go over all filters in the list
for opt, values in search_opts.iteritems():
@@ -172,21 +151,12 @@ class API(base.Base):
# no such filter - ignore it, go to next filter
continue
else:
if filter_func(volume, values) == False:
# if one of conditions didn't match - remove
volumes.remove(volume)
if filter_func(volume, values):
result.append(volume)
break
volumes = result
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):
rv = self.db.snapshot_get(context, snapshot_id)
return dict(rv.iteritems())
@@ -286,3 +256,12 @@ class API(base.Base):
self.db.volume_metadata_update(context, volume_id, _metadata, True)
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 log as logging
from nova import utils
from nova.volume import volume_types
LOG = logging.getLogger("nova.volume.driver")
@@ -516,7 +517,7 @@ class ISCSIDriver(VolumeDriver):
iscsi_properties = self._get_iscsi_properties(volume)
if not iscsi_properties['target_discovered']:
self._run_iscsiadm(iscsi_properties, '--op=new')
self._run_iscsiadm(iscsi_properties, ('--op', 'new'))
if iscsi_properties.get('auth_method'):
self._iscsiadm_update(iscsi_properties,
@@ -568,7 +569,7 @@ class ISCSIDriver(VolumeDriver):
iscsi_properties = self._get_iscsi_properties(volume)
self._iscsiadm_update(iscsi_properties, "node.startup", "manual")
self._run_iscsiadm(iscsi_properties, "--logout")
self._run_iscsiadm(iscsi_properties, '--op=delete')
self._run_iscsiadm(iscsi_properties, ('--op', 'delete'))
def check_for_export(self, context, volume_id):
"""Make sure volume is exported."""
@@ -813,9 +814,15 @@ class LoggingVolumeDriver(VolumeDriver):
class ZadaraBEDriver(ISCSIDriver):
"""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."""
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'])
return True
else:
@@ -828,9 +835,14 @@ class ZadaraBEDriver(ISCSIDriver):
""" Volume Driver methods """
def create_volume(self, 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)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s creation - do nothing"),
volume['name'])
return
if int(volume['size']) == 0:
sizestr = '0' # indicates full-partition
else:
@@ -838,9 +850,16 @@ class ZadaraBEDriver(ISCSIDriver):
# Set the qos-str to default type sas
qosstr = 'SAS_1000'
drive_type = volume.get('drive_type')
if drive_type is not None:
qosstr = drive_type['type'] + ("_%s" % drive_type['size_gb'])
LOG.debug(_("\tvolume_type_id=%s"), volume['volume_type_id'])
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:
self._sync_exec('/var/lib/zadara/bin/zadara_sncfg',
@@ -858,9 +877,14 @@ class ZadaraBEDriver(ISCSIDriver):
def delete_volume(self, 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)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s deletion - do nothing"),
volume['name'])
return
try:
self._sync_exec('/var/lib/zadara/bin/zadara_sncfg',
'delete_partition',
@@ -874,16 +898,26 @@ class ZadaraBEDriver(ISCSIDriver):
LOG.debug(_("VSA BE delete_volume for %s suceeded"), volume['name'])
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)
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"))
def ensure_export(self, context, 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)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s ensure export - do nothing"),
volume['name'])
return
try:
iscsi_target = self.db.volume_get_iscsi_target_num(context,
volume['id'])
@@ -900,9 +934,14 @@ class ZadaraBEDriver(ISCSIDriver):
def create_export(self, context, 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)
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'])
iscsi_target = self.db.volume_allocate_iscsi_target(context,
volume['id'],
@@ -915,9 +954,14 @@ class ZadaraBEDriver(ISCSIDriver):
def remove_export(self, context, 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)
if self._is_vsa_volume(volume):
LOG.debug(_("\tFE VSA Volume %s remove export - do nothing"),
volume['name'])
return
try:
iscsi_target = self.db.volume_get_iscsi_target_num(context,
volume['id'])
@@ -939,14 +983,14 @@ class ZadaraBEDriver(ISCSIDriver):
def create_snapshot(self, 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)
pass
def delete_snapshot(self, 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)
pass

View File

@@ -45,11 +45,12 @@ intact.
from nova import context
from nova import exception
from nova import rpc
from nova import flags
from nova import log as logging
from nova import manager
from nova import rpc
from nova import utils
from nova.volume import volume_types
LOG = logging.getLogger('nova.volume.manager')
@@ -144,13 +145,23 @@ class VolumeManager(manager.SchedulerDependentManager):
return volume_id
def _notify_vsa(self, context, volume_ref, status):
if volume_ref['to_vsa_id'] is not None:
rpc.cast(context,
FLAGS.vsa_topic,
{"method": "vsa_volume_created",
"args": {"vol_id": volume_ref['id'],
"vsa_id": volume_ref['to_vsa_id'],
"status": status}})
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,
FLAGS.vsa_topic,
{"method": "vsa_volume_created",
"args": {"vol_id": volume_ref['id'],
"vsa_id": vsa_id,
"status": status}})
def delete_volume(self, context, volume_id):
"""Deletes and unexports volume."""

View File

@@ -64,14 +64,12 @@ class SanISCSIDriver(ISCSIDriver):
# discover_volume is still OK
# undiscover_volume is still OK
def _connect_to_ssh(self, san_ip=None):
if san_ip is None:
san_ip = FLAGS.san_ip
def _connect_to_ssh(self):
ssh = paramiko.SSHClient()
#TODO(justinsb): We need a better SSH key policy
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if FLAGS.san_password:
ssh.connect(san_ip,
ssh.connect(FLAGS.san_ip,
port=FLAGS.san_ssh_port,
username=FLAGS.san_login,
password=FLAGS.san_password)
@@ -79,7 +77,7 @@ class SanISCSIDriver(ISCSIDriver):
privatekeyfile = os.path.expanduser(FLAGS.san_privatekey)
# It sucks that paramiko doesn't support DSA keys
privatekey = paramiko.RSAKey.from_private_key_file(privatekeyfile)
ssh.connect(san_ip,
ssh.connect(FLAGS.san_ip,
port=FLAGS.san_ssh_port,
username=FLAGS.san_login,
pkey=privatekey)
@@ -87,9 +85,9 @@ class SanISCSIDriver(ISCSIDriver):
raise exception.Error(_("Specify san_password or san_privatekey"))
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 (?)
ssh = self._connect_to_ssh(san_ip)
ssh = self._connect_to_ssh()
#TODO(justinsb): Reintroduce the retry hack
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
else:
if filter_func(type_args, values):
# if one of conditions didn't match - remove
result[type_name] = type_args
break
vol_types = result
return vol_types
def get_volume_type(context, id):
def get_volume_type(ctxt, id):
"""Retrieves single volume type by id."""
if id is None:
raise exception.InvalidVolumeType(volume_type=id)
if ctxt is None:
ctxt = context.get_admin_context()
try:
return db.volume_type_get(context, id)
return db.volume_type_get(ctxt, id)
except exception.DBError:
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)
except exception.DBError:
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 base64
from xml.etree import ElementTree
from nova import compute
from nova import db
from nova import exception
from nova import flags
from nova import log as logging
from nova import quota
from nova import rpc
from nova.db import base
from nova import compute
from nova import volume
from nova.compute import instance_types
from nova.vsa import drive_types
from nova.db import base
from nova.volume import volume_types
class VsaState:
CREATING = 'creating' # VSA creating (not ready yet)
LAUNCHING = 'launching' # Launching VCs (all BE volumes were created)
CREATED = 'created' # VSA fully created and ready for use
PARTIAL = 'partial' # Some BE drives were allocated
FAILED = 'failed' # Some BE storage allocations failed
DELETING = 'deleting' # VSA started the deletion procedure
FLAGS = flags.FLAGS
@@ -43,22 +47,14 @@ flags.DEFINE_string('vsa_ec2_access_key', None,
'EC2 access key used by VSA for accessing nova')
flags.DEFINE_string('vsa_ec2_user_id', None,
'User ID used by VSA for accessing nova')
flags.DEFINE_boolean('vsa_multi_vol_creation', True,
'Ask scheduler to create multiple volumes in one call')
flags.DEFINE_string('vsa_volume_type_name', 'VSA volume type',
'Name of volume type associated with FE VSA volumes')
LOG = logging.getLogger('nova.vsa')
class VsaState:
CREATING = 'creating' # VSA creating (not ready yet)
LAUNCHING = 'launching' # Launching VCs (all BE volumes were created)
CREATED = 'created' # VSA fully created and ready for use
PARTIAL = 'partial' # Some BE storage allocations failed
FAILED = 'failed' # Some BE storage allocations failed
DELETING = 'deleting' # VSA started the deletion procedure
class API(base.Base):
"""API for interacting with the VSA manager."""
@@ -67,6 +63,15 @@ class API(base.Base):
self.volume_api = volume_api or volume.API()
super(API, self).__init__(**kwargs)
def _check_volume_type_correctness(self, vol_type):
if vol_type.get('extra_specs') == None or\
vol_type['extra_specs'].get('type') != 'vsa_drive' or\
vol_type['extra_specs'].get('drive_type') == None or\
vol_type['extra_specs'].get('drive_size') == None:
raise exception.ApiError(_("Invalid drive type %s")
% vol_type['name'])
def _get_default_vsa_instance_type(self):
return instance_types.get_instance_type_by_name(
FLAGS.default_vsa_instance_type)
@@ -89,16 +94,17 @@ class API(base.Base):
if name is None:
raise exception.ApiError(_("No drive_name param found in %s")
% node)
# find DB record for this disk
try:
drive_ref = drive_types.get_by_name(context, name)
vol_type = volume_types.get_volume_type_by_name(context, name)
except exception.NotFound:
raise exception.ApiError(_("Invalid drive type name %s")
% name)
self._check_volume_type_correctness(vol_type)
# if size field present - override disk size specified in DB
size = node.get('size', drive_ref['size_gb'])
size = int(node.get('size',
vol_type['extra_specs'].get('drive_size')))
if shared:
part_size = FLAGS.vsa_part_size_gb
@@ -110,17 +116,15 @@ class API(base.Base):
size = 0 # special handling for full drives
for i in range(num_volumes):
# volume_name = vsa_name + ("_%s_vol-%d" % (name, i))
volume_name = "drive-%03d" % first_index
first_index += 1
volume_desc = 'BE volume for VSA %s type %s' % \
(vsa_name, name)
volume = {
'size': size,
'snapshot_id': None,
'name': volume_name,
'description': volume_desc,
'drive_ref': drive_ref
'volume_type_id': vol_type['id'],
}
volume_params.append(volume)
@@ -211,7 +215,7 @@ class API(base.Base):
if len(volume_params) > 0:
request_spec = {
'num_volumes': len(volume_params),
'vsa_id': vsa_id,
'vsa_id': str(vsa_id),
'volumes': volume_params,
}
@@ -227,17 +231,21 @@ class API(base.Base):
try:
vol_name = vol['name']
vol_size = vol['size']
vol_type_id = vol['volume_type_id']
LOG.debug(_("VSA ID %(vsa_id)d %(vsa_name)s: Create "\
"volume %(vol_name)s, %(vol_size)d GB"),
locals())
"volume %(vol_name)s, %(vol_size)d GB, "\
"type %(vol_type_id)s"), locals())
vol_type = volume_types.get_volume_type(context,
vol['volume_type_id'])
vol_ref = self.volume_api.create(context,
vol_size,
vol['snapshot_id'],
None,
vol_name,
vol['description'],
to_vsa_id=vsa_id,
drive_type_id=vol['drive_ref'].get('id'),
volume_type=vol_type,
metadata=dict(to_vsa_id=str(vsa_id)),
availability_zone=availability_zone)
except:
self.update_vsa_status(context, vsa_id,
@@ -249,7 +257,7 @@ class API(base.Base):
rpc.cast(context,
FLAGS.vsa_topic,
{"method": "create_vsa",
"args": {"vsa_id": vsa_id}})
"args": {"vsa_id": str(vsa_id)}})
return vsa_ref
@@ -314,8 +322,7 @@ class API(base.Base):
def _force_volume_delete(self, ctxt, volume):
"""Delete a volume, bypassing the check that it must be available."""
host = volume['host']
if not host or volume['from_vsa_id']:
# Volume not yet assigned to host OR FE volume
if not host:
# Deleting volume from database and skipping rpc.
self.db.volume_destroy(ctxt, volume['id'])
return
@@ -328,9 +335,9 @@ class API(base.Base):
def delete_vsa_volumes(self, context, vsa_id, direction,
force_delete=True):
if direction == "FE":
volumes = self.db.volume_get_all_assigned_from_vsa(context, vsa_id)
volumes = self.get_all_vsa_volumes(context, vsa_id)
else:
volumes = self.db.volume_get_all_assigned_to_vsa(context, vsa_id)
volumes = self.get_all_vsa_drives(context, vsa_id)
for volume in volumes:
try:
@@ -374,58 +381,25 @@ class API(base.Base):
return self.db.vsa_get_all(context)
return self.db.vsa_get_all_by_project(context, context.project_id)
def generate_user_data(self, context, vsa, volumes):
SubElement = ElementTree.SubElement
def get_vsa_volume_type(self, context):
name = FLAGS.vsa_volume_type_name
try:
vol_type = volume_types.get_volume_type_by_name(context, name)
except exception.NotFound:
volume_types.create(context, name,
extra_specs=dict(type='vsa_volume'))
vol_type = volume_types.get_volume_type_by_name(context, name)
e_vsa = ElementTree.Element("vsa")
return vol_type
e_vsa_detail = SubElement(e_vsa, "id")
e_vsa_detail.text = str(vsa['id'])
e_vsa_detail = SubElement(e_vsa, "name")
e_vsa_detail.text = vsa['display_name']
e_vsa_detail = SubElement(e_vsa, "description")
e_vsa_detail.text = vsa['display_description']
e_vsa_detail = SubElement(e_vsa, "vc_count")
e_vsa_detail.text = str(vsa['vc_count'])
def get_all_vsa_instances(self, context, vsa_id):
return self.compute_api.get_all(context,
search_opts={'metadata': dict(vsa_id=str(vsa_id))})
e_vsa_detail = SubElement(e_vsa, "auth_user")
e_vsa_detail.text = FLAGS.vsa_ec2_user_id
e_vsa_detail = SubElement(e_vsa, "auth_access_key")
e_vsa_detail.text = FLAGS.vsa_ec2_access_key
def get_all_vsa_volumes(self, context, vsa_id):
return self.volume_api.get_all(context,
search_opts={'metadata': dict(from_vsa_id=str(vsa_id))})
e_volumes = SubElement(e_vsa, "volumes")
for volume in volumes:
loc = volume['provider_location']
if loc is None:
ip = ''
iscsi_iqn = ''
iscsi_portal = ''
else:
(iscsi_target, _sep, iscsi_iqn) = loc.partition(" ")
(ip, iscsi_portal) = iscsi_target.split(":", 1)
e_vol = SubElement(e_volumes, "volume")
e_vol_detail = SubElement(e_vol, "id")
e_vol_detail.text = str(volume['id'])
e_vol_detail = SubElement(e_vol, "name")
e_vol_detail.text = volume['name']
e_vol_detail = SubElement(e_vol, "display_name")
e_vol_detail.text = volume['display_name']
e_vol_detail = SubElement(e_vol, "size_gb")
e_vol_detail.text = str(volume['size'])
e_vol_detail = SubElement(e_vol, "status")
e_vol_detail.text = volume['status']
e_vol_detail = SubElement(e_vol, "ip")
e_vol_detail.text = ip
e_vol_detail = SubElement(e_vol, "iscsi_iqn")
e_vol_detail.text = iscsi_iqn
e_vol_detail = SubElement(e_vol, "iscsi_portal")
e_vol_detail.text = iscsi_portal
e_vol_detail = SubElement(e_vol, "lun")
e_vol_detail.text = '0'
e_vol_detail = SubElement(e_vol, "sn_host")
e_vol_detail.text = volume['host']
_xml = ElementTree.tostring(e_vsa)
return base64.b64encode(_xml)
def get_all_vsa_drives(self, context, vsa_id):
return self.volume_api.get_all(context,
search_opts={'metadata': dict(to_vsa_id=str(vsa_id))})

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

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)