313 lines
14 KiB
Python
313 lines
14 KiB
Python
# 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 novaclient import exceptions as nova_exceptions
|
|
from reddwarf.common import config
|
|
from reddwarf.common import remote
|
|
from reddwarf.common import utils
|
|
from reddwarf.common.exception import GuestError
|
|
from reddwarf.common.exception import PollTimeOut
|
|
from reddwarf.common.exception import VolumeCreationFailure
|
|
from reddwarf.common.exception import NotFound
|
|
from reddwarf.common.exception import ReddwarfError
|
|
from reddwarf.common.remote import create_dns_client
|
|
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 reddwarf.common.utils import poll_until
|
|
from reddwarf.extensions.mysql.common import populate_databases
|
|
from reddwarf.extensions.mysql.common import populate_users
|
|
from reddwarf.instance import models as inst_models
|
|
from reddwarf.instance.models import DBInstance
|
|
from reddwarf.instance.models import BuiltInstance
|
|
from reddwarf.instance.models import FreshInstance
|
|
from reddwarf.instance.models import InstanceStatus
|
|
from reddwarf.instance.models import InstanceServiceStatus
|
|
from reddwarf.instance.models import ServiceStatuses
|
|
from reddwarf.instance.views import get_ip_address
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class FreshInstanceTasks(FreshInstance):
|
|
|
|
def create_instance(self, flavor_id, flavor_ram, image_id,
|
|
databases, users, service_type, volume_size):
|
|
try:
|
|
volume_info = self._create_volume(volume_size)
|
|
block_device_mapping = volume_info['block_device']
|
|
server = self._create_server(flavor_id, image_id, service_type,
|
|
block_device_mapping)
|
|
server_id = server.id
|
|
# Save server ID.
|
|
self.update_db(compute_instance_id=server_id)
|
|
self._create_dns_entry()
|
|
self._guest_prepare(server, flavor_ram, volume_info,
|
|
databases, users)
|
|
finally:
|
|
self.update_db(task_status=inst_models.InstanceTasks.NONE)
|
|
|
|
def _create_volume(self, volume_size):
|
|
LOG.info("Entering create_volume")
|
|
LOG.debug(_("Starting to create the volume for the instance"))
|
|
|
|
volume_support = config.Config.get("reddwarf_volume_support", 'False')
|
|
LOG.debug(_("reddwarf volume support = %s") % volume_support)
|
|
if volume_size is None or \
|
|
utils.bool_from_string(volume_support) is False:
|
|
volume_info = {'block_device': None,
|
|
'device_path': None,
|
|
'mount_point': None,
|
|
'volumes': None}
|
|
return volume_info
|
|
|
|
volume_client = create_nova_volume_client(self.context)
|
|
volume_desc = ("mysql volume for %s" % self.id)
|
|
volume_ref = volume_client.volumes.create(
|
|
volume_size,
|
|
display_name="mysql-%s" % self.id,
|
|
display_description=volume_desc)
|
|
|
|
# Record the volume ID in case something goes wrong.
|
|
self.update_db(volume_id=volume_ref.id)
|
|
|
|
utils.poll_until(
|
|
lambda: volume_client.volumes.get(volume_ref.id),
|
|
lambda v_ref: v_ref.status in ['available', 'error'],
|
|
sleep_time=2,
|
|
time_out=2 * 60)
|
|
|
|
v_ref = volume_client.volumes.get(volume_ref.id)
|
|
if v_ref.status in ['error']:
|
|
raise VolumeCreationFailure()
|
|
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)
|
|
bdm = config.Config.get('block_device_mapping', 'vdb')
|
|
block_device = {bdm: mapping}
|
|
volumes = [{'id': v_ref.id,
|
|
'size': v_ref.size}]
|
|
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)
|
|
LOG.debug(_("mount_point = %s") % mount_point)
|
|
|
|
volume_info = {'block_device': block_device,
|
|
'device_path': device_path,
|
|
'mount_point': mount_point,
|
|
'volumes': volumes}
|
|
return volume_info
|
|
|
|
def _create_server(self, flavor_id, image_id,
|
|
service_type, block_device_mapping):
|
|
nova_client = create_nova_client(self.context)
|
|
files = {"/etc/guest_info": "guest_id=%s\nservice_type=%s\n" %
|
|
(self.id, service_type)}
|
|
server = nova_client.servers.create(self.name, image_id, flavor_id,
|
|
files=files, block_device_mapping=block_device_mapping)
|
|
LOG.debug(_("Created new compute instance %s.") % server.id)
|
|
return server
|
|
|
|
def _guest_prepare(self, server, flavor_ram, volume_info,
|
|
databases, users):
|
|
LOG.info("Entering guest_prepare.")
|
|
# Now wait for the response from the create to do additional work
|
|
self.guest.prepare(flavor_ram, databases, users,
|
|
device_path=volume_info['device_path'],
|
|
mount_point=volume_info['mount_point'])
|
|
|
|
def _create_dns_entry(self):
|
|
LOG.debug("%s: Creating dns entry for instance: %s"
|
|
% (greenthread.getcurrent(), self.id))
|
|
dns_client = create_dns_client(self.context)
|
|
dns_support = config.Config.get("reddwarf_dns_support", 'False')
|
|
LOG.debug(_("reddwarf dns support = %s") % dns_support)
|
|
|
|
nova_client = create_nova_client(self.context)
|
|
if utils.bool_from_string(dns_support):
|
|
|
|
def get_server():
|
|
c_id = self.db_info.compute_instance_id
|
|
return nova_client.servers.get(c_id)
|
|
|
|
def ip_is_available(server):
|
|
LOG.info("Polling for ip addresses: $%s " % server.addresses)
|
|
if server.addresses != {}:
|
|
return True
|
|
elif server.addresses == {} and\
|
|
server.status != InstanceStatus.ERROR:
|
|
return False
|
|
elif server.addresses == {} and\
|
|
server.status == InstanceStatus.ERROR:
|
|
LOG.error(_("Instance IP not available, instance (%s): "
|
|
"server had status (%s).")
|
|
% (self.id, server.status))
|
|
raise ReddwarfError(status=server.status)
|
|
poll_until(get_server, ip_is_available,
|
|
sleep_time=1, time_out=60 * 2)
|
|
server = nova_client.servers.get(self.db_info.compute_instance_id)
|
|
LOG.info("Creating dns entry...")
|
|
dns_client.create_instance_entry(self.id,
|
|
get_ip_address(server.addresses))
|
|
|
|
|
|
class BuiltInstanceTasks(BuiltInstance):
|
|
"""
|
|
Performs the various asynchronous instance related tasks.
|
|
"""
|
|
|
|
def get_volume_mountpoint(self):
|
|
volume = create_nova_volume_client(self.context).volumes.get(volume_id)
|
|
mountpoint = volume.attachments[0]['device']
|
|
if mountpoint[0] is not "/":
|
|
return "/%s" % mountpoint
|
|
else:
|
|
return mountpoint
|
|
|
|
def delete_instance(self):
|
|
try:
|
|
self.server.delete()
|
|
except Exception as ex:
|
|
LOG.error("Error during delete compute server %s "
|
|
% self.server.id)
|
|
LOG.error(ex)
|
|
|
|
try:
|
|
dns_support = config.Config.get("reddwarf_dns_support", 'False')
|
|
LOG.debug(_("reddwarf dns support = %s") % dns_support)
|
|
if utils.bool_from_string(dns_support):
|
|
dns_api = create_dns_client(self.context)
|
|
dns_api.delete_instance_entry(instance_id=self.db_info.id)
|
|
except Exception as ex:
|
|
LOG.error("Error during dns entry for instance %s "
|
|
% self.db_info.id)
|
|
LOG.error(ex)
|
|
|
|
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')))
|
|
volume = self.volume_client.volumes.get(self.volume_id)
|
|
self.update_db(volume_size=volume.size)
|
|
self.nova_client.volumes.rescan_server_volume(self.server,
|
|
self.volume_id)
|
|
self.guest.resize_fs(self.get_volume_mountpoint())
|
|
except 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.update_db(task_status=inst_models.InstanceTasks.NONE)
|
|
|
|
def resize_flavor(self, new_flavor_id, old_memory_size,
|
|
new_memory_size):
|
|
def resize_status_msg():
|
|
return "instance_id=%s, status=%s, flavor_id=%s, "\
|
|
"dest. flavor id=%s)" % (self.db_info.id,
|
|
self.server.status,
|
|
str(self.flavor['id']),
|
|
str(new_flavor_id))
|
|
|
|
try:
|
|
LOG.debug("Instance %s calling stop_mysql..." % self.db_info.id)
|
|
self.guest.stop_mysql()
|
|
try:
|
|
LOG.debug("Instance %s calling Compute resize..."
|
|
% self.db_info.id)
|
|
self.server.resize(new_flavor_id)
|
|
|
|
# Do initial check and confirm the status is appropriate.
|
|
self._refresh_compute_server_info()
|
|
if self.server.status != "RESIZE" and\
|
|
self.server.status != "VERIFY_RESIZE":
|
|
raise ReddwarfError("Unexpected status after " \
|
|
"call to resize! : %s" % resize_status_msg())
|
|
|
|
# Wait for the flavor to change.
|
|
def update_server_info():
|
|
self._refresh_compute_server_info()
|
|
return self.server.status != 'RESIZE'
|
|
|
|
utils.poll_until(
|
|
update_server_info,
|
|
sleep_time=2,
|
|
time_out=60 * 2)
|
|
|
|
# Do check to make sure the status and flavor id are correct.
|
|
if (str(self.server.flavor['id']) != str(new_flavor_id) or
|
|
self.server.status != "VERIFY_RESIZE"):
|
|
raise ReddwarfError("Assertion failed! flavor_id=%s "
|
|
"and not %s"
|
|
% (str(self.server.flavor['id']), str(new_flavor_id)))
|
|
|
|
# Confirm the resize with Nova.
|
|
LOG.debug("Instance %s calling Compute confirm resize..."
|
|
% self.db_info.id)
|
|
self.server.confirm_resize()
|
|
# Record the new flavor_id in our database.
|
|
LOG.debug("Updating instance %s to flavor_id %s."
|
|
% (self.id, new_flavor_id))
|
|
self.update_db(flavor_id=new_flavor_id)
|
|
except PollTimeOut as pto:
|
|
LOG.error("Timeout trying to resize the flavor for instance "
|
|
" %s" % self.db_info.id)
|
|
except Exception as ex:
|
|
new_memory_size = old_memory_size
|
|
LOG.error("Error during resize compute! Aborting action.")
|
|
LOG.error(ex)
|
|
finally:
|
|
# Tell the guest to restart MySQL with the new RAM size.
|
|
# This is in the finally because we have to call this, or
|
|
# else MySQL could stay turned off on an otherwise usable
|
|
# instance.
|
|
LOG.debug("Instance %s starting mysql..." % self.db_info.id)
|
|
self.guest.start_mysql_with_conf_changes(new_memory_size)
|
|
finally:
|
|
self.update_db(task_status=inst_models.InstanceTasks.NONE)
|
|
|
|
def restart(self):
|
|
LOG.debug("Restarting instance %s " % self.id)
|
|
try:
|
|
self.guest.restart()
|
|
LOG.debug("Restarting successful %s " % self.id)
|
|
except GuestError:
|
|
LOG.error("Failure to restart MySQL for instance %s." % self.id)
|
|
finally:
|
|
LOG.debug("Restarting FINALLY %s " % self.id)
|
|
self.update_db(task_status=inst_models.InstanceTasks.NONE)
|
|
|
|
def _refresh_compute_server_info(self):
|
|
"""Refreshes the compute server field."""
|
|
server = self.nova_client.servers.get(self.server.id)
|
|
self.server = server
|