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 import version
|
||||||
from reddwarf.common import config
|
from reddwarf.common import config
|
||||||
from reddwarf.common import service
|
from reddwarf.common import service
|
||||||
|
from reddwarf.db import db_api
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
@ -51,6 +52,7 @@ if __name__ == '__main__':
|
|||||||
try:
|
try:
|
||||||
conf, app = config.Config.load_paste_app('reddwarf-taskmanager',
|
conf, app = config.Config.load_paste_app('reddwarf-taskmanager',
|
||||||
options, args)
|
options, args)
|
||||||
|
db_api.configure_db(conf)
|
||||||
server = service.Service.create(binary='reddwarf-taskmanager')
|
server = service.Service.create(binary='reddwarf-taskmanager')
|
||||||
service.serve(server)
|
service.serve(server)
|
||||||
service.wait()
|
service.wait()
|
||||||
|
@ -11,7 +11,7 @@ rabbit_password=f7999d1955c5014aa32c
|
|||||||
# SQLAlchemy connection string for the reference implementation
|
# SQLAlchemy connection string for the reference implementation
|
||||||
# registry server. Any valid SQLAlchemy connection string is fine.
|
# registry server. Any valid SQLAlchemy connection string is fine.
|
||||||
# See: http://www.sqlalchemy.org/docs/05/reference/sqlalchemy/connections.html#sqlalchemy.create_engine
|
# 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
|
# sql_connection = mysql://root:root@localhost/reddwarf
|
||||||
|
|
||||||
# Period in seconds after which SQLAlchemy should reestablish its connection
|
# Period in seconds after which SQLAlchemy should reestablish its connection
|
||||||
@ -24,8 +24,16 @@ sql_connection = sqlite:///reddwarf_test.sqlite
|
|||||||
sql_idle_timeout = 3600
|
sql_idle_timeout = 3600
|
||||||
|
|
||||||
#DB Api Implementation
|
#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.
|
# Configuration options for talking to nova via the novaclient.
|
||||||
# These options are for an admin user in your keystone config.
|
# These options are for an admin user in your keystone config.
|
||||||
|
@ -46,9 +46,11 @@ add_addresses = True
|
|||||||
|
|
||||||
# Config options for enabling volume service
|
# Config options for enabling volume service
|
||||||
reddwarf_volume_support = True
|
reddwarf_volume_support = True
|
||||||
block_device_mapping = vdb
|
block_device_mapping = /var/lib/mysql
|
||||||
device_path = /dev/vdb
|
device_path = /dev/vdb
|
||||||
mount_point = /var/lib/mysql
|
mount_point = /var/lib/mysql
|
||||||
|
max_accepted_volume_size = 10
|
||||||
|
volume_time_out=30
|
||||||
|
|
||||||
# Reddwarf DNS
|
# Reddwarf DNS
|
||||||
reddwarf_dns_support = False
|
reddwarf_dns_support = False
|
||||||
|
@ -61,6 +61,8 @@ nova_volume_service_type = volume
|
|||||||
nova_volume_service_name = Volume Service
|
nova_volume_service_name = Volume Service
|
||||||
device_path = /dev/vdb
|
device_path = /dev/vdb
|
||||||
mount_point = /var/lib/mysql
|
mount_point = /var/lib/mysql
|
||||||
|
max_accepted_volume_size = 10
|
||||||
|
volume_time_out=30
|
||||||
|
|
||||||
# ============ notifer queue kombu connection options ========================
|
# ============ notifer queue kombu connection options ========================
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ _ENGINE = None
|
|||||||
_MAKER = None
|
_MAKER = None
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger('reddwarf.db.sqlalchemy.session')
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def configure_db(options, models_mapper=None):
|
def configure_db(options, models_mapper=None):
|
||||||
@ -82,10 +82,12 @@ def _create_engine(options):
|
|||||||
|
|
||||||
def get_session(autocommit=True, expire_on_commit=False):
|
def get_session(autocommit=True, expire_on_commit=False):
|
||||||
"""Helper method to grab session."""
|
"""Helper method to grab session."""
|
||||||
|
|
||||||
global _MAKER, _ENGINE
|
global _MAKER, _ENGINE
|
||||||
if not _MAKER:
|
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,
|
_MAKER = sessionmaker(bind=_ENGINE,
|
||||||
autocommit=autocommit,
|
autocommit=autocommit,
|
||||||
expire_on_commit=expire_on_commit)
|
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.guestagent import api as guest_api
|
||||||
from reddwarf.instance.tasks import InstanceTask
|
from reddwarf.instance.tasks import InstanceTask
|
||||||
from reddwarf.instance.tasks import InstanceTasks
|
from reddwarf.instance.tasks import InstanceTasks
|
||||||
|
from reddwarf.taskmanager import api as task_api
|
||||||
|
|
||||||
|
|
||||||
from eventlet import greenthread
|
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_info = volume_client.volumes.get(volume_id)
|
||||||
volume = {'id': volume_info.id,
|
volume = {'id': volume_info.id,
|
||||||
'size': volume_info.size}
|
'size': volume_info.size}
|
||||||
|
if volume_info.attachments:
|
||||||
|
volume['mountpoint'] = volume_info.attachments[0]['device']
|
||||||
volumes.append(volume)
|
volumes.append(volume)
|
||||||
except nova_exceptions.NotFound, e:
|
except nova_exceptions.NotFound, e:
|
||||||
LOG.debug("Could not find nova server_id(%s)" % server_id)
|
LOG.debug("Could not find nova server_id(%s)" % server_id)
|
||||||
@ -518,10 +521,18 @@ class Instance(object):
|
|||||||
|
|
||||||
def resize_volume(self, new_size):
|
def resize_volume(self, new_size):
|
||||||
LOG.info("Resizing volume of instance %s..." % self.id)
|
LOG.info("Resizing volume of instance %s..." % self.id)
|
||||||
# TODO(tim.simpson): Validate old_size < new_size, or raise
|
if len(self.volumes) != 1:
|
||||||
# rd_exceptions.BadRequest.
|
raise rd_exceptions.BadRequest("The instance has %r attached "
|
||||||
# TODO(tim.simpson): resize volume.
|
"volumes" % len(self.volumes))
|
||||||
raise RuntimeError("Not implemented (yet).")
|
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):
|
def restart(self):
|
||||||
if self.server.status in SERVER_INVALID_ACTION_STATUSES:
|
if self.server.status in SERVER_INVALID_ACTION_STATUSES:
|
||||||
|
@ -167,11 +167,8 @@ class InstanceController(BaseController):
|
|||||||
raise exception.BadRequest(_("Missing resize arguments."))
|
raise exception.BadRequest(_("Missing resize arguments."))
|
||||||
|
|
||||||
def _action_resize_volume(self, instance, volume):
|
def _action_resize_volume(self, instance, volume):
|
||||||
if 'size' not in volume:
|
InstanceController._validate_resize_volume(volume)
|
||||||
raise exception.BadRequest(
|
instance.resize_volume(volume['size'])
|
||||||
"Missing 'size' property of 'volume' in request body.")
|
|
||||||
new_size = volume['size']
|
|
||||||
instance.resize_volume(new_size)
|
|
||||||
return webob.exc.HTTPAccepted()
|
return webob.exc.HTTPAccepted()
|
||||||
|
|
||||||
def _action_resize_flavor(self, instance, flavorRef):
|
def _action_resize_flavor(self, instance, flavorRef):
|
||||||
@ -277,6 +274,16 @@ class InstanceController(BaseController):
|
|||||||
msg = "The request contains an empty body"
|
msg = "The request contains an empty body"
|
||||||
raise exception.ReddwarfError(msg)
|
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
|
@staticmethod
|
||||||
def _validate_volume_size(size):
|
def _validate_volume_size(size):
|
||||||
"""Validate the various possible errors for volume size"""
|
"""Validate the various possible errors for volume size"""
|
||||||
@ -292,13 +299,13 @@ class InstanceController(BaseController):
|
|||||||
"integer value, %s cannot be accepted."
|
"integer value, %s cannot be accepted."
|
||||||
% volume_size)
|
% volume_size)
|
||||||
raise exception.ReddwarfError(msg)
|
raise exception.ReddwarfError(msg)
|
||||||
#TODO(cp16net) add in the volume validation when volumes are supported
|
max_size = int(config.Config.get('max_accepted_volume_size',
|
||||||
# max_size = FLAGS.reddwarf_max_accepted_volume_size
|
1))
|
||||||
# if int(volume_size) > max_size:
|
if int(volume_size) > max_size:
|
||||||
# msg = ("Volume 'size' cannot exceed maximum "
|
msg = ("Volume 'size' cannot exceed maximum "
|
||||||
# "of %d Gb, %s cannot be accepted."
|
"of %d Gb, %s cannot be accepted."
|
||||||
# % (max_size, volume_size))
|
% (max_size, volume_size))
|
||||||
# raise exception.ReddwarfError(msg)
|
raise exception.ReddwarfError(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _validate(body):
|
def _validate(body):
|
||||||
@ -307,26 +314,17 @@ class InstanceController(BaseController):
|
|||||||
try:
|
try:
|
||||||
body['instance']
|
body['instance']
|
||||||
body['instance']['flavorRef']
|
body['instance']['flavorRef']
|
||||||
# TODO(cp16net) add in volume to the mix
|
vol_enabled = utils.bool_from_string(
|
||||||
if 'volume' in body['instance'] and \
|
config.Config.get('reddwarf_volume_support',
|
||||||
body['instance']['volume'] is not None:
|
'True'))
|
||||||
|
if vol_enabled:
|
||||||
volume_size = body['instance']['volume']['size']
|
volume_size = body['instance']['volume']['size']
|
||||||
|
InstanceController._validate_volume_size(volume_size)
|
||||||
except KeyError as e:
|
except KeyError as e:
|
||||||
LOG.error(_("Create Instance Required field(s) - %s") % e)
|
LOG.error(_("Create Instance Required field(s) - %s") % e)
|
||||||
raise exception.ReddwarfError("Required element/key - %s "
|
raise exception.ReddwarfError("Required element/key - %s "
|
||||||
"was not specified" % e)
|
"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):
|
class API(wsgi.Router):
|
||||||
"""API"""
|
"""API"""
|
||||||
|
@ -55,3 +55,8 @@ class API(object):
|
|||||||
def _get_routing_key(self):
|
def _get_routing_key(self):
|
||||||
"""Create the routing key for the taskmanager"""
|
"""Create the routing key for the taskmanager"""
|
||||||
return "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 excutils
|
||||||
from reddwarf.common import service
|
from reddwarf.common import service
|
||||||
|
from reddwarf.taskmanager import models
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
@ -50,3 +51,7 @@ class TaskManager(service.Manager):
|
|||||||
excutils.save_and_reraise_exception()
|
excutils.save_and_reraise_exception()
|
||||||
finally:
|
finally:
|
||||||
del self.tasks[greenthread.getcurrent()]
|
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