diff --git a/bin/reddwarf-taskmanager b/bin/reddwarf-taskmanager index fa69d636ee..04f11b60bd 100755 --- a/bin/reddwarf-taskmanager +++ b/bin/reddwarf-taskmanager @@ -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() diff --git a/etc/reddwarf/reddwarf-taskmanager.conf.sample b/etc/reddwarf/reddwarf-taskmanager.conf.sample index 5c6228744d..d7b5d4c4e3 100644 --- a/etc/reddwarf/reddwarf-taskmanager.conf.sample +++ b/etc/reddwarf/reddwarf-taskmanager.conf.sample @@ -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. diff --git a/etc/reddwarf/reddwarf.conf.sample b/etc/reddwarf/reddwarf.conf.sample index 2c1f496f19..1595a477bf 100644 --- a/etc/reddwarf/reddwarf.conf.sample +++ b/etc/reddwarf/reddwarf.conf.sample @@ -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 diff --git a/etc/reddwarf/reddwarf.conf.test b/etc/reddwarf/reddwarf.conf.test index 72cfaa8e87..6124dab53c 100644 --- a/etc/reddwarf/reddwarf.conf.test +++ b/etc/reddwarf/reddwarf.conf.test @@ -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 ======================== diff --git a/reddwarf/db/sqlalchemy/session.py b/reddwarf/db/sqlalchemy/session.py index 9b560edeff..4b779de4f0 100644 --- a/reddwarf/db/sqlalchemy/session.py +++ b/reddwarf/db/sqlalchemy/session.py @@ -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) diff --git a/reddwarf/instance/models.py b/reddwarf/instance/models.py index 4e1fdc0a2d..68973d8107 100644 --- a/reddwarf/instance/models.py +++ b/reddwarf/instance/models.py @@ -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: diff --git a/reddwarf/instance/service.py b/reddwarf/instance/service.py index e4b68eedf2..a938e82eb4 100644 --- a/reddwarf/instance/service.py +++ b/reddwarf/instance/service.py @@ -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""" diff --git a/reddwarf/taskmanager/api.py b/reddwarf/taskmanager/api.py index 4704f93ffa..99ab017b07 100644 --- a/reddwarf/taskmanager/api.py +++ b/reddwarf/taskmanager/api.py @@ -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) diff --git a/reddwarf/taskmanager/manager.py b/reddwarf/taskmanager/manager.py index 7560450ea5..ebcfb8a780 100644 --- a/reddwarf/taskmanager/manager.py +++ b/reddwarf/taskmanager/manager.py @@ -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) diff --git a/reddwarf/taskmanager/models.py b/reddwarf/taskmanager/models.py new file mode 100644 index 0000000000..741c95425c --- /dev/null +++ b/reddwarf/taskmanager/models.py @@ -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()