Resize live volumes attached to an instance

This commit is contained in:
Nirmal Ranganathan 2012-06-05 16:42:18 -05:00
parent 2ff6e1d09e
commit ebd2d227d6
10 changed files with 169 additions and 36 deletions

View File

@ -39,6 +39,7 @@ if os.path.exists(os.path.join(possible_topdir, 'reddwarf', '__init__.py')):
from reddwarf import version
from reddwarf.common import config
from reddwarf.common import service
from reddwarf.db import db_api
if __name__ == '__main__':
@ -51,6 +52,7 @@ if __name__ == '__main__':
try:
conf, app = config.Config.load_paste_app('reddwarf-taskmanager',
options, args)
db_api.configure_db(conf)
server = service.Service.create(binary='reddwarf-taskmanager')
service.serve(server)
service.wait()

View File

@ -11,7 +11,7 @@ rabbit_password=f7999d1955c5014aa32c
# SQLAlchemy connection string for the reference implementation
# registry server. Any valid SQLAlchemy connection string is fine.
# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
sql_connection = sqlite:///reddwarf_test.sqlite
sql_connection = mysql://root:e1a2c042c828d3566d0a@localhost/reddwarf
# sql_connection = mysql://root:root@localhost/reddwarf
# Period in seconds after which SQLAlchemy should reestablish its connection
@ -24,8 +24,16 @@ sql_connection = sqlite:///reddwarf_test.sqlite
sql_idle_timeout = 3600
#DB Api Implementation
db_api_implementation = "reddwarf.db.sqlalchemy.api"
db_api_implementation = reddwarf.db.sqlalchemy.api
# Configuration options for talking to nova via the novaclient.
reddwarf_auth_url = http://0.0.0.0:5000/v2.0
nova_compute_url = http://localhost:8774/v2
nova_volume_url = http://localhost:8776/v1
# Config options for enabling volume service
reddwarf_volume_support = True
volume_time_out=30
# Configuration options for talking to nova via the novaclient.
# These options are for an admin user in your keystone config.

View File

@ -46,9 +46,11 @@ add_addresses = True
# Config options for enabling volume service
reddwarf_volume_support = True
block_device_mapping = vdb
block_device_mapping = /var/lib/mysql
device_path = /dev/vdb
mount_point = /var/lib/mysql
max_accepted_volume_size = 10
volume_time_out=30
# Reddwarf DNS
reddwarf_dns_support = False

View File

@ -61,6 +61,8 @@ nova_volume_service_type = volume
nova_volume_service_name = Volume Service
device_path = /dev/vdb
mount_point = /var/lib/mysql
max_accepted_volume_size = 10
volume_time_out=30
# ============ notifer queue kombu connection options ========================

View File

@ -28,7 +28,7 @@ _ENGINE = None
_MAKER = None
LOG = logging.getLogger('reddwarf.db.sqlalchemy.session')
LOG = logging.getLogger(__name__)
def configure_db(options, models_mapper=None):
@ -82,10 +82,12 @@ def _create_engine(options):
def get_session(autocommit=True, expire_on_commit=False):
"""Helper method to grab session."""
global _MAKER, _ENGINE
if not _MAKER:
assert _ENGINE
if not _ENGINE:
msg = "***The Database has not been setup!!!***"
LOG.exception(msg)
raise RuntimeError(msg)
_MAKER = sessionmaker(bind=_ENGINE,
autocommit=autocommit,
expire_on_commit=expire_on_commit)

View File

@ -39,6 +39,7 @@ from reddwarf.common.utils import poll_until
from reddwarf.guestagent import api as guest_api
from reddwarf.instance.tasks import InstanceTask
from reddwarf.instance.tasks import InstanceTasks
from reddwarf.taskmanager import api as task_api
from eventlet import greenthread
@ -79,6 +80,8 @@ def load_volumes(context, server_id, client=None):
volume_info = volume_client.volumes.get(volume_id)
volume = {'id': volume_info.id,
'size': volume_info.size}
if volume_info.attachments:
volume['mountpoint'] = volume_info.attachments[0]['device']
volumes.append(volume)
except nova_exceptions.NotFound, e:
LOG.debug("Could not find nova server_id(%s)" % server_id)
@ -518,10 +521,18 @@ class Instance(object):
def resize_volume(self, new_size):
LOG.info("Resizing volume of instance %s..." % self.id)
# TODO(tim.simpson): Validate old_size < new_size, or raise
# rd_exceptions.BadRequest.
# TODO(tim.simpson): resize volume.
raise RuntimeError("Not implemented (yet).")
if len(self.volumes) != 1:
raise rd_exceptions.BadRequest("The instance has %r attached "
"volumes" % len(self.volumes))
old_size = self.volumes[0]['size']
if int(new_size) <= old_size:
raise rd_exceptions.BadRequest("The new volume 'size' cannot be "
"less than the current volume size of '%s'" % old_size)
# Set the task to Resizing before sending off to the taskmanager
self.db_info.task_status = InstanceTasks.RESIZING
self.db_info.save()
task_api.API(self.context).resize_volume(new_size, self.id)
def restart(self):
if self.server.status in SERVER_INVALID_ACTION_STATUSES:

View File

@ -167,11 +167,8 @@ class InstanceController(BaseController):
raise exception.BadRequest(_("Missing resize arguments."))
def _action_resize_volume(self, instance, volume):
if 'size' not in volume:
raise exception.BadRequest(
"Missing 'size' property of 'volume' in request body.")
new_size = volume['size']
instance.resize_volume(new_size)
InstanceController._validate_resize_volume(volume)
instance.resize_volume(volume['size'])
return webob.exc.HTTPAccepted()
def _action_resize_flavor(self, instance, flavorRef):
@ -277,6 +274,16 @@ class InstanceController(BaseController):
msg = "The request contains an empty body"
raise exception.ReddwarfError(msg)
@staticmethod
def _validate_resize_volume(volume):
"""
We are going to check that volume resizing data is present.
"""
if 'size' not in volume:
raise rd_exceptions.BadRequest(
"Missing 'size' property of 'volume' in request body.")
InstanceController._validate_volume_size(volume['size'])
@staticmethod
def _validate_volume_size(size):
"""Validate the various possible errors for volume size"""
@ -292,13 +299,13 @@ class InstanceController(BaseController):
"integer value, %s cannot be accepted."
% volume_size)
raise exception.ReddwarfError(msg)
#TODO(cp16net) add in the volume validation when volumes are supported
# max_size = FLAGS.reddwarf_max_accepted_volume_size
# if int(volume_size) > max_size:
# msg = ("Volume 'size' cannot exceed maximum "
# "of %d Gb, %s cannot be accepted."
# % (max_size, volume_size))
# raise exception.ReddwarfError(msg)
max_size = int(config.Config.get('max_accepted_volume_size',
1))
if int(volume_size) > max_size:
msg = ("Volume 'size' cannot exceed maximum "
"of %d Gb, %s cannot be accepted."
% (max_size, volume_size))
raise exception.ReddwarfError(msg)
@staticmethod
def _validate(body):
@ -307,26 +314,17 @@ class InstanceController(BaseController):
try:
body['instance']
body['instance']['flavorRef']
# TODO(cp16net) add in volume to the mix
if 'volume' in body['instance'] and \
body['instance']['volume'] is not None:
vol_enabled = utils.bool_from_string(
config.Config.get('reddwarf_volume_support',
'True'))
if vol_enabled:
volume_size = body['instance']['volume']['size']
InstanceController._validate_volume_size(volume_size)
except KeyError as e:
LOG.error(_("Create Instance Required field(s) - %s") % e)
raise exception.ReddwarfError("Required element/key - %s "
"was not specified" % e)
@staticmethod
def _validate_resize_instance(body):
""" Validate that the resize body has the attributes for flavorRef """
try:
body['resize']
body['resize']['flavorRef']
except KeyError as e:
LOG.error(_("Resize Instance Required field(s) - %s") % e)
raise exception.ReddwarfError("Required element/key - %s "
"was not specified" % e)
class API(wsgi.Router):
"""API"""

View File

@ -55,3 +55,8 @@ class API(object):
def _get_routing_key(self):
"""Create the routing key for the taskmanager"""
return "taskmanager"
def resize_volume(self, new_size, instance_id):
LOG.debug("Making async call to resize volume for instance: %s"
% instance_id)
self._cast("resize_volume", new_size=new_size, instance_id=instance_id)

View File

@ -22,6 +22,7 @@ from eventlet import greenthread
from reddwarf.common import excutils
from reddwarf.common import service
from reddwarf.taskmanager import models
LOG = logging.getLogger(__name__)
@ -50,3 +51,7 @@ class TaskManager(service.Manager):
excutils.save_and_reraise_exception()
finally:
del self.tasks[greenthread.getcurrent()]
def resize_volume(self, context, instance_id, new_size):
instance_tasks = models.InstanceTasks.load(context, instance_id)
instance_tasks.resize_volume(new_size)

View File

@ -0,0 +1,98 @@
# Copyright 2012 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 logging
from eventlet import greenthread
from reddwarf.common import config
from reddwarf.common import exception
from reddwarf.common import remote
from reddwarf.common import utils
from reddwarf.instance import models as inst_models
LOG = logging.getLogger(__name__)
class InstanceTasks:
"""
Performs the various asynchronous instance related tasks.
"""
def __init__(self, context, db_info, server, volumes,
nova_client=None, volume_client=None, guest=None):
self.context = context
self.db_info = db_info
self.server = server
self.volumes = volumes
self.nova_client = nova_client
self.volume_client = volume_client
self.guest = guest
@property
def volume_id(self):
return self.volumes[0]['id']
@property
def volume_mountpoint(self):
mountpoint = self.volumes[0]['mountpoint']
if mountpoint[0] is not "/":
return "/%s" % mountpoint
else:
return mountpoint
@staticmethod
def load(context, id):
if context is None:
raise TypeError("Argument context not defined.")
elif id is None:
raise TypeError("Argument id not defined.")
try:
db_info = inst_models.DBInstance.find_by(id=id)
except exception.NotFound:
raise exception.NotFound(uuid=id)
server, volumes = inst_models.load_server_with_volumes(context,
db_info.id,
db_info.compute_instance_id)
nova_client = remote.create_nova_client(context)
volume_client = remote.create_nova_volume_client(context)
guest = remote.create_guest_client(context, id)
return InstanceTasks(context, db_info, server, volumes,
nova_client=nova_client,
volume_client=volume_client, guest=guest)
def resize_volume(self, new_size):
LOG.debug("%s: Resizing volume for instance: %s to %r GB"
% (greenthread.getcurrent(), self.server.id, new_size))
self.volume_client.volumes.resize(self.volume_id, int(new_size))
try:
utils.poll_until(
lambda: self.volume_client.volumes.get(self.volume_id),
lambda volume: volume.status == 'in-use',
sleep_time=2,
time_out=int(config.Config.get('volume_time_out')))
self.nova_client.volumes.rescan_server_volume(self.server,
self.volume_id)
self.guest.resize_fs(self.volume_mountpoint)
except exception.PollTimeOut as pto:
LOG.error("Timeout trying to rescan or resize the attached volume "
"filesystem for volume: %s" % self.volume_id)
except Exception as e:
LOG.error("Error encountered trying to rescan or resize the "
"attached volume filesystem for volume: %s"
% self.volume_id)
finally:
self.db_info.task_status = inst_models.InstanceTasks.NONE
self.db_info.save()