Refactoring the instance load method Adding back the volume used parameter. Added timeouts for all guest sync calls. Refactored the instance models.

This commit is contained in:
Nirmal Ranganathan 2012-06-17 11:01:14 -05:00
parent 3380bbb738
commit c7d93e7ce7
25 changed files with 383 additions and 200 deletions

View File

@ -74,7 +74,8 @@ class Commands(object):
def image_update(self, service_name, image_id):
db_api.configure_db(self.conf)
image = db_api.find_by(instance_models.ServiceImage, service_name=service_name)
image = db_api.find_by(instance_models.ServiceImage,
service_name=service_name)
if image is None:
# Create a new one
image = instance_models.ServiceImage()

View File

@ -33,6 +33,9 @@ nova_volume_url = http://localhost:8776/v1
# Config options for enabling volume service
reddwarf_volume_support = True
block_device_mapping = /var/lib/mysql
device_path = /var/lib/mysql
mount_point = /var/lib/mysql
volume_time_out=30
# Configuration options for talking to nova via the novaclient.
@ -50,6 +53,11 @@ taskmanager_manager=reddwarf.taskmanager.manager.TaskManager
# Reddwarf DNS
reddwarf_dns_support = False
# Guest related conf
agent_heartbeat_time = 10
agent_call_low_timeout = 5
agent_call_high_timeout = 100
# ============ notifer queue kombu connection options ========================
notifier_queue_hostname = localhost

View File

@ -47,7 +47,7 @@ add_addresses = True
# Config options for enabling volume service
reddwarf_volume_support = True
block_device_mapping = /var/lib/mysql
device_path = /dev/vdb
device_path = /var/lib/mysql
mount_point = /var/lib/mysql
max_accepted_volume_size = 10
volume_time_out=30
@ -58,6 +58,11 @@ reddwarf_dns_support = False
# Auth
admin_roles = [admin]
# Guest related conf
agent_heartbeat_time = 10
agent_call_low_timeout = 5
agent_call_high_timeout = 100
# ============ notifer queue kombu connection options ========================
notifier_queue_hostname = localhost

View File

@ -64,6 +64,14 @@ mount_point = /var/lib/mysql
max_accepted_volume_size = 10
volume_time_out=30
# Auth
admin_roles = [admin]
# Guest related conf
agent_heartbeat_time = 10
agent_call_low_timeout = 5
agent_call_high_timeout = 100
# ============ notifer queue kombu connection options ========================
notifier_queue_hostname = localhost

View File

@ -88,6 +88,11 @@ class GuestError(ReddwarfError):
"%(original_message)s.")
class GuestTimeout(ReddwarfError):
message = _("Timeout trying to connect to the Guest Agent.")
class BadRequest(ReddwarfError):
message = _("The server could not comply with the request since it is "
@ -147,3 +152,13 @@ class PollTimeOut(ReddwarfError):
class Forbidden(ReddwarfError):
message = _("User does not have admin privileges.")
class InvalidModelError(ReddwarfError):
message = _("The following values are invalid: %(errors)s")
class ModelNotFoundError(NotFound):
message = _("Not Found")

View File

@ -46,14 +46,15 @@ eventlet.patcher.monkey_patch(all=False, socket=True)
LOG = logging.getLogger('reddwarf.common.wsgi')
XMLNS = 'http://docs.openstack.org/database/api/v1.0'
CUSTOM_PLURALS_METADATA = {'databases':'', 'users':''}
CUSTOM_SERIALIZER_METADATA = {'instance': {'status':'', 'hostname':'',
'id':'', 'name':'','created':'', 'updated':''},
'volume': {'size':'', 'used':''},
'flavor': {'id':'', 'ram': '', 'name': ''},
'link': {'href':'', 'rel': ''},
'database': {'name':''},
'user': {'name':'', 'password':''}}
CUSTOM_PLURALS_METADATA = {'databases': '', 'users': ''}
CUSTOM_SERIALIZER_METADATA = {'instance': {'status': '', 'hostname': '',
'id': '', 'name': '', 'created': '',
'updated': ''},
'volume': {'size': '', 'used': ''},
'flavor': {'id': '', 'ram': '', 'name': ''},
'link': {'href': '', 'rel': ''},
'database': {'name': ''},
'user': {'name': '', 'password': ''}}
def versioned_urlmap(*args, **kwargs):

92
reddwarf/db/models.py Normal file
View File

@ -0,0 +1,92 @@
# Copyright 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 logging
from reddwarf import db
from reddwarf.common import exception
from reddwarf.common import models
from reddwarf.common import pagination
from reddwarf.common import utils
LOG = logging.getLogger(__name__)
class DatabaseModelBase(models.ModelBase):
_auto_generated_attrs = ['id']
@classmethod
def create(cls, **values):
values['id'] = utils.generate_uuid()
values['created'] = utils.utcnow()
instance = cls(**values).save()
if not instance.is_valid():
raise exception.InvalidModelError(errors=instance.errors)
return instance
def save(self):
if not self.is_valid():
raise exception.InvalidModelError(errors=self.errors)
self['updated'] = utils.utcnow()
LOG.debug(_("Saving %s: %s") %
(self.__class__.__name__, self.__dict__))
return db.db_api.save(self)
def delete(self):
self['updated'] = utils.utcnow()
LOG.debug(_("Deleting %s: %s") %
(self.__class__.__name__, self.__dict__))
return db.db_api.delete(self)
def __init__(self, **kwargs):
self.merge_attributes(kwargs)
if not self.is_valid():
raise exception.InvalidModelError(errors=self.errors)
def merge_attributes(self, values):
"""dict.update() behaviour."""
for k, v in values.iteritems():
self[k] = v
@classmethod
def find_by(cls, **conditions):
model = cls.get_by(**conditions)
if model is None:
raise exception.ModelNotFoundError(_("%s Not Found") %
cls.__name__)
return model
@classmethod
def get_by(cls, **kwargs):
return db.db_api.find_by(cls, **cls._process_conditions(kwargs))
@classmethod
def find_all(cls, **kwargs):
return db.db_query.find_all(cls, **cls._process_conditions(kwargs))
@classmethod
def _process_conditions(cls, raw_conditions):
"""Override in inheritors to format/modify any conditions."""
return raw_conditions
@classmethod
def find_by_pagination(cls, collection_type, collection_query,
paginated_url, **kwargs):
elements, next_marker = collection_query.paginated_collection(**kwargs)
return pagination.PaginatedDataView(collection_type,
elements,
paginated_url,
next_marker)

View File

@ -36,6 +36,8 @@ def map(engine, models):
Table('service_statuses', meta, autoload=True))
orm.mapper(models['dns_records'],
Table('dns_records', meta, autoload=True))
orm.mapper(models['agent_heartbeats'],
Table('agent_heartbeats', meta, autoload=True))
def mapping_exists(model):

View File

@ -42,11 +42,13 @@ def configure_db(options, models_mapper=None):
from reddwarf.instance import models as base_models
from reddwarf.dns import models as dns_models
from reddwarf.extensions.mysql import models as mysql_models
from reddwarf.guestagent import models as agent_models
model_modules = [
base_models,
dns_models,
mysql_models,
agent_models,
]
models = {}

View File

@ -22,9 +22,8 @@ Model classes that map instance Ip to dns record.
import logging
from reddwarf import db
from reddwarf.common import exception
from reddwarf.common.models import ModelBase
from reddwarf.instance.models import InvalidModelError
from reddwarf.instance.models import ModelNotFoundError
LOG = logging.getLogger(__name__)
@ -48,12 +47,12 @@ class DnsRecord(ModelBase):
def create(cls, **values):
record = cls(**values).save()
if not record.is_valid():
raise InvalidModelError(record.errors)
raise exception.InvalidModelError(errors=record.errors)
return record
def save(self):
if not self.is_valid():
raise InvalidModelError(self.errors)
raise exception.InvalidModelError(errors=self.errors)
LOG.debug(_("Saving %s: %s") %
(self.__class__.__name__, self.__dict__))
return db.db_api.save(self)
@ -67,7 +66,8 @@ class DnsRecord(ModelBase):
def find_by(cls, **conditions):
model = cls.get_by(**conditions)
if model is None:
raise ModelNotFoundError(_("%s Not Found") % cls.__name__)
raise exception.ModelNotFoundError(_("%s Not Found") %
cls.__name__)
return model
@classmethod

View File

@ -218,6 +218,3 @@ class RsDnsZone(object):
def __str__(self):
return "%s:%s" % (self.id, self.name)

View File

@ -22,7 +22,6 @@ from reddwarf.common import exception
from reddwarf.common import pagination
from reddwarf.common import wsgi
from reddwarf.guestagent.db import models as guest_models
from reddwarf.instance import models as instance_models
from reddwarf.extensions.mysql import models
from reddwarf.extensions.mysql import views
@ -44,7 +43,7 @@ class BaseController(wsgi.Controller):
],
webob.exc.HTTPNotFound: [
exception.NotFound,
instance_models.ModelNotFoundError,
exception.ModelNotFoundError,
],
webob.exc.HTTPConflict: [
],

View File

@ -20,7 +20,7 @@
from reddwarf import db
from novaclient import exceptions as nova_exceptions
from reddwarf.common import exception as rd_exceptions
from reddwarf.common import exception
from reddwarf.common import utils
from reddwarf.common.models import NovaRemoteModelBase
from reddwarf.common.remote import create_nova_client
@ -39,13 +39,13 @@ class Flavor(object):
client = create_nova_client(context)
self.flavor = client.flavors.get(flavor_id)
except nova_exceptions.NotFound, e:
raise rd_exceptions.NotFound(uuid=flavor_id)
raise exception.NotFound(uuid=flavor_id)
except nova_exceptions.ClientException, e:
raise rd_exceptions.ReddwarfError(str(e))
raise exception.ReddwarfError(str(e))
return
msg = ("Flavor is not defined, and"
" context and flavor_id were not specified.")
raise InvalidModelError(msg)
raise exception.InvalidModelError(errors=msg)
@property
def id(self):

View File

@ -19,16 +19,19 @@
Handles all request to the Platform or Guest VM
"""
import logging
from eventlet import Timeout
from reddwarf import rpc
from reddwarf.common import config
from reddwarf.common import exception
from reddwarf.common import utils
# from nova.db import api as dbapi
LOG = logging.getLogger(__name__)
AGENT_LOW_TIMEOUT = int(config.Config.get('agent_call_low_timeout', 5))
AGENT_HIGH_TIMEOUT = int(config.Config.get('agent_call_high_timeout', 60))
class API(object):
@ -38,22 +41,30 @@ class API(object):
self.context = context
self.id = id
def _call(self, method_name, **kwargs):
def _call(self, method_name, timeout_sec, **kwargs):
LOG.debug("Calling %s" % method_name)
timeout = Timeout(timeout_sec)
try:
result = rpc.call(self.context, self._get_routing_key(),
{"method": method_name, "args": kwargs})
{'method': method_name, 'args': kwargs})
LOG.debug("Result is %s" % result)
return result
except Exception as e:
LOG.error(e)
raise exception.GuestError(original_message=str(e))
except Timeout as t:
if t is not timeout:
raise
else:
raise exception.GuestTimeout()
finally:
timeout.cancel()
def _cast(self, method_name, **kwargs):
try:
rpc.cast(self.context, self._get_routing_key(),
{"method": method_name,
"args": kwargs})
{'method': method_name, 'args': kwargs})
except Exception as e:
LOG.error(e)
raise exception.GuestError(original_message=str(e))
@ -61,7 +72,7 @@ class API(object):
def _cast_with_consumer(self, method_name, **kwargs):
try:
rpc.cast_with_consumer(self.context, self._get_routing_key(),
{"method": method_name, "args": kwargs})
{'method': method_name, 'args': kwargs})
except Exception as e:
LOG.error(e)
raise exception.GuestError(original_message=str(e))
@ -78,8 +89,8 @@ class API(object):
def list_users(self, limit=None, marker=None, include_marker=False):
"""Make an asynchronous call to list database users"""
LOG.debug(_("Listing Users for Instance %s"), self.id)
return self._call("list_users", limit=limit, marker=marker,
include_marker=include_marker)
return self._call("list_users", AGENT_LOW_TIMEOUT, limit=limit,
marker=marker, include_marker=include_marker)
def delete_user(self, user):
"""Make an asynchronous call to delete an existing database user"""
@ -95,8 +106,8 @@ class API(object):
def list_databases(self, limit=None, marker=None, include_marker=False):
"""Make an asynchronous call to list databases"""
LOG.debug(_("Listing databases for Instance %s"), self.id)
return self._call("list_databases", limit=limit, marker=marker,
include_marker=include_marker)
return self._call("list_databases", AGENT_LOW_TIMEOUT, limit=limit,
marker=marker, include_marker=include_marker)
def delete_database(self, database):
"""Make an asynchronous call to delete an existing database
@ -108,24 +119,24 @@ class API(object):
"""Make a synchronous call to enable the root user for
access from anywhere"""
LOG.debug(_("Enable root user for Instance %s"), self.id)
return self._call("enable_root")
return self._call("enable_root", AGENT_LOW_TIMEOUT)
def disable_root(self):
"""Make a synchronous call to disable the root user for
access from anywhere"""
LOG.debug(_("Disable root user for Instance %s"), self.id)
return self._call("disable_root")
return self._call("disable_root", AGENT_LOW_TIMEOUT)
def is_root_enabled(self):
"""Make a synchronous call to check if root access is
available for the container"""
LOG.debug(_("Check root access for Instance %s"), self.id)
return self._call("is_root_enabled")
return self._call("is_root_enabled", AGENT_LOW_TIMEOUT)
def get_diagnostics(self):
"""Make a synchronous call to get diagnostics for the container"""
LOG.debug(_("Check diagnostics on Instance %s"), self.id)
return self._call("get_diagnostics")
return self._call("get_diagnostics", AGENT_LOW_TIMEOUT)
def prepare(self, memory_mb, databases, users,
device_path='/dev/vdb', mount_point='/mnt/volume'):
@ -139,20 +150,26 @@ class API(object):
def restart(self):
"""Restart the MySQL server."""
LOG.debug(_("Sending the call to restart MySQL on the Guest."))
self._call("restart")
self._call("restart", AGENT_HIGH_TIMEOUT)
def start_mysql_with_conf_changes(self, updated_memory_size):
"""Start the MySQL server."""
LOG.debug(_("Sending the call to start MySQL on the Guest."))
self._call("start_mysql_with_conf_changes",
self._call("start_mysql_with_conf_changes", AGENT_HIGH_TIMEOUT,
updated_memory_size=updated_memory_size)
def stop_mysql(self):
"""Stop the MySQL server."""
LOG.debug(_("Sending the call to stop MySQL on the Guest."))
self._call("stop_mysql")
self._call("stop_mysql", AGENT_HIGH_TIMEOUT)
def upgrade(self):
"""Make an asynchronous call to self upgrade the guest agent"""
LOG.debug(_("Sending an upgrade call to nova-guest"))
self._cast_with_consumer("upgrade")
def get_volume_info(self):
"""Make a synchronous call to get volume info for the container"""
LOG.debug(_("Check Volume Info on Instance %s"), self.id)
return self._call("get_filesystem_stats", AGENT_LOW_TIMEOUT,
fs_path="/var/lib/mysql")

View File

@ -72,6 +72,7 @@ INCLUDE_MARKER_OPERATORS = {
False: ">"
}
def generate_random_password():
return str(uuid.uuid4())

View File

@ -0,0 +1,65 @@
# Copyright 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 logging
from datetime import datetime
from datetime import timedelta
from reddwarf import db
from reddwarf.common import config
from reddwarf.common import exception
from reddwarf.common import utils
from reddwarf.db import models as dbmodels
LOG = logging.getLogger(__name__)
AGENT_HEARTBEAT = int(config.Config.get('agent_heartbeat_time', '10'))
def persisted_models():
return {
'agent_heartbeats': AgentHeartBeat,
}
class AgentHeartBeat(dbmodels.DatabaseModelBase):
"""Defines the state of a Guest Agent."""
_data_fields = ['instance_id', 'updated_at']
_table_name = 'agent_heartbeats'
def __init__(self, **kwargs):
super(AgentHeartBeat, self).__init__(**kwargs)
@classmethod
def create(cls, **values):
values['id'] = utils.generate_uuid()
heartbeat = cls(**values).save()
if not heartbeat.is_valid():
raise exception.InvalidModelError(errors=heartbeat.errors)
return heartbeat
def save(self):
if not self.is_valid():
raise exception.InvalidModelError(errors=self.errors)
self['updated_at'] = utils.utcnow()
LOG.debug(_("Saving %s: %s") %
(self.__class__.__name__, self.__dict__))
return db.db_api.save(self)
@staticmethod
def is_active(agent):
return (datetime.now() - agent.updated_at) < \
timedelta(seconds=AGENT_HEARTBEAT)

View File

@ -24,7 +24,8 @@ Intermediary class for building SQL queries for use by the guest agent.
class Query(object):
def __init__(self, columns=None, tables=None, where=None, order=None, group=None, limit=None):
def __init__(self, columns=None, tables=None, where=None, order=None,
group=None, limit=None):
self.columns = columns or []
self.tables = tables or []
self.where = where or []

View File

@ -21,27 +21,21 @@ import eventlet
import logging
import netaddr
from reddwarf import db
from novaclient import exceptions as nova_exceptions
from reddwarf.common import config
from reddwarf.common import exception as rd_exceptions
from reddwarf.common import pagination
from reddwarf.common import utils
from reddwarf.common.models import ModelBase
from novaclient import exceptions as nova_exceptions
from reddwarf.common import exception
from reddwarf.common.remote import create_dns_client
from reddwarf.common.remote import create_guest_client
from reddwarf.common.remote import create_nova_client
from reddwarf.common.remote import create_nova_volume_client
from reddwarf.common.utils import poll_until
from reddwarf.db import models as dbmodels
from reddwarf.instance.tasks import InstanceTask
from reddwarf.instance.tasks import InstanceTasks
from reddwarf.guestagent import models as agent_models
from reddwarf.taskmanager import api as task_api
from eventlet import greenthread
from reddwarf.instance.views import get_ip_address
CONFIG = config.Config
@ -55,10 +49,10 @@ def load_server(context, instance_id, server_id):
server = client.servers.get(server_id)
except nova_exceptions.NotFound, e:
LOG.debug("Could not find nova server_id(%s)" % server_id)
raise rd_exceptions.ComputeInstanceNotFound(instance_id=instance_id,
server_id=server_id)
raise exception.ComputeInstanceNotFound(instance_id=instance_id,
server_id=server_id)
except nova_exceptions.ClientException, e:
raise rd_exceptions.ReddwarfError(str(e))
raise exception.ReddwarfError(str(e))
return server
@ -80,7 +74,7 @@ def populate_databases(dbs):
databases.append(mydb.serialize())
return databases
except ValueError as ve:
raise rd_exceptions.BadRequest(ve.message)
raise exception.BadRequest(ve.message)
class InstanceStatus(object):
@ -114,7 +108,7 @@ def load_simple_instance_server_status(context, db_info):
# then assume the delete operation is done and raise an
# exception.
if InstanceTasks.DELETING == db_info.task_status:
raise rd_exceptions.NotFound(uuid=db_info.id)
raise exception.NotFound(uuid=db_info.id)
# If the compute server is in any of these states we can't perform any
@ -124,6 +118,9 @@ SERVER_INVALID_ACTION_STATUSES = ["BUILD", "REBOOT", "REBUILD"]
# Statuses in which an instance can have an action performed.
VALID_ACTION_STATUSES = ["ACTIVE"]
# Invalid states to contact the agent
AGENT_INVALID_STATUSES = ["BUILD", "REBOOT", "RESIZE"]
class SimpleInstance(object):
"""A simple view of an instance.
@ -168,24 +165,6 @@ class SimpleInstance(object):
"""True if the service status indicates MySQL is up and running."""
return self.service_status.status in MYSQL_RESPONSIVE_STATUSES
@staticmethod
def load(context, id):
try:
db_info = DBInstance.find_by(id=id)
except ModelNotFoundError:
raise rd_exceptions.NotFound(uuid=id)
service_status = InstanceServiceStatus.find_by(instance_id=id)
LOG.info("service status=%s" % service_status)
# TODO(tim.simpson): In the future, we'll listen to notifications and
# update the RDL database when the server status changes, and add
# server_status as a property to db_info, but for now we have to resort
# to this.
db_info = DBInstance.find_by(id=id)
if not context.is_admin and db_info.tenant_id != context.tenant:
raise rd_exceptions.NotFound(uuid=id)
load_simple_instance_server_status(context, db_info)
return SimpleInstance(context, db_info, service_status)
@property
def name(self):
return self.db_info.name
@ -234,19 +213,46 @@ class SimpleInstance(object):
return self.db_info.volume_size
def load_instance(cls, context, id, needs_server=False):
class DetailInstance(SimpleInstance):
"""A detailed view of an Instnace.
This loads a SimpleInstance and then adds additional data for the
instance from the guest.
"""
def __init__(self, context, db_info, service_status):
super(DetailInstance, self).__init__(context, db_info, service_status)
self._volume_used = None
@property
def volume_used(self):
return self._volume_used
@volume_used.setter
def volume_used(self, value):
self._volume_used = value
def get_db_info(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 = DBInstance.find_by(id=id)
except rd_exceptions.NotFound:
raise rd_exceptions.NotFound(uuid=id)
except exception.NotFound:
raise exception.NotFound(uuid=id)
except exception.ModelNotFoundError:
raise exception.NotFound(uuid=id)
if not context.is_admin and db_info.tenant_id != context.tenant:
LOG.error("Tenant %s tried to access instance %s, owned by %s."
% (context.tenant, id, db_info.tenant_id))
raise rd_exceptions.NotFound(uuid=id)
raise exception.NotFound(uuid=id)
return db_info
def load_instance(cls, context, id, needs_server=False):
db_info = get_db_info(context, id)
if not needs_server:
# TODO(tim.simpson): When we have notifications this won't be
# necessary and instead we'll just use the server_status field from
@ -260,17 +266,38 @@ def load_instance(cls, context, id, needs_server=False):
#TODO(tim.simpson): Remove this hack when we have notifications!
db_info.server_status = server.status
db_info.addresses = server.addresses
except rd_exceptions.ComputeInstanceNotFound:
except exception.ComputeInstanceNotFound:
LOG.error("COMPUTE ID = %s" % db_info.compute_instance_id)
raise rd_exceptions.UnprocessableEntity(
"Instance %s is not ready." % id)
raise exception.UnprocessableEntity("Instance %s is not ready." %
id)
task_status = db_info.task_status
service_status = InstanceServiceStatus.find_by(instance_id=id)
LOG.info("service status=%s" % service_status)
return cls(context, db_info, server, service_status)
def load_instance_with_guest(cls, context, id):
db_info = get_db_info(context, id)
load_simple_instance_server_status(context, db_info)
service_status = InstanceServiceStatus.find_by(instance_id=id)
LOG.info("service status=%s" % service_status)
instance = cls(context, db_info, service_status)
try:
agent = agent_models.AgentHeartBeat.find_by(instance_id=id)
except exception.ModelNotFoundError as mnfe:
LOG.warn(mnfe)
return instance
if instance.status not in AGENT_INVALID_STATUSES and \
agent_models.AgentHeartBeat.is_active(agent):
guest = create_guest_client(context, id)
try:
instance.volume_used = guest.get_volume_info()['used']
except Exception as e:
LOG.error(e)
return instance
class BaseInstance(SimpleInstance):
"""Represents an instance."""
@ -334,7 +361,7 @@ class Instance(BuiltInstance):
def delete(self, force=False):
if not force and \
self.db_info.server_status in SERVER_INVALID_ACTION_STATUSES:
raise rd_exceptions.UnprocessableEntity("Instance %s is not ready."
raise exception.UnprocessableEntity("Instance %s is not ready."
% self.id)
LOG.debug(_(" ... deleting compute id = %s") %
self.db_info.compute_instance_id)
@ -349,7 +376,7 @@ class Instance(BuiltInstance):
try:
flavor = client.flavors.get(flavor_id)
except nova_exceptions.NotFound:
raise rd_exceptions.FlavorNotFound(uuid=flavor_id)
raise exception.FlavorNotFound(uuid=flavor_id)
db_info = DBInstance.create(name=name,
flavor_id=flavor_id, tenant_id=context.tenant,
@ -375,7 +402,7 @@ class Instance(BuiltInstance):
msg = "Instance is not currently available for an action to be " \
"performed. Status [%s]"
LOG.debug(_(msg) % self.status)
raise rd_exceptions.UnprocessableEntity(_(msg) % self.status)
raise exception.UnprocessableEntity(_(msg) % self.status)
def resize_flavor(self, new_flavor_id):
self.validate_can_perform_resize()
@ -387,12 +414,12 @@ class Instance(BuiltInstance):
try:
new_flavor = client.flavors.get(new_flavor_id)
except nova_exceptions.NotFound:
raise rd_exceptions.FlavorNotFound(uuid=new_flavor_id)
raise exception.FlavorNotFound(uuid=new_flavor_id)
old_flavor = client.flavors.get(self.flavor_id)
new_flavor_size = new_flavor.ram
old_flavor_size = old_flavor.ram
if new_flavor_size == old_flavor_size:
raise rd_exceptions.CannotResizeToSameSize()
raise exception.CannotResizeToSameSize()
# Set the task to RESIZING and begin the async call before returning.
self.update_db(task_status=InstanceTasks.RESIZING)
@ -403,24 +430,22 @@ class Instance(BuiltInstance):
def resize_volume(self, new_size):
LOG.info("Resizing volume of instance %s..." % self.id)
if not self.volume_size:
raise rd_exceptions.BadRequest("Instance %s has no volume."
% self.id)
raise exception.BadRequest("Instance %s has no volume." % self.id)
old_size = self.volume_size
if int(new_size) <= old_size:
raise rd_exceptions.BadRequest("The new volume 'size' cannot be "
raise exception.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.update_db(task_status=InstanceTasks.RESIZING)
task_api.API(self.context).resize_volume(new_size, self.id)
def restart(self):
if self.db_info.server_status in SERVER_INVALID_ACTION_STATUSES:
msg = _("Restart instance not allowed while instance %s is in %s "
"status.") % (self.id, instance_state)
LOG.debug(msg)
# If the state is building then we throw an exception back
raise rd_exceptions.UnprocessableEntity(msg)
raise exception.UnprocessableEntity(msg)
else:
LOG.info("Restarting instance %s..." % self.id)
# Set our local status since Nova might not change it quick enough.
@ -442,7 +467,7 @@ class Instance(BuiltInstance):
"performed (task status was %s, service status was %s)." \
% (self.db_info.task_status, self.service_status.status)
LOG.error(msg)
raise rd_exceptions.UnprocessableEntity(msg)
raise exception.UnprocessableEntity(msg)
def validate_can_perform_resize(self):
"""
@ -452,7 +477,7 @@ class Instance(BuiltInstance):
msg = "Instance is not currently available for an action to be " \
"performed (status was %s)." % self.status
LOG.error(msg)
raise rd_exceptions.UnprocessableEntity(msg)
raise exception.UnprocessableEntity(msg)
def create_server_list_matcher(server_list):
@ -465,13 +490,13 @@ def create_server_list_matcher(server_list):
# The instance was not found in the list and
# this can happen if the instance is deleted from
# nova but still in reddwarf database
raise rd_exceptions.ComputeInstanceNotFound(
raise exception.ComputeInstanceNotFound(
instance_id=instance_id, server_id=server_id)
else:
# Should never happen, but never say never.
LOG.error(_("Server %s for instance %s was found twice!")
% (server_id, instance_id))
raise rd_exceptions.ReddwarfError(uuid=instance_id)
raise exception.ReddwarfError(uuid=instance_id)
return find_server
@ -481,6 +506,10 @@ class Instances(object):
@staticmethod
def load(context):
def load_simple_instance(context, db, status):
return SimpleInstance(context, db, status)
if context is None:
raise TypeError("Argument context not defined.")
client = create_nova_client(context)
@ -495,12 +524,20 @@ class Instances(object):
marker=context.marker)
next_marker = data_view.next_page_marker
ret = []
find_server = create_server_list_matcher(servers)
for db in db_infos:
LOG.debug("checking for db [id=%s, compute_instance_id=%s]" %
(db.id, db.compute_instance_id))
for db in data_view.collection:
ret = Instances._load_servers_status(load_simple_instance, context,
data_view.collection,
find_server)
return ret, next_marker
@staticmethod
def _load_servers_status(load_instance, context, db_items, find_server):
ret = []
for db in db_items:
server = None
try:
#TODO(tim.simpson): Delete when we get notifications working!
if InstanceTasks.BUILDING == db.task_status:
@ -509,99 +546,32 @@ class Instances(object):
try:
server = find_server(db.id, db.compute_instance_id)
db.server_status = server.status
except rd_exceptions.ComputeInstanceNotFound:
except exception.ComputeInstanceNotFound:
if InstanceTasks.DELETING == db.task_status:
#TODO(tim.simpson): This instance is actually
# deleted, but without notifications we never
# update our DB.
continue
db.server_status = "SHUTDOWN" # Fake it...
db.server_status = "SHUTDOWN" # Fake it...
#TODO(tim.simpson): End of hack.
#volumes = find_volumes(server.id)
status = InstanceServiceStatus.find_by(instance_id=db.id)
LOG.info(_("Server api_status(%s)") %
(status.status.api_status))
if not status.status: # This should never happen.
if not status.status: # This should never happen.
LOG.error(_("Server status could not be read for "
"instance id(%s)") % (db.id))
continue
except ModelNotFoundError:
except exception.ModelNotFoundError:
LOG.error(_("Server status could not be read for "
"instance id(%s)") % (db.id))
continue
ret.append(SimpleInstance(context, db, status))
return ret, next_marker
ret.append(load_instance(context, db, status))
return ret
class DatabaseModelBase(ModelBase):
_auto_generated_attrs = ['id']
@classmethod
def create(cls, **values):
values['id'] = utils.generate_uuid()
values['created'] = utils.utcnow()
instance = cls(**values).save()
if not instance.is_valid():
raise InvalidModelError(instance.errors)
return instance
def save(self):
if not self.is_valid():
raise InvalidModelError(self.errors)
self['updated'] = utils.utcnow()
LOG.debug(_("Saving %s: %s") %
(self.__class__.__name__, self.__dict__))
return db.db_api.save(self)
def delete(self):
self['updated'] = utils.utcnow()
LOG.debug(_("Deleting %s: %s") %
(self.__class__.__name__, self.__dict__))
return db.db_api.delete(self)
def __init__(self, **kwargs):
self.merge_attributes(kwargs)
if not self.is_valid():
raise InvalidModelError(self.errors)
def merge_attributes(self, values):
"""dict.update() behaviour."""
for k, v in values.iteritems():
self[k] = v
@classmethod
def find_by(cls, **conditions):
model = cls.get_by(**conditions)
if model is None:
raise ModelNotFoundError(_("%s Not Found") % cls.__name__)
return model
@classmethod
def get_by(cls, **kwargs):
return db.db_api.find_by(cls, **cls._process_conditions(kwargs))
@classmethod
def find_all(cls, **kwargs):
return db.db_query.find_all(cls, **cls._process_conditions(kwargs))
@classmethod
def _process_conditions(cls, raw_conditions):
"""Override in inheritors to format/modify any conditions."""
return raw_conditions
@classmethod
def find_by_pagination(cls, collection_type, collection_query,
paginated_url, **kwargs):
elements, next_marker = collection_query.paginated_collection(**kwargs)
return pagination.PaginatedDataView(collection_type,
elements,
paginated_url,
next_marker)
class DBInstance(DatabaseModelBase):
class DBInstance(dbmodels.DatabaseModelBase):
"""Defines the task being executed plus the start time."""
#TODO(tim.simpson): Add start time.
@ -632,13 +602,13 @@ class DBInstance(DatabaseModelBase):
task_status = property(get_task_status, set_task_status)
class ServiceImage(DatabaseModelBase):
class ServiceImage(dbmodels.DatabaseModelBase):
"""Defines the status of the service being run."""
_data_fields = ['service_name', 'image_id']
class InstanceServiceStatus(DatabaseModelBase):
class InstanceServiceStatus(dbmodels.DatabaseModelBase):
_data_fields = ['instance_id', 'status_id', 'status_description']
@ -672,19 +642,6 @@ def persisted_models():
}
class InvalidModelError(rd_exceptions.ReddwarfError):
message = _("The following values are invalid: %(errors)s")
def __init__(self, errors, message=None):
super(InvalidModelError, self).__init__(message, errors=errors)
class ModelNotFoundError(rd_exceptions.ReddwarfError):
message = _("Not Found")
class ServiceStatus(object):
"""Represents the status of the app and in some rare cases the agent.

View File

@ -40,7 +40,7 @@ class BaseController(wsgi.Controller):
exception.UnprocessableEntity,
],
webob.exc.HTTPBadRequest: [
models.InvalidModelError,
exception.InvalidModelError,
exception.BadRequest,
exception.CannotResizeToSameSize,
exception.BadValue
@ -48,7 +48,7 @@ class BaseController(wsgi.Controller):
webob.exc.HTTPNotFound: [
exception.NotFound,
exception.ComputeInstanceNotFound,
models.ModelNotFoundError,
exception.ModelNotFoundError,
],
webob.exc.HTTPConflict: [
],
@ -191,8 +191,8 @@ class InstanceController(BaseController):
LOG.info(_("id : '%s'\n\n") % id)
context = req.environ[wsgi.CONTEXT_KEY]
server = models.SimpleInstance.load(context=context, id=id)
# TODO(cp16net): need to set the return code correctly
server = models.load_instance_with_guest(models.DetailInstance,
context, id)
return wsgi.Result(views.InstanceDetailView(server, req=req,
add_addresses=self.add_addresses,
add_volumes=self.add_volumes).data(), 200)
@ -242,7 +242,7 @@ class InstanceController(BaseController):
try:
volume_size = int(body['instance']['volume']['size'])
except ValueError as e:
raise exception.BadValue(msg=e)
raise exception.BadValue(msg=e)
else:
volume_size = None
instance = models.Instance.create(context, name, flavor_id,

View File

@ -18,9 +18,8 @@
import logging
from reddwarf.common import config
from reddwarf.common import utils
from reddwarf.common import wsgi
from reddwarf.common.views import create_links
from reddwarf.instance import models
LOG = logging.getLogger(__name__)
@ -87,6 +86,8 @@ class InstanceDetailView(InstanceView):
self.add_addresses = add_addresses
self.add_volumes = add_volumes
def _to_gb(self, bytes):
return bytes / 1024.0 ** 3
def data(self):
result = super(InstanceDetailView, self).data()
@ -97,6 +98,11 @@ class InstanceDetailView(InstanceView):
ip = get_ip_address(self.instance.addresses)
if ip is not None and len(ip) > 0:
result['instance']['ip'] = ip
if self.add_volumes:
if isinstance(self.instance, models.DetailInstance) and \
self.instance.volume_used:
used = self._to_gb(self.instance.volume_used)
result['instance']['volume']['used'] = used
return result

View File

@ -50,6 +50,7 @@ class API(object):
from reddwarf.taskmanager.manager import TaskManager
instance = TaskManager()
method = getattr(instance, method_name)
def func():
try:
method(self.context, **kwargs)

View File

@ -79,5 +79,3 @@ class TaskManager(service.Manager):
instance_tasks = FreshInstanceTasks.load(context, instance_id)
instance_tasks.create_instance(flavor_id, flavor_ram, image_id,
databases, service_type, volume_size)

View File

@ -106,7 +106,6 @@ class FreshInstanceTasks(FreshInstance):
LOG.debug("block_device = %s" % block_device)
LOG.debug("volume = %s" % volumes)
device_path = config.Config.get('device_path', '/dev/vdb')
mount_point = config.Config.get('mount_point', '/var/lib/mysql')
LOG.debug(_("device_path = %s") % device_path)
@ -146,6 +145,7 @@ class FreshInstanceTasks(FreshInstance):
nova_client = create_nova_client(self.context)
if utils.bool_from_string(dns_support):
def get_server():
return nova_client.servers.get(self.db_info.compute_instance_id)

View File

@ -98,11 +98,13 @@ class FakeGuest(object):
mount_point=None):
from reddwarf.instance.models import InstanceServiceStatus
from reddwarf.instance.models import ServiceStatuses
from reddwarf.guestagent.models import AgentHeartBeat
def update_db():
status = InstanceServiceStatus.find_by(instance_id=self.id)
status.status = ServiceStatuses.RUNNING
status.save()
AgentHeartBeat.create(instance_id=self.id)
EventSimulator.add_event(2.0, update_db)
def restart(self):
@ -125,6 +127,10 @@ class FakeGuest(object):
status.status = ServiceStatuses.SHUTDOWN
status.save()
def get_volume_info(self):
"""Return used volume information in bytes."""
return {'used': 175756487}
def get_or_create(id):
if id not in DB:

View File

@ -289,8 +289,8 @@ class FakeVolume(object):
for attachment in self.attachments:
if attachment['server_id'] == server_id:
return # Do nothing
self.attachments.append({'server_id':server_id,
'device':self.device})
self.attachments.append({'server_id': server_id,
'device': self.device})
@property
def status(self):
@ -348,6 +348,7 @@ class FakeVolumes(object):
def resize(self, volume_id, new_size):
volume = self.get(volume_id)
def finish_resize():
volume._current_status = "in-use"
volume.size = new_size