VSA code redesign. Drive types completely replaced by Volume types
This commit is contained in:
214
bin/nova-manage
214
bin/nova-manage
@@ -53,6 +53,7 @@
|
||||
CLI interface for nova management.
|
||||
"""
|
||||
|
||||
import ast
|
||||
import gettext
|
||||
import glob
|
||||
import json
|
||||
@@ -64,8 +65,6 @@ import time
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
import ast
|
||||
|
||||
# If ../nova/__init__.py exists, add ../ to Python search path, so that
|
||||
# it will override what happens to be installed in /usr/(local/)lib/python...
|
||||
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||
@@ -87,15 +86,13 @@ from nova import quota
|
||||
from nova import rpc
|
||||
from nova import utils
|
||||
from nova import version
|
||||
from nova import vsa
|
||||
from nova.api.ec2 import ec2utils
|
||||
from nova.auth import manager
|
||||
from nova.cloudpipe import pipelib
|
||||
from nova.compute import instance_types
|
||||
from nova.db import migration
|
||||
from nova import compute
|
||||
from nova import volume
|
||||
from nova import vsa
|
||||
from nova.vsa import drive_types
|
||||
from nova.volume import volume_types
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
flags.DECLARE('fixed_range', 'nova.network.manager')
|
||||
@@ -1076,14 +1073,12 @@ class VsaCommands(object):
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.manager = manager.AuthManager()
|
||||
self.vsa_api = vsa.API()
|
||||
self.compute_api = compute.API()
|
||||
self.volume_api = volume.API()
|
||||
self.context = context.get_admin_context()
|
||||
|
||||
self._format_str_vsa = "%-5s %-15s %-25s %-10s %-6s "\
|
||||
"%-9s %-10s %-10s %10s"
|
||||
self._format_str_volume = "\t%-4s %-15s %-5s %-10s %-20s %s"
|
||||
self._format_str_drive = "\t%-4s %-15s %-5s %-10s %-20s %s"
|
||||
self._format_str_drive = "\t%-4s %-15s %-5s %-10s %-20s %-4s %-10s %s"
|
||||
self._format_str_instance = "\t%-4s %-10s %-20s %-12s %-10s "\
|
||||
"%-15s %-15s %-10s %-15s %s"
|
||||
|
||||
@@ -1124,7 +1119,7 @@ class VsaCommands(object):
|
||||
def _print_volume(self, vol):
|
||||
print self._format_str_volume %\
|
||||
(vol['id'],
|
||||
vol['display_name'],
|
||||
vol['display_name'] or vol['name'],
|
||||
vol['size'],
|
||||
vol['status'],
|
||||
vol['attach_status'],
|
||||
@@ -1138,15 +1133,24 @@ class VsaCommands(object):
|
||||
_('size'),
|
||||
_('status'),
|
||||
_('host'),
|
||||
_('type'),
|
||||
_('typeName'),
|
||||
_('createTime'))
|
||||
|
||||
def _print_drive(self, drive):
|
||||
print self._format_str_volume %\
|
||||
if drive['volume_type_id'] is not None and drive.get('volume_type'):
|
||||
drive_type_name = drive['volume_type'].get('name')
|
||||
else:
|
||||
drive_type_name = ''
|
||||
|
||||
print self._format_str_drive %\
|
||||
(drive['id'],
|
||||
drive['display_name'],
|
||||
drive['size'],
|
||||
drive['status'],
|
||||
drive['host'],
|
||||
drive['volume_type_id'],
|
||||
drive_type_name,
|
||||
str(drive['created_at']))
|
||||
|
||||
def _print_instance_header(self):
|
||||
@@ -1196,9 +1200,7 @@ class VsaCommands(object):
|
||||
vsa_id = vsa.get('id')
|
||||
|
||||
if print_instances:
|
||||
instances = self.compute_api.get_all(context,
|
||||
search_opts={'metadata':
|
||||
dict(vsa_id=str(vsa_id))})
|
||||
instances = self.vsa_api.get_all_vsa_instances(context, vsa_id)
|
||||
if instances:
|
||||
print
|
||||
self._print_instance_header()
|
||||
@@ -1207,8 +1209,7 @@ class VsaCommands(object):
|
||||
print
|
||||
|
||||
if print_drives:
|
||||
drives = self.volume_api.get_all_by_vsa(context,
|
||||
vsa_id, "to")
|
||||
drives = self.vsa_api.get_all_vsa_drives(context, vsa_id)
|
||||
if drives:
|
||||
self._print_drive_header()
|
||||
for drive in drives:
|
||||
@@ -1216,8 +1217,7 @@ class VsaCommands(object):
|
||||
print
|
||||
|
||||
if print_volumes:
|
||||
volumes = self.volume_api.get_all_by_vsa(context,
|
||||
vsa_id, "from")
|
||||
volumes = self.vsa_api.get_all_vsa_volumes(context, vsa_id)
|
||||
if volumes:
|
||||
self._print_volume_header()
|
||||
for volume in volumes:
|
||||
@@ -1344,7 +1344,7 @@ class VsaCommands(object):
|
||||
|
||||
@args('--id', dest='vsa_id', metavar="<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):
|
||||
|
||||
10
bin/nova-vsa
10
bin/nova-vsa
@@ -4,6 +4,7 @@
|
||||
# Copyright (c) 2011 Zadara Storage Inc.
|
||||
# Copyright (c) 2011 OpenStack LLC.
|
||||
#
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
@@ -17,6 +18,10 @@
|
||||
# under the License.
|
||||
|
||||
"""Starter script for Nova VSA."""
|
||||
|
||||
import eventlet
|
||||
eventlet.monkey_patch()
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
@@ -28,6 +33,7 @@ possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||
if os.path.exists(os.path.join(possible_topdir, 'nova', '__init__.py')):
|
||||
sys.path.insert(0, possible_topdir)
|
||||
|
||||
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import service
|
||||
@@ -37,5 +43,7 @@ if __name__ == '__main__':
|
||||
utils.default_flagfile()
|
||||
flags.FLAGS(sys.argv)
|
||||
logging.setup()
|
||||
service.serve()
|
||||
utils.monkey_patch()
|
||||
server = service.Service.create(binary='nova-vsa')
|
||||
service.serve(server)
|
||||
service.wait()
|
||||
|
||||
@@ -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
|
||||
@@ -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)}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
####################
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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!")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
@@ -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())
|
||||
|
||||
@@ -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]))
|
||||
@@ -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())
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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'])
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
148
nova/vsa/api.py
148
nova/vsa/api.py
@@ -20,22 +20,26 @@ Handles all requests relating to Virtual Storage Arrays (VSAs).
|
||||
"""
|
||||
|
||||
import sys
|
||||
import base64
|
||||
|
||||
from xml.etree import ElementTree
|
||||
|
||||
from nova import compute
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova import flags
|
||||
from nova import log as logging
|
||||
from nova import quota
|
||||
from nova import rpc
|
||||
from nova.db import base
|
||||
|
||||
from nova import compute
|
||||
from nova import volume
|
||||
from nova.compute import instance_types
|
||||
from nova.vsa import drive_types
|
||||
from nova.db import base
|
||||
from nova.volume import volume_types
|
||||
|
||||
|
||||
class VsaState:
|
||||
CREATING = 'creating' # VSA creating (not ready yet)
|
||||
LAUNCHING = 'launching' # Launching VCs (all BE volumes were created)
|
||||
CREATED = 'created' # VSA fully created and ready for use
|
||||
PARTIAL = 'partial' # Some BE drives were allocated
|
||||
FAILED = 'failed' # Some BE storage allocations failed
|
||||
DELETING = 'deleting' # VSA started the deletion procedure
|
||||
|
||||
|
||||
FLAGS = flags.FLAGS
|
||||
@@ -43,22 +47,14 @@ flags.DEFINE_string('vsa_ec2_access_key', None,
|
||||
'EC2 access key used by VSA for accessing nova')
|
||||
flags.DEFINE_string('vsa_ec2_user_id', None,
|
||||
'User ID used by VSA for accessing nova')
|
||||
|
||||
flags.DEFINE_boolean('vsa_multi_vol_creation', True,
|
||||
'Ask scheduler to create multiple volumes in one call')
|
||||
flags.DEFINE_string('vsa_volume_type_name', 'VSA volume type',
|
||||
'Name of volume type associated with FE VSA volumes')
|
||||
|
||||
LOG = logging.getLogger('nova.vsa')
|
||||
|
||||
|
||||
class VsaState:
|
||||
CREATING = 'creating' # VSA creating (not ready yet)
|
||||
LAUNCHING = 'launching' # Launching VCs (all BE volumes were created)
|
||||
CREATED = 'created' # VSA fully created and ready for use
|
||||
PARTIAL = 'partial' # Some BE storage allocations failed
|
||||
FAILED = 'failed' # Some BE storage allocations failed
|
||||
DELETING = 'deleting' # VSA started the deletion procedure
|
||||
|
||||
|
||||
class API(base.Base):
|
||||
"""API for interacting with the VSA manager."""
|
||||
|
||||
@@ -67,6 +63,15 @@ class API(base.Base):
|
||||
self.volume_api = volume_api or volume.API()
|
||||
super(API, self).__init__(**kwargs)
|
||||
|
||||
def _check_volume_type_correctness(self, vol_type):
|
||||
if vol_type.get('extra_specs') == None or\
|
||||
vol_type['extra_specs'].get('type') != 'vsa_drive' or\
|
||||
vol_type['extra_specs'].get('drive_type') == None or\
|
||||
vol_type['extra_specs'].get('drive_size') == None:
|
||||
|
||||
raise exception.ApiError(_("Invalid drive type %s")
|
||||
% vol_type['name'])
|
||||
|
||||
def _get_default_vsa_instance_type(self):
|
||||
return instance_types.get_instance_type_by_name(
|
||||
FLAGS.default_vsa_instance_type)
|
||||
@@ -89,16 +94,17 @@ class API(base.Base):
|
||||
if name is None:
|
||||
raise exception.ApiError(_("No drive_name param found in %s")
|
||||
% node)
|
||||
|
||||
# find DB record for this disk
|
||||
try:
|
||||
drive_ref = drive_types.get_by_name(context, name)
|
||||
vol_type = volume_types.get_volume_type_by_name(context, name)
|
||||
except exception.NotFound:
|
||||
raise exception.ApiError(_("Invalid drive type name %s")
|
||||
% name)
|
||||
|
||||
self._check_volume_type_correctness(vol_type)
|
||||
|
||||
# if size field present - override disk size specified in DB
|
||||
size = node.get('size', drive_ref['size_gb'])
|
||||
size = int(node.get('size',
|
||||
vol_type['extra_specs'].get('drive_size')))
|
||||
|
||||
if shared:
|
||||
part_size = FLAGS.vsa_part_size_gb
|
||||
@@ -110,17 +116,15 @@ class API(base.Base):
|
||||
size = 0 # special handling for full drives
|
||||
|
||||
for i in range(num_volumes):
|
||||
# volume_name = vsa_name + ("_%s_vol-%d" % (name, i))
|
||||
volume_name = "drive-%03d" % first_index
|
||||
first_index += 1
|
||||
volume_desc = 'BE volume for VSA %s type %s' % \
|
||||
(vsa_name, name)
|
||||
volume = {
|
||||
'size': size,
|
||||
'snapshot_id': None,
|
||||
'name': volume_name,
|
||||
'description': volume_desc,
|
||||
'drive_ref': drive_ref
|
||||
'volume_type_id': vol_type['id'],
|
||||
}
|
||||
volume_params.append(volume)
|
||||
|
||||
@@ -211,7 +215,7 @@ class API(base.Base):
|
||||
if len(volume_params) > 0:
|
||||
request_spec = {
|
||||
'num_volumes': len(volume_params),
|
||||
'vsa_id': vsa_id,
|
||||
'vsa_id': str(vsa_id),
|
||||
'volumes': volume_params,
|
||||
}
|
||||
|
||||
@@ -227,17 +231,21 @@ class API(base.Base):
|
||||
try:
|
||||
vol_name = vol['name']
|
||||
vol_size = vol['size']
|
||||
vol_type_id = vol['volume_type_id']
|
||||
LOG.debug(_("VSA ID %(vsa_id)d %(vsa_name)s: Create "\
|
||||
"volume %(vol_name)s, %(vol_size)d GB"),
|
||||
locals())
|
||||
"volume %(vol_name)s, %(vol_size)d GB, "\
|
||||
"type %(vol_type_id)s"), locals())
|
||||
|
||||
vol_type = volume_types.get_volume_type(context,
|
||||
vol['volume_type_id'])
|
||||
|
||||
vol_ref = self.volume_api.create(context,
|
||||
vol_size,
|
||||
vol['snapshot_id'],
|
||||
None,
|
||||
vol_name,
|
||||
vol['description'],
|
||||
to_vsa_id=vsa_id,
|
||||
drive_type_id=vol['drive_ref'].get('id'),
|
||||
volume_type=vol_type,
|
||||
metadata=dict(to_vsa_id=str(vsa_id)),
|
||||
availability_zone=availability_zone)
|
||||
except:
|
||||
self.update_vsa_status(context, vsa_id,
|
||||
@@ -249,7 +257,7 @@ class API(base.Base):
|
||||
rpc.cast(context,
|
||||
FLAGS.vsa_topic,
|
||||
{"method": "create_vsa",
|
||||
"args": {"vsa_id": vsa_id}})
|
||||
"args": {"vsa_id": str(vsa_id)}})
|
||||
|
||||
return vsa_ref
|
||||
|
||||
@@ -314,8 +322,7 @@ class API(base.Base):
|
||||
def _force_volume_delete(self, ctxt, volume):
|
||||
"""Delete a volume, bypassing the check that it must be available."""
|
||||
host = volume['host']
|
||||
if not host or volume['from_vsa_id']:
|
||||
# Volume not yet assigned to host OR FE volume
|
||||
if not host:
|
||||
# Deleting volume from database and skipping rpc.
|
||||
self.db.volume_destroy(ctxt, volume['id'])
|
||||
return
|
||||
@@ -328,9 +335,9 @@ class API(base.Base):
|
||||
def delete_vsa_volumes(self, context, vsa_id, direction,
|
||||
force_delete=True):
|
||||
if direction == "FE":
|
||||
volumes = self.db.volume_get_all_assigned_from_vsa(context, vsa_id)
|
||||
volumes = self.get_all_vsa_volumes(context, vsa_id)
|
||||
else:
|
||||
volumes = self.db.volume_get_all_assigned_to_vsa(context, vsa_id)
|
||||
volumes = self.get_all_vsa_drives(context, vsa_id)
|
||||
|
||||
for volume in volumes:
|
||||
try:
|
||||
@@ -374,58 +381,25 @@ class API(base.Base):
|
||||
return self.db.vsa_get_all(context)
|
||||
return self.db.vsa_get_all_by_project(context, context.project_id)
|
||||
|
||||
def generate_user_data(self, context, vsa, volumes):
|
||||
SubElement = ElementTree.SubElement
|
||||
def get_vsa_volume_type(self, context):
|
||||
name = FLAGS.vsa_volume_type_name
|
||||
try:
|
||||
vol_type = volume_types.get_volume_type_by_name(context, name)
|
||||
except exception.NotFound:
|
||||
volume_types.create(context, name,
|
||||
extra_specs=dict(type='vsa_volume'))
|
||||
vol_type = volume_types.get_volume_type_by_name(context, name)
|
||||
|
||||
e_vsa = ElementTree.Element("vsa")
|
||||
return vol_type
|
||||
|
||||
e_vsa_detail = SubElement(e_vsa, "id")
|
||||
e_vsa_detail.text = str(vsa['id'])
|
||||
e_vsa_detail = SubElement(e_vsa, "name")
|
||||
e_vsa_detail.text = vsa['display_name']
|
||||
e_vsa_detail = SubElement(e_vsa, "description")
|
||||
e_vsa_detail.text = vsa['display_description']
|
||||
e_vsa_detail = SubElement(e_vsa, "vc_count")
|
||||
e_vsa_detail.text = str(vsa['vc_count'])
|
||||
def get_all_vsa_instances(self, context, vsa_id):
|
||||
return self.compute_api.get_all(context,
|
||||
search_opts={'metadata': dict(vsa_id=str(vsa_id))})
|
||||
|
||||
e_vsa_detail = SubElement(e_vsa, "auth_user")
|
||||
e_vsa_detail.text = FLAGS.vsa_ec2_user_id
|
||||
e_vsa_detail = SubElement(e_vsa, "auth_access_key")
|
||||
e_vsa_detail.text = FLAGS.vsa_ec2_access_key
|
||||
def get_all_vsa_volumes(self, context, vsa_id):
|
||||
return self.volume_api.get_all(context,
|
||||
search_opts={'metadata': dict(from_vsa_id=str(vsa_id))})
|
||||
|
||||
e_volumes = SubElement(e_vsa, "volumes")
|
||||
for volume in volumes:
|
||||
|
||||
loc = volume['provider_location']
|
||||
if loc is None:
|
||||
ip = ''
|
||||
iscsi_iqn = ''
|
||||
iscsi_portal = ''
|
||||
else:
|
||||
(iscsi_target, _sep, iscsi_iqn) = loc.partition(" ")
|
||||
(ip, iscsi_portal) = iscsi_target.split(":", 1)
|
||||
|
||||
e_vol = SubElement(e_volumes, "volume")
|
||||
e_vol_detail = SubElement(e_vol, "id")
|
||||
e_vol_detail.text = str(volume['id'])
|
||||
e_vol_detail = SubElement(e_vol, "name")
|
||||
e_vol_detail.text = volume['name']
|
||||
e_vol_detail = SubElement(e_vol, "display_name")
|
||||
e_vol_detail.text = volume['display_name']
|
||||
e_vol_detail = SubElement(e_vol, "size_gb")
|
||||
e_vol_detail.text = str(volume['size'])
|
||||
e_vol_detail = SubElement(e_vol, "status")
|
||||
e_vol_detail.text = volume['status']
|
||||
e_vol_detail = SubElement(e_vol, "ip")
|
||||
e_vol_detail.text = ip
|
||||
e_vol_detail = SubElement(e_vol, "iscsi_iqn")
|
||||
e_vol_detail.text = iscsi_iqn
|
||||
e_vol_detail = SubElement(e_vol, "iscsi_portal")
|
||||
e_vol_detail.text = iscsi_portal
|
||||
e_vol_detail = SubElement(e_vol, "lun")
|
||||
e_vol_detail.text = '0'
|
||||
e_vol_detail = SubElement(e_vol, "sn_host")
|
||||
e_vol_detail.text = volume['host']
|
||||
|
||||
_xml = ElementTree.tostring(e_vsa)
|
||||
return base64.b64encode(_xml)
|
||||
def get_all_vsa_drives(self, context, vsa_id):
|
||||
return self.volume_api.get_all(context,
|
||||
search_opts={'metadata': dict(to_vsa_id=str(vsa_id))})
|
||||
|
||||
@@ -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)
|
||||
@@ -16,7 +16,7 @@
|
||||
# under the License.
|
||||
|
||||
|
||||
class FakeVcConnection:
|
||||
class FakeVcConnection(object):
|
||||
|
||||
def init_host(self, host):
|
||||
pass
|
||||
|
||||
@@ -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
80
nova/vsa/utils.py
Normal 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)
|
||||
Reference in New Issue
Block a user