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