VSA code redesign. Drive types completely replaced by Volume types

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,146 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 Zadara Storage Inc.
# Copyright (c) 2011 OpenStack LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Unit Tests for drive types codecode
"""
import time
from nova import context
from nova import flags
from nova import log as logging
from nova import test
from nova.vsa import drive_types
FLAGS = flags.FLAGS
LOG = logging.getLogger('nova.tests.test_drive_types')
class DriveTypesTestCase(test.TestCase):
"""Test cases for driver types code"""
def setUp(self):
super(DriveTypesTestCase, self).setUp()
self.ctxt = context.RequestContext(None, None)
self.ctxt_admin = context.get_admin_context()
self._dtype = self._create_default_drive_type()
def tearDown(self):
self._dtype = None
def _create_default_drive_type(self):
"""Create a volume object."""
dtype = {}
dtype['type'] = 'SATA'
dtype['size_gb'] = 150
dtype['rpm'] = 5000
dtype['capabilities'] = None
dtype['visible'] = True
LOG.debug(_("Default values for Drive Type: %s"), dtype)
return dtype
def test_drive_type_create_delete(self):
dtype = self._dtype
prev_all_dtypes = drive_types.get_all(self.ctxt_admin, False)
new = drive_types.create(self.ctxt_admin, **dtype)
for k, v in dtype.iteritems():
self.assertEqual(v, new[k], 'one of fields doesnt match')
new_all_dtypes = drive_types.get_all(self.ctxt_admin, False)
self.assertNotEqual(len(prev_all_dtypes),
len(new_all_dtypes),
'drive type was not created')
drive_types.delete(self.ctxt_admin, new['id'])
new_all_dtypes = drive_types.get_all(self.ctxt_admin, False)
self.assertEqual(prev_all_dtypes,
new_all_dtypes,
'drive types was not deleted')
def test_drive_type_check_name_generation(self):
dtype = self._dtype
new = drive_types.create(self.ctxt_admin, **dtype)
expected_name = FLAGS.drive_type_template_short % \
(dtype['type'], dtype['size_gb'], dtype['rpm'])
self.assertEqual(new['name'], expected_name,
'name was not generated correctly')
dtype['capabilities'] = 'SEC'
new2 = drive_types.create(self.ctxt_admin, **dtype)
expected_name = FLAGS.drive_type_template_long % \
(dtype['type'], dtype['size_gb'], dtype['rpm'],
dtype['capabilities'])
self.assertEqual(new2['name'], expected_name,
'name was not generated correctly')
drive_types.delete(self.ctxt_admin, new['id'])
drive_types.delete(self.ctxt_admin, new2['id'])
def test_drive_type_create_delete_invisible(self):
dtype = self._dtype
dtype['visible'] = False
prev_all_dtypes = drive_types.get_all(self.ctxt_admin, True)
new = drive_types.create(self.ctxt_admin, **dtype)
new_all_dtypes = drive_types.get_all(self.ctxt_admin, True)
self.assertEqual(prev_all_dtypes, new_all_dtypes)
new_all_dtypes = drive_types.get_all(self.ctxt_admin, False)
self.assertNotEqual(prev_all_dtypes, new_all_dtypes)
drive_types.delete(self.ctxt_admin, new['id'])
def test_drive_type_rename_update(self):
dtype = self._dtype
dtype['capabilities'] = None
new = drive_types.create(self.ctxt_admin, **dtype)
for k, v in dtype.iteritems():
self.assertEqual(v, new[k], 'one of fields doesnt match')
new_name = 'NEW_DRIVE_NAME'
new = drive_types.rename(self.ctxt_admin, new['name'], new_name)
self.assertEqual(new['name'], new_name)
new = drive_types.rename(self.ctxt_admin, new_name)
expected_name = FLAGS.drive_type_template_short % \
(dtype['type'], dtype['size_gb'], dtype['rpm'])
self.assertEqual(new['name'], expected_name)
changes = {'rpm': 7200}
new = drive_types.update(self.ctxt_admin, new['id'], **changes)
for k, v in changes.iteritems():
self.assertEqual(v, new[k], 'one of fields doesnt match')
drive_types.delete(self.ctxt_admin, new['id'])
def test_drive_type_get(self):
dtype = self._dtype
new = drive_types.create(self.ctxt_admin, **dtype)
new2 = drive_types.get(self.ctxt_admin, new['id'])
for k, v in new2.iteritems():
self.assertEqual(str(new[k]), str(new2[k]),
'one of fields doesnt match')
new2 = drive_types.get_by_name(self.ctxt_admin, new['name'])
for k, v in new.iteritems():
self.assertEqual(str(new[k]), str(new2[k]),
'one of fields doesnt match')
drive_types.delete(self.ctxt_admin, new['id'])

View File

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

View File

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

View File

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

View File

@ -20,22 +20,26 @@ Handles all requests relating to Virtual Storage Arrays (VSAs).
"""
import sys
import base64
from xml.etree import ElementTree
from nova import compute
from nova import db
from nova import exception
from nova import flags
from nova import log as logging
from nova import quota
from nova import rpc
from nova.db import base
from nova import compute
from nova import volume
from nova.compute import instance_types
from nova.vsa import drive_types
from nova.db import base
from nova.volume import volume_types
class VsaState:
CREATING = 'creating' # VSA creating (not ready yet)
LAUNCHING = 'launching' # Launching VCs (all BE volumes were created)
CREATED = 'created' # VSA fully created and ready for use
PARTIAL = 'partial' # Some BE drives were allocated
FAILED = 'failed' # Some BE storage allocations failed
DELETING = 'deleting' # VSA started the deletion procedure
FLAGS = flags.FLAGS
@ -43,22 +47,14 @@ flags.DEFINE_string('vsa_ec2_access_key', None,
'EC2 access key used by VSA for accessing nova')
flags.DEFINE_string('vsa_ec2_user_id', None,
'User ID used by VSA for accessing nova')
flags.DEFINE_boolean('vsa_multi_vol_creation', True,
'Ask scheduler to create multiple volumes in one call')
flags.DEFINE_string('vsa_volume_type_name', 'VSA volume type',
'Name of volume type associated with FE VSA volumes')
LOG = logging.getLogger('nova.vsa')
class VsaState:
CREATING = 'creating' # VSA creating (not ready yet)
LAUNCHING = 'launching' # Launching VCs (all BE volumes were created)
CREATED = 'created' # VSA fully created and ready for use
PARTIAL = 'partial' # Some BE storage allocations failed
FAILED = 'failed' # Some BE storage allocations failed
DELETING = 'deleting' # VSA started the deletion procedure
class API(base.Base):
"""API for interacting with the VSA manager."""
@ -67,6 +63,15 @@ class API(base.Base):
self.volume_api = volume_api or volume.API()
super(API, self).__init__(**kwargs)
def _check_volume_type_correctness(self, vol_type):
if vol_type.get('extra_specs') == None or\
vol_type['extra_specs'].get('type') != 'vsa_drive' or\
vol_type['extra_specs'].get('drive_type') == None or\
vol_type['extra_specs'].get('drive_size') == None:
raise exception.ApiError(_("Invalid drive type %s")
% vol_type['name'])
def _get_default_vsa_instance_type(self):
return instance_types.get_instance_type_by_name(
FLAGS.default_vsa_instance_type)
@ -89,16 +94,17 @@ class API(base.Base):
if name is None:
raise exception.ApiError(_("No drive_name param found in %s")
% node)
# find DB record for this disk
try:
drive_ref = drive_types.get_by_name(context, name)
vol_type = volume_types.get_volume_type_by_name(context, name)
except exception.NotFound:
raise exception.ApiError(_("Invalid drive type name %s")
% name)
self._check_volume_type_correctness(vol_type)
# if size field present - override disk size specified in DB
size = node.get('size', drive_ref['size_gb'])
size = int(node.get('size',
vol_type['extra_specs'].get('drive_size')))
if shared:
part_size = FLAGS.vsa_part_size_gb
@ -110,17 +116,15 @@ class API(base.Base):
size = 0 # special handling for full drives
for i in range(num_volumes):
# volume_name = vsa_name + ("_%s_vol-%d" % (name, i))
volume_name = "drive-%03d" % first_index
first_index += 1
volume_desc = 'BE volume for VSA %s type %s' % \
(vsa_name, name)
volume = {
'size': size,
'snapshot_id': None,
'name': volume_name,
'description': volume_desc,
'drive_ref': drive_ref
'volume_type_id': vol_type['id'],
}
volume_params.append(volume)
@ -211,7 +215,7 @@ class API(base.Base):
if len(volume_params) > 0:
request_spec = {
'num_volumes': len(volume_params),
'vsa_id': vsa_id,
'vsa_id': str(vsa_id),
'volumes': volume_params,
}
@ -227,17 +231,21 @@ class API(base.Base):
try:
vol_name = vol['name']
vol_size = vol['size']
vol_type_id = vol['volume_type_id']
LOG.debug(_("VSA ID %(vsa_id)d %(vsa_name)s: Create "\
"volume %(vol_name)s, %(vol_size)d GB"),
locals())
"volume %(vol_name)s, %(vol_size)d GB, "\
"type %(vol_type_id)s"), locals())
vol_type = volume_types.get_volume_type(context,
vol['volume_type_id'])
vol_ref = self.volume_api.create(context,
vol_size,
vol['snapshot_id'],
None,
vol_name,
vol['description'],
to_vsa_id=vsa_id,
drive_type_id=vol['drive_ref'].get('id'),
volume_type=vol_type,
metadata=dict(to_vsa_id=str(vsa_id)),
availability_zone=availability_zone)
except:
self.update_vsa_status(context, vsa_id,
@ -249,7 +257,7 @@ class API(base.Base):
rpc.cast(context,
FLAGS.vsa_topic,
{"method": "create_vsa",
"args": {"vsa_id": vsa_id}})
"args": {"vsa_id": str(vsa_id)}})
return vsa_ref
@ -314,8 +322,7 @@ class API(base.Base):
def _force_volume_delete(self, ctxt, volume):
"""Delete a volume, bypassing the check that it must be available."""
host = volume['host']
if not host or volume['from_vsa_id']:
# Volume not yet assigned to host OR FE volume
if not host:
# Deleting volume from database and skipping rpc.
self.db.volume_destroy(ctxt, volume['id'])
return
@ -328,9 +335,9 @@ class API(base.Base):
def delete_vsa_volumes(self, context, vsa_id, direction,
force_delete=True):
if direction == "FE":
volumes = self.db.volume_get_all_assigned_from_vsa(context, vsa_id)
volumes = self.get_all_vsa_volumes(context, vsa_id)
else:
volumes = self.db.volume_get_all_assigned_to_vsa(context, vsa_id)
volumes = self.get_all_vsa_drives(context, vsa_id)
for volume in volumes:
try:
@ -374,58 +381,25 @@ class API(base.Base):
return self.db.vsa_get_all(context)
return self.db.vsa_get_all_by_project(context, context.project_id)
def generate_user_data(self, context, vsa, volumes):
SubElement = ElementTree.SubElement
def get_vsa_volume_type(self, context):
name = FLAGS.vsa_volume_type_name
try:
vol_type = volume_types.get_volume_type_by_name(context, name)
except exception.NotFound:
volume_types.create(context, name,
extra_specs=dict(type='vsa_volume'))
vol_type = volume_types.get_volume_type_by_name(context, name)
e_vsa = ElementTree.Element("vsa")
return vol_type
e_vsa_detail = SubElement(e_vsa, "id")
e_vsa_detail.text = str(vsa['id'])
e_vsa_detail = SubElement(e_vsa, "name")
e_vsa_detail.text = vsa['display_name']
e_vsa_detail = SubElement(e_vsa, "description")
e_vsa_detail.text = vsa['display_description']
e_vsa_detail = SubElement(e_vsa, "vc_count")
e_vsa_detail.text = str(vsa['vc_count'])
def get_all_vsa_instances(self, context, vsa_id):
return self.compute_api.get_all(context,
search_opts={'metadata': dict(vsa_id=str(vsa_id))})
e_vsa_detail = SubElement(e_vsa, "auth_user")
e_vsa_detail.text = FLAGS.vsa_ec2_user_id
e_vsa_detail = SubElement(e_vsa, "auth_access_key")
e_vsa_detail.text = FLAGS.vsa_ec2_access_key
def get_all_vsa_volumes(self, context, vsa_id):
return self.volume_api.get_all(context,
search_opts={'metadata': dict(from_vsa_id=str(vsa_id))})
e_volumes = SubElement(e_vsa, "volumes")
for volume in volumes:
loc = volume['provider_location']
if loc is None:
ip = ''
iscsi_iqn = ''
iscsi_portal = ''
else:
(iscsi_target, _sep, iscsi_iqn) = loc.partition(" ")
(ip, iscsi_portal) = iscsi_target.split(":", 1)
e_vol = SubElement(e_volumes, "volume")
e_vol_detail = SubElement(e_vol, "id")
e_vol_detail.text = str(volume['id'])
e_vol_detail = SubElement(e_vol, "name")
e_vol_detail.text = volume['name']
e_vol_detail = SubElement(e_vol, "display_name")
e_vol_detail.text = volume['display_name']
e_vol_detail = SubElement(e_vol, "size_gb")
e_vol_detail.text = str(volume['size'])
e_vol_detail = SubElement(e_vol, "status")
e_vol_detail.text = volume['status']
e_vol_detail = SubElement(e_vol, "ip")
e_vol_detail.text = ip
e_vol_detail = SubElement(e_vol, "iscsi_iqn")
e_vol_detail.text = iscsi_iqn
e_vol_detail = SubElement(e_vol, "iscsi_portal")
e_vol_detail.text = iscsi_portal
e_vol_detail = SubElement(e_vol, "lun")
e_vol_detail.text = '0'
e_vol_detail = SubElement(e_vol, "sn_host")
e_vol_detail.text = volume['host']
_xml = ElementTree.tostring(e_vsa)
return base64.b64encode(_xml)
def get_all_vsa_drives(self, context, vsa_id):
return self.volume_api.get_all(context,
search_opts={'metadata': dict(to_vsa_id=str(vsa_id))})

View File

@ -1,114 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 Zadara Storage Inc.
# Copyright (c) 2011 OpenStack LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Handles all requests relating to Virtual Storage Arrays (VSAs).
"""
from nova import db
from nova import exception
from nova import flags
from nova import log as logging
FLAGS = flags.FLAGS
flags.DEFINE_string('drive_type_template_short', '%s_%sGB_%sRPM',
'Template string for generation of drive type name')
flags.DEFINE_string('drive_type_template_long', '%s_%sGB_%sRPM_%s',
'Template string for generation of drive type name')
LOG = logging.getLogger('nova.drive_types')
def _generate_default_drive_name(type, size_gb, rpm, capabilities):
if capabilities is None or capabilities == '':
return FLAGS.drive_type_template_short % \
(type, str(size_gb), rpm)
else:
return FLAGS.drive_type_template_long % \
(type, str(size_gb), rpm, capabilities)
def create(context, type, size_gb, rpm, capabilities='',
visible=True, name=None):
if name is None:
name = _generate_default_drive_name(type, size_gb, rpm,
capabilities)
LOG.debug(_("Creating drive type %(name)s: "\
"%(type)s %(size_gb)s %(rpm)s %(capabilities)s"), locals())
values = {
'type': type,
'size_gb': size_gb,
'rpm': rpm,
'capabilities': capabilities,
'visible': visible,
'name': name
}
return db.drive_type_create(context, values)
def update(context, id, **kwargs):
LOG.debug(_("Updating drive type with id %(id)s: %(kwargs)s"), locals())
updatable_fields = ['type',
'size_gb',
'rpm',
'capabilities',
'visible']
changes = {}
for field in updatable_fields:
if field in kwargs and \
kwargs[field] is not None and \
kwargs[field] != '':
changes[field] = kwargs[field]
# call update regadless if changes is empty or not
return db.drive_type_update(context, id, changes)
def rename(context, name, new_name=None):
if new_name is None or \
new_name == '':
disk = db.drive_type_get_by_name(context, name)
new_name = _generate_default_drive_name(disk['type'],
disk['size_gb'], disk['rpm'], disk['capabilities'])
LOG.debug(_("Renaming drive type %(name)s to %(new_name)s"), locals())
values = dict(name=new_name)
dtype = db.drive_type_get_by_name(context, name)
return db.drive_type_update(context, dtype['id'], values)
def delete(context, id):
LOG.debug(_("Deleting drive type %d"), id)
db.drive_type_destroy(context, id)
def get(context, id):
return db.drive_type_get(context, id)
def get_by_name(context, name):
return db.drive_type_get_by_name(context, name)
def get_all(context, visible=True):
return db.drive_type_get_all(context, visible)

View File

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

View File

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

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

@ -0,0 +1,80 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 Zadara Storage Inc.
# Copyright (c) 2011 OpenStack LLC.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import base64
from xml.etree import ElementTree
from nova import flags
FLAGS = flags.FLAGS
def generate_user_data(vsa, volumes):
SubElement = ElementTree.SubElement
e_vsa = ElementTree.Element("vsa")
e_vsa_detail = SubElement(e_vsa, "id")
e_vsa_detail.text = str(vsa['id'])
e_vsa_detail = SubElement(e_vsa, "name")
e_vsa_detail.text = vsa['display_name']
e_vsa_detail = SubElement(e_vsa, "description")
e_vsa_detail.text = vsa['display_description']
e_vsa_detail = SubElement(e_vsa, "vc_count")
e_vsa_detail.text = str(vsa['vc_count'])
e_vsa_detail = SubElement(e_vsa, "auth_user")
e_vsa_detail.text = FLAGS.vsa_ec2_user_id
e_vsa_detail = SubElement(e_vsa, "auth_access_key")
e_vsa_detail.text = FLAGS.vsa_ec2_access_key
e_volumes = SubElement(e_vsa, "volumes")
for volume in volumes:
loc = volume['provider_location']
if loc is None:
ip = ''
iscsi_iqn = ''
iscsi_portal = ''
else:
(iscsi_target, _sep, iscsi_iqn) = loc.partition(" ")
(ip, iscsi_portal) = iscsi_target.split(":", 1)
e_vol = SubElement(e_volumes, "volume")
e_vol_detail = SubElement(e_vol, "id")
e_vol_detail.text = str(volume['id'])
e_vol_detail = SubElement(e_vol, "name")
e_vol_detail.text = volume['name']
e_vol_detail = SubElement(e_vol, "display_name")
e_vol_detail.text = volume['display_name']
e_vol_detail = SubElement(e_vol, "size_gb")
e_vol_detail.text = str(volume['size'])
e_vol_detail = SubElement(e_vol, "status")
e_vol_detail.text = volume['status']
e_vol_detail = SubElement(e_vol, "ip")
e_vol_detail.text = ip
e_vol_detail = SubElement(e_vol, "iscsi_iqn")
e_vol_detail.text = iscsi_iqn
e_vol_detail = SubElement(e_vol, "iscsi_portal")
e_vol_detail.text = iscsi_portal
e_vol_detail = SubElement(e_vol, "lun")
e_vol_detail.text = '0'
e_vol_detail = SubElement(e_vol, "sn_host")
e_vol_detail.text = volume['host']
_xml = ElementTree.tostring(e_vsa)
return base64.b64encode(_xml)