Resize live volumes attached to an instance
This commit is contained in:
parent
2ff6e1d09e
commit
ebd2d227d6
@ -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()
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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 ========================
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -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"""
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
98
reddwarf/taskmanager/models.py
Normal file
98
reddwarf/taskmanager/models.py
Normal 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()
|
Loading…
x
Reference in New Issue
Block a user