Allowing a user to add a volume to the instance on create api call

added some helper code to make sure the mysql service is up

add a config value to turn on volume support

moving around the lines to be dependent on volume support

trying to stop volume support

fixes with the merge

imported and raised exceptions in guest correctly

fixed the config get values as boolean and not just the string

deleting the serivce status entry on delete of the instance so that the

guest can not update the instance to active again.

made the create and update times saved correctly for an instance

check if mysql is already installed in the guest

clean up instance model

using _(i8n)

adding the populate databases back in

pep8 compliance

updated apt get version method

moved the volumeHelper class to a new module

made the start/stop mysql methods public

created a method to create volume for instances and readability

created a new client for volume calls

added the volume and size to the create/list/show instance api calls

removing the mount_point and device_path from the MysqlApp class

removing code that deleted the instance status entry

adding volume service to the reddwarf.conf.test file

set volumes to not be disabled by default
This commit is contained in:
Craig Vyvial 2012-04-03 13:02:38 -05:00
parent 6527d65118
commit 777f492d1b
15 changed files with 477 additions and 99 deletions

View File

@ -52,6 +52,13 @@ nova_service_name = Compute Service
# Config option for showing the IP address that nova doles out
add_addresses = True
# Config options for enabling volume service
reddwarf_volume_support = False
nova_volume_service_type = volume
nova_volume_service_name = Volume Service
device_path = /dev/vdb
mount_point = /var/lib/mysql
# ============ notifer queue kombu connection options ========================
notifier_queue_hostname = localhost

View File

@ -52,6 +52,16 @@ nova_region_name = RegionOne
nova_service_type = compute
nova_service_name = Compute Service
# Config option for showing the IP address that nova doles out
add_addresses = True
# Config options for enabling volume service
reddwarf_volume_support = False
nova_volume_service_type = volume
nova_volume_service_name = Volume Service
device_path = /dev/vdb
mount_point = /var/lib/mysql
# ============ notifer queue kombu connection options ========================
notifier_queue_hostname = localhost

View File

@ -87,3 +87,8 @@ class UnprocessableEntity(ReddwarfError):
message = _("Unable to process the contained request")
class VolumeAttachmentsNotFound(NotFound):
message = _("Cannot find the volumes attached to compute "
"instance %(server_id)")

View File

@ -19,7 +19,6 @@ from reddwarf.common import config
from novaclient.v1_1.client import Client
CONFIG = config.Config
@ -40,6 +39,7 @@ def create_nova_client(context):
PROXY_AUTH_URL = CONFIG.get('reddwarf_auth_url',
'http://0.0.0.0:5000/v2.0')
REGION_NAME = CONFIG.get('nova_region_name', 'RegionOne')
SERVICE_TYPE = CONFIG.get('nova_service_type', 'compute')
SERVICE_NAME = CONFIG.get('nova_service_name', 'Compute Service')
@ -55,10 +55,38 @@ def create_nova_client(context):
return client
def create_nova_volume_client(context):
# Quite annoying but due to a paste config loading bug.
# TODO(hub-cap): talk to the openstack-common people about this
PROXY_ADMIN_USER = CONFIG.get('reddwarf_proxy_admin_user', 'admin')
PROXY_ADMIN_PASS = CONFIG.get('reddwarf_proxy_admin_pass',
'3de4922d8b6ac5a1aad9')
PROXY_ADMIN_TENANT_NAME = CONFIG.get('reddwarf_proxy_admin_tenant_name',
'admin')
PROXY_AUTH_URL = CONFIG.get('reddwarf_auth_url',
'http://0.0.0.0:5000/v2.0')
REGION_NAME = CONFIG.get('nova_region_name', 'RegionOne')
SERVICE_TYPE = CONFIG.get('nova_volume_service_type', 'volume')
SERVICE_NAME = CONFIG.get('nova_volume_service_name', 'Volume Service')
#TODO(cp16net) need to fix this proxy_tenant_id
client = Client(PROXY_ADMIN_USER, PROXY_ADMIN_PASS,
PROXY_ADMIN_TENANT_NAME, PROXY_AUTH_URL,
proxy_tenant_id=context.tenant,
proxy_token=context.auth_tok,
region_name=REGION_NAME,
service_type=SERVICE_TYPE,
service_name=SERVICE_NAME)
client.authenticate()
return client
if CONFIG.get("remote_implementation", "real") == "fake":
# Override the functions above with fakes.
from reddwarf.tests.fakes.nova import fake_create_nova_client
from reddwarf.tests.fakes.nova import fake_create_nova_volume_client
from reddwarf.tests.fakes.guestagent import fake_create_guest_client
def create_guest_client(context, id):
@ -66,3 +94,6 @@ if CONFIG.get("remote_implementation", "real") == "fake":
def create_nova_client(context):
return fake_create_nova_client(context)
def create_nova_volume_client(context):
return fake_create_nova_volume_client(context)

View File

@ -22,6 +22,7 @@ import logging
import re
import signal
import sys
import urlparse
import uuid
from eventlet import event
@ -146,6 +147,7 @@ class LoopingCallDone(Exception):
def __init__(self, retvalue=True):
""":param retvalue: Value that LoopingCall.wait() should return."""
super(LoopingCallDone, self).__init__()
self.retvalue = retvalue
@ -208,10 +210,12 @@ def get_id_from_href(href):
def execute_with_timeout(*args, **kwargs):
time = kwargs.get('timeout', 30)
def cb_timeout():
raise exception.ProcessExecutionError("Time out after waiting "
+ str(time) + " seconds when running proc: " + str(args)
+ str(kwargs))
msg = _("Time out after waiting"
" %(time)s seconds when running proc: %(args)s"
" %(kwargs)s") % locals()
raise exception.ProcessExecutionError(msg)
timeout = Timeout(time)
try:
@ -220,8 +224,9 @@ def execute_with_timeout(*args, **kwargs):
if t is not timeout:
raise
else:
raise exception.ProcessExecutionError("Time out after waiting "
+ str(time) + " seconds when running proc: " + str(args)
+ str(kwargs))
msg = _("Time out after waiting "
"%(time)s seconds when running proc: %(args)s"
" %(kwargs)s") % locals()
raise exception.ProcessExecutionError(msg)
finally:
timeout.cancel()

View File

@ -122,12 +122,14 @@ class API(object):
LOG.debug(_("Check diagnostics on Instance %s"), self.id)
return self._call("get_diagnostics")
def prepare(self, memory_mb, databases, users):
def prepare(self, memory_mb, databases, users,
device_path='/dev/vdb', mount_point='/mnt/volume'):
"""Make an asynchronous call to prepare the guest
as a database container"""
LOG.debug(_("Sending the call to prepare the Guest"))
self._cast_with_consumer("prepare", databases=databases,
memory_mb=memory_mb, users=users)
memory_mb=memory_mb, users=users, device_path=device_path,
mount_point=mount_point)
def restart(self):
"""Restart the MySQL server."""
@ -147,5 +149,5 @@ class API(object):
def upgrade(self):
"""Make an asynchronous call to self upgrade the guest agent"""
LOG.debug(_("Sending an upgrade call to nova-guest %s"), topic)
LOG.debug(_("Sending an upgrade call to nova-guest"))
self._cast_with_consumer("upgrade")

View File

@ -28,6 +28,7 @@ handles RPC calls relating to Platform specific operations.
import logging
import os
import pexpect
import re
import sys
import time
@ -40,12 +41,15 @@ from sqlalchemy import interfaces
from sqlalchemy.sql.expression import text
from reddwarf import db
from reddwarf.common.exception import GuestError
from reddwarf.common.exception import ProcessExecutionError
from reddwarf.common import config
from reddwarf.common import utils
from reddwarf.guestagent.db import models
from reddwarf.guestagent.volume import VolumeHelper
from reddwarf.instance import models as rd_models
ADMIN_USER_NAME = "os_admin"
LOG = logging.getLogger(__name__)
FLUSH = text("""FLUSH PRIVILEGES;""")
@ -61,6 +65,8 @@ TMP_MYCNF = "/tmp/my.cnf.tmp"
DBAAS_MYCNF = "/etc/dbaas/my.cnf/my.cnf.%dM"
MYSQL_BASE_DIR = "/var/lib/mysql"
CONFIG = config.Config
def generate_random_password():
return str(uuid.uuid4())
@ -140,7 +146,6 @@ class MySqlAppStatus(object):
self.status = self._load_status()
self.restart_mode = False
def begin_mysql_install(self):
"""Called right before MySQL is prepared."""
self.set_status(rd_models.ServiceStatuses.BUILDING)
@ -176,8 +181,8 @@ class MySqlAppStatus(object):
except ProcessExecutionError as e:
LOG.error("Process execution ")
try:
out, err = utils.execute_with_timeout("/bin/ps", "-C", "mysqld",
"h")
out, err = utils.execute_with_timeout("/bin/ps", "-C",
"mysqld", "h")
pid = out.split()[0]
# TODO(rnirmal): Need to create new statuses for instances
# where the mysql service is up, but unresponsive
@ -228,7 +233,6 @@ class MySqlAppStatus(object):
db_status.save()
self.status = status
def update(self):
"""Find and report status of MySQL on this machine.
@ -488,13 +492,33 @@ class DBaaSAgent(object):
def is_root_enabled(self):
return MySqlAdmin().is_root_enabled()
def prepare(self, databases, memory_mb, users):
def prepare(self, databases, memory_mb, users, device_path=None,
mount_point=None):
"""Makes ready DBAAS on a Guest container."""
from reddwarf.guestagent.pkg import PkgAgent
if not isinstance(self, PkgAgent):
raise TypeError("This must also be an instance of Pkg agent.")
pkg = self # Python cast.
self.status.begin_mysql_install()
# status end_mysql_install set with install_and_secure()
app = MySqlApp(self.status)
restart_mysql = False
if not device_path is None:
VolumeHelper.format(device_path)
if app.is_installed(pkg):
#stop and do not update database
app.stop_mysql()
restart_mysql = True
#rsync exiting data
VolumeHelper.migrate_data(device_path, MYSQL_BASE_DIR)
#mount the volume
VolumeHelper.mount(device_path, mount_point)
#TODO(cp16net) need to update the fstab here so that on a
# restart the volume will be mounted automatically again
LOG.debug(_("Mounted the volume."))
#check mysql was installed and stopped
if restart_mysql:
app.start_mysql()
app.install_and_secure(pkg, memory_mb)
LOG.info("Creating initial databases and users following successful "
"prepare.")
@ -536,11 +560,12 @@ class MySqlApp(object):
"""Prepares DBaaS on a Guest container."""
TIME_OUT = 1000
MYSQL_PACKAGE_VERSION = "mysql-server-5.1"
def __init__(self, status):
""" By default login with root no password for initial setup. """
self.state_change_wait_time = config.Config.get(
'state_change_wait_time', 2 * 60)
self.state_change_wait_time = int(config.Config.get(
'state_change_wait_time', 2 * 60))
self.status = status
def _create_admin_user(self, client, password):
@ -586,21 +611,21 @@ class MySqlApp(object):
self._remove_remote_root_access(client)
self._create_admin_user(client, admin_password)
self._internal_stop_mysql()
self.stop_mysql()
self._write_mycnf(pkg, memory_mb, admin_password)
self._start_mysql()
self.start_mysql()
self.status.end_install_or_restart()
LOG.info(_("Dbaas preparation complete."))
LOG.info(_("Dbaas install_and_secure complete."))
def _install_mysql(self, pkg):
"""Install mysql server. The current version is 5.1"""
LOG.debug(_("Installing mysql server"))
pkg.pkg_install("mysql-server-5.1", self.TIME_OUT)
pkg.pkg_install(self.MYSQL_PACKAGE_VERSION, self.TIME_OUT)
LOG.debug(_("Finished installing mysql server"))
#TODO(rnirmal): Add checks to make sure the package got installed
def _internal_stop_mysql(self, update_db=False):
def stop_mysql(self, update_db=False):
LOG.info(_("Stopping mysql..."))
utils.execute_with_timeout("sudo", "/etc/init.d/mysql", "stop")
if not self.status.wait_for_real_status_to_change_to(
@ -623,8 +648,8 @@ class MySqlApp(object):
def restart(self):
try:
self.status.begin_mysql_restart()
self._internal_stop_mysql()
self._start_mysql()
self.stop_mysql()
self.start_mysql()
finally:
self.status.end_install_or_restart()
@ -702,8 +727,7 @@ class MySqlApp(object):
utils.execute_with_timeout("sudo", "ln", "-s", FINAL_MYCNF, ORIG_MYCNF)
self.wipe_ib_logfiles()
def _start_mysql(self, update_db=False):
def start_mysql(self, update_db=False):
LOG.info(_("Starting mysql..."))
# This is the site of all the trouble in the restart tests.
# Essentially what happens is thaty mysql start fails, but does not
@ -736,8 +760,10 @@ class MySqlApp(object):
"MySQL state == %s!") % self.status)
raise RuntimeError("MySQL not stopped.")
LOG.info(_("Initiating config."))
self._write_mycnf(pkg, update_memory_mb, None)
self._start_mysql(True)
self._write_mycnf(pkg, updated_memory_mb, None)
self.start_mysql(True)
def stop_mysql(self):
self._internal_stop_mysql(True)
def is_installed(self, pkg):
#(cp16net) could raise an exception, does it need to be handled here?
version = pkg.pkg_version(self.MYSQL_PACKAGE_VERSION)
return not version is None

View File

@ -44,8 +44,9 @@ class GuestManager(service.Manager):
"""Manages the tasks within a Guest VM."""
def __init__(self, guest_drivers=None, *args, **kwargs):
service_type = CONFIG.get('service_type')
try:
service_impl = GUEST_SERVICES[CONFIG.get('service_type')]
service_impl = GUEST_SERVICES[service_type]
except KeyError as e:
LOG.error(_("Could not create guest, no impl for key - %s") %
service_type)

View File

@ -18,8 +18,10 @@
"""
Manages packages on the Guest VM.
"""
import commands
import logging
import pexpect
import re
from reddwarf.common import exception
from reddwarf.common.exception import ProcessExecutionError
@ -27,7 +29,6 @@ from reddwarf.common import utils
LOG = logging.getLogger(__name__)
# FLAGS = flags.FLAGS
class PkgAdminLockError(exception.ReddwarfError):
@ -157,7 +158,8 @@ class PkgAgent(object):
def pkg_install(self, package_name, time_out):
"""Installs a package."""
try:
utils.execute("apt-get", "update", run_as_root=True)
utils.execute("apt-get", "update", run_as_root=True,
root_helper="sudo")
except ProcessExecutionError as e:
LOG.error(_("Error updating the apt sources"))
@ -171,44 +173,45 @@ class PkgAgent(object):
% package_name)
def pkg_version(self, package_name):
"""Returns the installed version of the given package.
It is sometimes impossible to know if a package is completely
unavailable before you attempt to install. Some packages may return
no information from the dpkg command but then install fine with apt-get
install.
"""
child = pexpect.spawn("dpkg -l %s" % package_name)
i = child.expect([".*No packages found matching*", "\+\+\+\-"])
if i == 0:
#raise PkgNotFoundError()
cmd_list = ["dpkg", "-l", package_name]
p = commands.getstatusoutput(' '.join(cmd_list))
# check the command status code
if not p[0] == 0:
return None
# Need to capture the version string
child.expect("\n")
i = child.expect(["<none>", ".*"])
if i == 0:
return None
line = child.match.group()
parts = line.split()
# Should be something like:
# ['un', 'cowsay', '<none>', '(no', 'description', 'available)']
try:
wait_and_close_proc(child)
except pexpect.TIMEOUT:
kill_proc(child)
raise PkgTimeout("Remove process took too long.")
if len(parts) <= 2:
raise Error("Unexpected output.")
if parts[1] != package_name:
raise Error("Unexpected output:[1] == " + str(parts[1]))
if parts[0] == 'un' or parts[2] == '<none>':
return None
return parts[2]
# check the command output
std_out = p[1]
patterns = ['.*No packages found matching.*',
"\w\w\s+(\S+)\s+(\S+)\s+(.*)$"]
for line in std_out.split("\n"):
for p in patterns:
regex = re.compile(p)
matches = regex.match(line)
if matches:
line = matches.group()
parts = line.split()
if not parts:
msg = _("returned nothing")
LOG.error(msg)
raise exception.GuestError(msg)
if len(parts) <= 2:
msg = _("Unexpected output.")
LOG.error(msg)
raise exception.GuestError(msg)
if parts[1] != package_name:
msg = _("Unexpected output:[1] = %s" % str(parts[1]))
LOG.error(msg)
raise exception.GuestError(msg)
if parts[0] == 'un' or parts[2] == '<none>':
return None
return parts[2]
msg = _("version() saw unexpected output from dpkg!")
LOG.error(msg)
raise exception.GuestError(msg)
def pkg_remove(self, package_name, time_out):
"""Removes a package."""
if self.pkg_version(package_name) == None:
if self.pkg_version(package_name) is None:
return
result = self._remove(package_name, time_out)

View File

@ -0,0 +1,129 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2011 OpenStack, LLC.
# All Rights Reserved.
#
# 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
import os
import pexpect
from reddwarf.common import config
from reddwarf.common import utils
from reddwarf.common.exception import GuestError
from reddwarf.common.exception import ProcessExecutionError
TMP_MOUNT_POINT = "/mnt/volume"
LOG = logging.getLogger(__name__)
CONFIG = config.Config
class VolumeHelper(object):
@staticmethod
def _has_volume_device(device_path):
return not device_path is None
@staticmethod
def migrate_data(device_path, mysql_base):
""" Synchronize the data from the mysql directory to the new volume """
utils.execute("sudo", "mkdir", "-p", TMP_MOUNT_POINT)
VolumeHelper.mount(device_path, TMP_MOUNT_POINT)
if not mysql_base[-1] == '/':
mysql_base = "%s/" % mysql_base
utils.execute("sudo", "rsync", "--safe-links", "--perms",
"--recursive", "--owner", "--group", "--xattrs",
"--sparse", mysql_base, TMP_MOUNT_POINT)
VolumeHelper.unmount(device_path)
@staticmethod
def _check_device_exists(device_path):
"""Check that the device path exists.
Verify that the device path has actually been created and can report
it's size, only then can it be available for formatting, retry
num_tries to account for the time lag.
"""
try:
num_tries = CONFIG.get('num_tries', 3)
utils.execute('sudo', 'blockdev', '--getsize64', device_path,
attempts=num_tries)
except ProcessExecutionError:
raise GuestError("InvalidDevicePath(path=%s)" % device_path)
@staticmethod
def _check_format(device_path):
"""Checks that an unmounted volume is formatted."""
child = pexpect.spawn("sudo dumpe2fs %s" % device_path)
try:
i = child.expect(['has_journal', 'Wrong magic number'])
if i == 0:
return
volume_fstype = CONFIG.get('volume_fstype', 'ext3')
raise IOError('Device path at %s did not seem to be %s.' %
(device_path, volume_fstype))
except pexpect.EOF:
raise IOError("Volume was not formatted.")
child.expect(pexpect.EOF)
@staticmethod
def _format(device_path):
"""Calls mkfs to format the device at device_path."""
volume_fstype = CONFIG.get('volume_fstype', 'ext3')
format_options = CONFIG.get('format_options', '-m 5')
cmd = "sudo mkfs -t %s %s %s" % (volume_fstype,
format_options, device_path)
volume_format_timeout = CONFIG.get('volume_format_timeout', 120)
child = pexpect.spawn(cmd, timeout=volume_format_timeout)
# child.expect("(y,n)")
# child.sendline('y')
child.expect(pexpect.EOF)
@staticmethod
def format(device_path):
"""Formats the device at device_path and checks the filesystem."""
VolumeHelper._check_device_exists(device_path)
VolumeHelper._format(device_path)
VolumeHelper._check_format(device_path)
@staticmethod
def mount(device_path, mount_point):
if not os.path.exists(mount_point):
os.makedirs(mount_point)
volume_fstype = CONFIG.get('volume_fstype', 'ext3')
mount_options = CONFIG.get('mount_options', 'noatime')
cmd = "sudo mount -t %s -o %s %s %s" % (volume_fstype,
mount_options,
device_path, mount_point)
child = pexpect.spawn(cmd)
child.expect(pexpect.EOF)
@staticmethod
def unmount(mount_point):
if os.path.exists(mount_point):
cmd = "sudo umount %s" % mount_point
child = pexpect.spawn(cmd)
child.expect(pexpect.EOF)
@staticmethod
def resize_fs(device_path):
"""Resize the filesystem on the specified device"""
VolumeHelper._check_device_exists(device_path)
try:
utils.execute("sudo", "resize2fs", device_path)
except ProcessExecutionError as err:
LOG.error(err)
raise GuestError("Error resizing the filesystem: %s"
% device_path)

View File

@ -23,19 +23,20 @@ import netaddr
from reddwarf import db
from reddwarf.common import config
#from reddwarf.guestagent import api as guest_api
from reddwarf.common import exception as rd_exceptions
from reddwarf.common import utils
from reddwarf.instance.tasks import InstanceTask
from reddwarf.instance.tasks import InstanceTasks
from novaclient.v1_1.client import Client
from reddwarf.common.models import ModelBase
from novaclient import exceptions as nova_exceptions
from reddwarf.common.models import NovaRemoteModelBase
from reddwarf.common.remote import create_nova_client
from reddwarf.common.remote import create_nova_volume_client
from reddwarf.common.remote import create_guest_client
from eventlet import greenthread
CONFIG = config.Config
LOG = logging.getLogger(__name__)
@ -45,12 +46,40 @@ def load_server(context, instance_id, server_id):
client = create_nova_client(context)
try:
server = client.servers.get(server_id)
volumes = load_volumes(context, server_id, client=client)
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)
except nova_exceptions.ClientException, e:
raise rd_exceptions.ReddwarfError(str(e))
return server
return server, volumes
def load_volumes(context, server_id, client=None):
volume_support = config.Config.get("reddwarf_volume_support", 'False')
if utils.bool_from_string(volume_support):
if client is None:
client = create_nova_client(context)
volume_client = create_nova_volume_client(context)
try:
volumes = []
if utils.bool_from_string(volume_support):
volumes_info = client.volumes.get_server_volumes(server_id)
volume_ids = [attachments.volumeId for attachments in
volumes_info]
for volume_id in volume_ids:
volume_info = volume_client.volumes.get(volume_id)
volume = {'id': volume_info.id,
'size': volume_info.size}
volumes.append(volume)
except nova_exceptions.NotFound, e:
LOG.debug("Could not find nova server_id(%s)" % server_id)
raise rd_exceptions.VolumeAttachmentsNotFound(server_id=server_id)
except nova_exceptions.ClientException, e:
raise rd_exceptions.ReddwarfError(str(e))
return volumes
return None
# This probably should not happen here. Seems like it should
@ -71,7 +100,7 @@ def populate_databases(dbs):
databases.append(mydb.serialize())
return databases
except ValueError as ve:
raise exception.BadRequest(ve.message)
raise rd_exceptions.BadRequest(ve.message)
class InstanceStatus(object):
@ -95,13 +124,14 @@ VALID_ACTION_STATUSES = ["ACTIVE"]
class Instance(object):
_data_fields = ['name', 'status', 'id', 'created', 'updated',
'flavor', 'links', 'addresses']
'flavor', 'links', 'addresses', 'volume']
def __init__(self, context, db_info, server, service_status):
def __init__(self, context, db_info, server, service_status, volumes):
self.context = context
self.db_info = db_info
self.server = server
self.service_status = service_status
self.volumes = volumes
@staticmethod
def load(context, id):
@ -113,11 +143,12 @@ class Instance(object):
db_info = DBInstance.find_by(id=id)
except rd_exceptions.NotFound:
raise rd_exceptions.NotFound(uuid=id)
server = load_server(context, db_info.id, db_info.compute_instance_id)
server, volumes = load_server(context, db_info.id,
db_info.compute_instance_id)
task_status = db_info.task_status
service_status = InstanceServiceStatus.find_by(instance_id=id)
LOG.info("service status=%s" % service_status)
return Instance(context, db_info, server, service_status)
return Instance(context, db_info, server, service_status, volumes)
def delete(self, force=False):
if not force and self.server.status in SERVER_INVALID_ACTION_STATUSES:
@ -140,26 +171,94 @@ class Instance(object):
except nova_exceptions.ClientException, e:
raise rd_exceptions.ReddwarfError()
@classmethod
def _create_volume(cls, context, db_info, volume_size):
volume_support = config.Config.get("reddwarf_volume_support", 'False')
LOG.debug(_("Volume support = %s") % volume_support)
if utils.bool_from_string(volume_support):
LOG.debug(_("Starting to create the volume for the instance"))
volume_client = create_nova_volume_client(context)
volume_desc = ("mysql volume for %s" % context.tenant)
volume_ref = volume_client.volumes.create(
volume_size,
display_name="mysql-%s" % db_info.id,
display_description=volume_desc)
#TODO(cp16net) this is bad to wait here for the volume create
# before returning but this was a quick way to get it working
# for now we need this to go into the task manager
v_ref = volume_client.volumes.get(volume_ref.id)
while not v_ref.status in ['available', 'error']:
LOG.debug(_("waiting for volume [volume.status=%s]") %
v_ref.status)
greenthread.sleep(1)
v_ref = volume_client.volumes.get(volume_ref.id)
if v_ref.status in ['error']:
raise rd_exceptions.ReddwarfError(
_("Could not create volume"))
LOG.debug(_("Created volume %s") % v_ref)
# The mapping is in the format:
# <id>:[<type>]:[<size(GB)>]:[<delete_on_terminate>]
# setting the delete_on_terminate instance to true=1
mapping = "%s:%s:%s:%s" % (v_ref.id, '', v_ref.size, 1)
# TODO(rnirmal) This mapping device needs to be configurable.
# and we may have to do a little more trickery here.
# We don't know what's the next device available on the
# guest. Also in cases for ovz where this is mounted on
# the host, that's not going to work for us.
block_device = {'vdb': mapping}
volume = [{'id': v_ref.id,
'size': v_ref.size}]
LOG.debug("block_device = %s" % block_device)
LOG.debug("volume = %s" % volume)
device_path = CONFIG.get('device_path')
mount_point = CONFIG.get('mount_point')
LOG.debug(_("device_path = %s") % device_path)
LOG.debug(_("mount_point = %s") % mount_point)
else:
LOG.debug(_("Skipping setting up the volume"))
block_device = None
device_path = None
mount_point = None
volume = None
#end volume_support
volume_info = {'block_device': block_device,
'device_path': device_path,
'mount_point': mount_point}
return volume, volume_info
@classmethod
def create(cls, context, name, flavor_ref, image_id,
databases, service_type):
databases, service_type, volume_size):
db_info = DBInstance.create(name=name,
task_status=InstanceTasks.NONE)
LOG.debug(_("Created new Reddwarf instance %s...") % db_info.id)
volume, volume_info = cls._create_volume(context,
db_info,
volume_size)
client = create_nova_client(context)
files = {"/etc/guest_info": "guest_id=%s\nservice_type=%s\n" %
(db_info.id, service_type)}
server = client.servers.create(name, image_id, flavor_ref,
files=files)
files=files,
block_device_mapping=volume_info['block_device'])
LOG.debug(_("Created new compute instance %s.") % server.id)
db_info.compute_instance_id = server.id
db_info.save()
service_status = InstanceServiceStatus.create(instance_id=db_info.id,
status=ServiceStatuses.NEW)
# Now wait for the response from the create to do additional work
guest = create_guest_client(context, db_info.id)
guest.prepare(databases=[], memory_mb=512, users=[])
return Instance(context, db_info, server, service_status)
# populate the databases
model_schemas = populate_databases(databases)
guest.prepare(512, model_schemas, users=[],
device_path=volume_info['device_path'],
mount_point=volume_info['mount_point'])
return Instance(context, db_info, server, service_status, volume)
def get_guest(self):
return create_guest_client(self.context, self.db_info.id)
@ -201,8 +300,8 @@ class Instance(object):
if self.server.status in ["ACTIVE", "SHUTDOWN"]:
return InstanceStatus.SHUTDOWN
else:
LOG.error(_("While shutting down instance %s: server had status "
" %s.") % (self.id, self.server.status))
LOG.error(_("While shutting down instance (%s): server had "
" status (%s).") % (self.id, self.server.status))
return InstanceStatus.ERROR
# For everything else we can look at the service status mapping.
return self.service_status.status.api_status
@ -311,7 +410,6 @@ class Instance(object):
raise rd_exceptions.UnprocessableEntity(msg)
def create_server_list_matcher(server_list):
# Returns a method which finds a server from the given list.
def find_server(instance_id, server_id):
@ -319,6 +417,9 @@ def create_server_list_matcher(server_list):
if len(matches) == 1:
return matches[0]
elif len(matches) < 1:
# 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(
instance_id=instance_id, server_id=server_id)
else:
@ -341,7 +442,8 @@ class Instances(object):
ret = []
find_server = create_server_list_matcher(servers)
for db in db_infos:
status = InstanceServiceStatus.find_by(instance_id=db.id)
LOG.debug("checking for db [id=%s, compute_instance_id=%s]" %
(db.id, db.compute_instance_id))
try:
# TODO(hub-cap): Figure out if this is actually correct.
# We are not sure if we should be doing some validation.
@ -350,11 +452,28 @@ class Instances(object):
# nova db has compared to what we have. We should have
# a way to handle this.
server = find_server(db.id, db.compute_instance_id)
volumes = load_volumes(context, db.compute_instance_id)
status = InstanceServiceStatus.find_by(instance_id=db.id)
LOG.info(_("Server api_status(%s)") %
(status.status.api_status))
if not status.status:
LOG.info(_("Server status could not be read for "
"instance id(%s)") % (db.compute_instance_id))
continue
if status.status.api_status in ['SHUTDOWN']:
LOG.info(_("Server was shutdown id(%s)") %
(db.compute_instance_id))
continue
except rd_exceptions.ComputeInstanceNotFound:
LOG.info(_("Could not find server %s") %
db.compute_instance_id)
continue
ret.append(Instance(context, db, server, status))
except ModelNotFoundError:
LOG.info(_("Status entry not found either failed to start "
"or instance was deleted"))
continue
ret.append(Instance(context, db, server, status, volumes))
return ret
@ -364,6 +483,7 @@ class DatabaseModelBase(ModelBase):
@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)
@ -372,10 +492,17 @@ class DatabaseModelBase(ModelBase):
def save(self):
if not self.is_valid():
raise InvalidModelError(self.errors)
self['updated_at'] = utils.utcnow()
LOG.debug(_("Saving %s: %s") % (self.__class__.__name__, self.__dict__))
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():

View File

@ -56,7 +56,10 @@ class BaseController(wsgi.Controller):
}
def __init__(self):
self.add_addresses = config.Config.get('add_addresses', False)
self.add_addresses = utils.bool_from_string(
config.Config.get('add_addresses', 'False'))
self.add_volumes = utils.bool_from_string(
config.Config.get('reddwarf_volume_support', 'False'))
pass
def _extract_required_params(self, params, model_name):
@ -166,7 +169,7 @@ class InstanceController(BaseController):
def detail(self, req, tenant_id):
"""Return all instances."""
LOG.info(_("req : '%s'\n\n") % req)
LOG.info(_("Detailing a database instance for tenant '%s'") % tenant_id)
LOG.info(_("Detailing database instance for tenant '%s'") % tenant_id)
#TODO(cp16net) return a detailed list instead of index
return self.index(req, tenant_id, detailed=True)
@ -180,7 +183,8 @@ class InstanceController(BaseController):
view_cls = views.InstancesDetailView if detailed \
else views.InstancesView
return wsgi.Result(view_cls(servers,
add_addresses=self.add_addresses).data(), 200)
add_addresses=self.add_addresses,
add_volumes=self.add_volumes).data(), 200)
def show(self, req, tenant_id, id):
"""Return a single instance."""
@ -201,7 +205,8 @@ class InstanceController(BaseController):
# Adding the root history, if it exists.
history = models.RootHistory.load(context=context, instance_id=id)
return wsgi.Result(views.InstanceDetailView(server, roothistory=history,
add_addresses=self.add_addresses).data(), 200)
add_addresses=self.add_addresses,
add_volumes=self.add_volumes).data(), 200)
def delete(self, req, tenant_id, id):
"""Delete a single instance."""
@ -252,10 +257,13 @@ class InstanceController(BaseController):
databases = body['instance'].get('databases')
if databases is None:
databases = []
volume_size = body['instance']['volume']['size']
instance = models.Instance.create(context, name, flavor_ref,
image_id, databases, service_type)
image_id, databases,
service_type, volume_size)
return wsgi.Result(views.InstanceDetailView(instance).data(), 200)
return wsgi.Result(views.InstanceDetailView(instance,
add_volumes=self.add_volumes).data(), 200)
@staticmethod
def _validate_body_not_empty(body):
@ -295,12 +303,12 @@ class InstanceController(BaseController):
body['instance']
body['instance']['flavorRef']
# TODO(cp16net) add in volume to the mix
# volume_size = body['instance']['volume']['size']
volume_size = body['instance']['volume']['size']
except KeyError as e:
LOG.error(_("Create Instance Required field(s) - %s") % e)
raise rd_exceptions.ReddwarfError("Required element/key - %s "
"was not specified" % e)
# Instance._validate_volume_size(volume_size)
InstanceController._validate_volume_size(volume_size)
@staticmethod
def _validate_resize_instance(body):

View File

@ -15,6 +15,9 @@
# License for the specific language governing permissions and limitations
# under the License.
import logging
LOG = logging.getLogger(__name__)
def get_ip_address(addresses):
if addresses is not None and \
@ -23,14 +26,22 @@ def get_ip_address(addresses):
return [addr.get('addr') for addr in addresses['private']]
def get_volumes(volumes):
LOG.debug("volumes - %s" % volumes)
if volumes is not None and len(volumes) > 0:
return {'size': volumes[0].get('size')}
class InstanceView(object):
def __init__(self, instance, add_addresses=False):
def __init__(self, instance, add_addresses=False, add_volumes=False):
self.instance = instance
self.add_addresses = add_addresses
self.add_volumes = add_volumes
def data(self):
ip = get_ip_address(self.instance.addresses)
volumes = get_volumes(self.instance.volumes)
instance_dict = {
"id": self.instance.id,
"name": self.instance.name,
@ -39,6 +50,9 @@ class InstanceView(object):
}
if self.add_addresses and ip is not None and len(ip) > 0:
instance_dict['ip'] = ip
if self.add_volumes and volumes is not None:
instance_dict['volume'] = volumes
LOG.debug(instance_dict)
return {"instance": instance_dict}
@ -61,9 +75,10 @@ class InstanceDetailView(InstanceView):
class InstancesView(object):
def __init__(self, instances, add_addresses=False):
def __init__(self, instances, add_addresses=False, add_volumes=False):
self.instances = instances
self.add_addresses = add_addresses
self.add_volumes = add_volumes
def data(self):
data = []
@ -81,4 +96,5 @@ class InstancesDetailView(InstancesView):
def data_for_instance(self, instance):
return InstanceDetailView(instance,
self.add_addresses).data()['instance']
self.add_addresses,
self.add_volumes).data()['instance']

View File

@ -69,9 +69,11 @@ class FakeGuest(object):
def list_users(self):
return [self.users[name] for name in self.users]
def prepare(self, memory_mb, databases, users):
def prepare(self, databases, memory_mb, users, device_path=None,
mount_point=None):
from reddwarf.instance.models import InstanceServiceStatus
from reddwarf.instance.models import ServiceStatuses
def update_db():
status = InstanceServiceStatus.find_by(instance_id=self.id)
status.status = ServiceStatuses.RUNNING
@ -87,6 +89,7 @@ class FakeGuest(object):
def start_mysql_with_conf_changes(self, updated_memory_size):
from reddwarf.instance.models import InstanceServiceStatus
from reddwarf.instance.models import ServiceStatuses
def update_db():
status = InstanceServiceStatus.find_by(instance_id=self.id)
status.status = ServiceStatuses.RUNNING
@ -96,6 +99,7 @@ class FakeGuest(object):
def stop_mysql(self):
from reddwarf.instance.models import InstanceServiceStatus
from reddwarf.instance.models import ServiceStatuses
def update_db():
status = InstanceServiceStatus.find_by(instance_id=self.id)
status.status = ServiceStatuses.SHUTDOWN

View File

@ -96,7 +96,7 @@ class FakeServer(object):
@property
def addresses(self):
return {"private":[{"addr":"123.123.123.123"}]}
return {"private": [{"addr":"123.123.123.123"}]}
def delete(self):
self.schedule_status = []
@ -194,3 +194,7 @@ class FakeClient(object):
def fake_create_nova_client(context):
return FakeClient(context)
def fake_create_nova_volume_client(context):
return FakeClient(context)