Cinder Support For Boot Volume

When use cinder to store data dir of database, also create rootdisk in cinder.

Story: #2009245
Task: #43418
Change-Id: Ia5841222c7a70cb0c88078575b4d8b4f7988d5e0
This commit is contained in:
Bo Tran 2021-12-29 22:59:13 +07:00
parent 22469809ec
commit 8b6ff821a1
4 changed files with 89 additions and 8 deletions

View File

@ -0,0 +1,10 @@
---
features:
- Implements nova instances with root disk in cinder backend
if the configuration `volume_rootdisk_support` is True
The size of root disk being created is `volume_rootdisk_size`
fixes:
- When creating Nova instances with a root disk in the Cinder
backend, it will allow live migration of Nova instances
and features when using Cinder as the storage backend.

View File

@ -63,8 +63,12 @@ common_opts = [
help='Port the API server will listen on.'), help='Port the API server will listen on.'),
cfg.StrOpt('api_paste_config', default="api-paste.ini", cfg.StrOpt('api_paste_config', default="api-paste.ini",
help='File name for the paste.deploy config for trove-api.'), help='File name for the paste.deploy config for trove-api.'),
cfg.BoolOpt('trove_volume_support', default=True, cfg.BoolOpt('trove_volume_support', default=False,
help='Whether to provision a Cinder volume for datadir.'), help='Whether to provision a Cinder volume for datadir.'),
cfg.BoolOpt('volume_rootdisk_support', default=False,
help='Whether to provision a Cinder volume for rootdisk.'),
cfg.IntOpt('volume_rootdisk_size', default=10,
help='Size of volume rootdisk for Database instance'),
cfg.ListOpt('admin_roles', default=['admin'], cfg.ListOpt('admin_roles', default=['admin'],
help='Roles to add to an admin user.'), help='Roles to add to an admin user.'),
cfg.BoolOpt('update_status_on_fail', default=True, cfg.BoolOpt('update_status_on_fail', default=True,

View File

@ -880,7 +880,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
def _create_server_volume(self, flavor_id, image_id, datastore_manager, def _create_server_volume(self, flavor_id, image_id, datastore_manager,
volume_size, availability_zone, nics, files, volume_size, availability_zone, nics, files,
volume_type, scheduler_hints): volume_type, scheduler_hints):
LOG.debug("Begin _create_server_volume for id: %s", self.id) LOG.debug("Begin _create_server_volume for instance: %s", self.id)
server = None server = None
volume_info = self._build_volume_info( volume_info = self._build_volume_info(
datastore_manager, datastore_manager,
@ -888,6 +888,17 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
volume_type=volume_type, volume_type=volume_type,
availability_zone=availability_zone) availability_zone=availability_zone)
block_device_mapping_v2 = volume_info['block_device'] block_device_mapping_v2 = volume_info['block_device']
if CONF.volume_rootdisk_support:
block_device_mapping_v2.insert(
0, self._create_root_volume(
image_id,
CONF.volume_rootdisk_size,
volume_type,
availability_zone
))
image_id = None
try: try:
server = self._create_server( server = self._create_server(
flavor_id, image_id, flavor_id, image_id,
@ -904,7 +915,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
exc_fmt = _("Failed to create server for instance %s") exc_fmt = _("Failed to create server for instance %s")
err = inst_models.InstanceTasks.BUILDING_ERROR_SERVER err = inst_models.InstanceTasks.BUILDING_ERROR_SERVER
self._log_and_raise(e, log_fmt, exc_fmt, self.id, err) self._log_and_raise(e, log_fmt, exc_fmt, self.id, err)
LOG.debug("End _create_server_volume for id: %s", self.id) LOG.debug("End _create_server_volume for instance: %s", self.id)
return volume_info return volume_info
def _build_volume_info(self, datastore_manager, volume_size=None, def _build_volume_info(self, datastore_manager, volume_size=None,
@ -953,6 +964,44 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin):
full_message = "%s%s" % (exc_fmt % fmt_content, exc_message) full_message = "%s%s" % (exc_fmt % fmt_content, exc_message)
raise TroveError(message=full_message) raise TroveError(message=full_message)
def _create_root_volume(self, image_id,
volume_size, volume_type, availability_zone):
LOG.debug("Begin _create_root_volume for instance: %s", self.id)
volume_client = create_cinder_client(self.context, self.region_name)
volume_desc = ("root volume for %s" % self.id)
volume_kwargs = {
'size': volume_size,
'name': "trove-%s" % self.id,
'description': volume_desc,
'volume_type': volume_type,
'imageRef': image_id
}
if CONF.enable_volume_az:
volume_kwargs['availability_zone'] = availability_zone
volume_ref = volume_client.volumes.create(**volume_kwargs)
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=CONF.volume_time_out)
v_ref = volume_client.volumes.get(volume_ref.id)
if v_ref.status in ['error']:
raise VolumeCreationFailure()
block_device_mapping_v2 = {
"uuid": v_ref.id,
"boot_index": 0,
"source_type": "volume",
"destination_type": "volume",
"delete_on_termination": True
}
LOG.debug("End _create_root_volume for instance: %s", self.id)
return block_device_mapping_v2
def _create_volume(self, volume_size, volume_type, datastore_manager, def _create_volume(self, volume_size, volume_type, datastore_manager,
availability_zone): availability_zone):
LOG.debug("Begin _create_volume for id: %s", self.id) LOG.debug("Begin _create_volume for id: %s", self.id)

View File

@ -13,6 +13,7 @@
# under the License. # under the License.
import json import json
import os import os
from tempfile import NamedTemporaryFile from tempfile import NamedTemporaryFile
from unittest import mock from unittest import mock
from unittest.mock import call from unittest.mock import call
@ -21,17 +22,19 @@ from unittest.mock import Mock
from unittest.mock import patch from unittest.mock import patch
from unittest.mock import PropertyMock from unittest.mock import PropertyMock
from cinderclient import exceptions as cinder_exceptions
import cinderclient.v3.client as cinderclient import cinderclient.v3.client as cinderclient
from cinderclient.v3 import volumes as cinderclient_volumes
import neutronclient.v2_0.client as neutronclient import neutronclient.v2_0.client as neutronclient
from novaclient import exceptions as nova_exceptions
import novaclient.v2.flavors import novaclient.v2.flavors
import novaclient.v2.servers import novaclient.v2.servers
from cinderclient import exceptions as cinder_exceptions
from cinderclient.v3 import volumes as cinderclient_volumes
from novaclient import exceptions as nova_exceptions
from oslo_config import cfg from oslo_config import cfg
from swiftclient.client import ClientException from swiftclient.client import ClientException
from testtools.matchers import Equals from testtools.matchers import Equals
from testtools.matchers import Is from testtools.matchers import Is
import trove.backup.models import trove.backup.models
import trove.common.context import trove.common.context
import trove.common.template as template import trove.common.template as template
@ -405,6 +408,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest):
@patch.object(taskmanager_models.FreshInstanceTasks, '_create_server') @patch.object(taskmanager_models.FreshInstanceTasks, '_create_server')
@patch.object(taskmanager_models.FreshInstanceTasks, '_create_secgroup') @patch.object(taskmanager_models.FreshInstanceTasks, '_create_secgroup')
@patch.object(taskmanager_models.FreshInstanceTasks, '_build_volume_info') @patch.object(taskmanager_models.FreshInstanceTasks, '_build_volume_info')
@patch.object(taskmanager_models.FreshInstanceTasks, '_create_root_volume')
@patch.object(taskmanager_models.FreshInstanceTasks, '_guest_prepare') @patch.object(taskmanager_models.FreshInstanceTasks, '_guest_prepare')
@patch.object(template, 'SingleInstanceConfigTemplate') @patch.object(template, 'SingleInstanceConfigTemplate')
@patch('trove.taskmanager.models.FreshInstanceTasks._create_port') @patch('trove.taskmanager.models.FreshInstanceTasks._create_port')
@ -412,6 +416,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest):
mock_create_port, mock_create_port,
mock_single_instance_template, mock_single_instance_template,
mock_guest_prepare, mock_guest_prepare,
mock_create_root_volume,
mock_build_volume_info, mock_build_volume_info,
mock_create_secgroup, mock_create_secgroup,
mock_create_server, mock_create_server,
@ -445,6 +450,12 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest):
is_mgmt=False, is_mgmt=False,
is_public=False is_public=False
) )
image_id = 'mysql-image-id'
if cfg.CONF.volume_rootdisk_support:
mock_create_root_volume.assert_called_with(
'mysql-image-id', 10, 'volume_type', None)
image_id = None
mock_build_volume_info.assert_called_with( mock_build_volume_info.assert_called_with(
'mysql', availability_zone=None, volume_size=2, 'mysql', availability_zone=None, volume_size=2,
volume_type='volume_type' volume_type='volume_type'
@ -454,7 +465,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest):
config_content, None, overrides, None, None, None, ds_version=None config_content, None, overrides, None, None, None, ds_version=None
) )
mock_create_server.assert_called_with( mock_create_server.assert_called_with(
8, 'mysql-image-id', 'mysql', 8, image_id, 'mysql',
mock_build_volume_info()['block_device'], None, mock_build_volume_info()['block_device'], None,
[{'port-id': 'fake-port-id'}], [{'port-id': 'fake-port-id'}],
mock_get_injected_files(), mock_get_injected_files(),
@ -466,6 +477,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest):
@patch.object(taskmanager_models.FreshInstanceTasks, 'get_injected_files') @patch.object(taskmanager_models.FreshInstanceTasks, 'get_injected_files')
@patch.object(taskmanager_models.FreshInstanceTasks, '_create_server') @patch.object(taskmanager_models.FreshInstanceTasks, '_create_server')
@patch.object(taskmanager_models.FreshInstanceTasks, '_build_volume_info') @patch.object(taskmanager_models.FreshInstanceTasks, '_build_volume_info')
@patch.object(taskmanager_models.FreshInstanceTasks, '_create_root_volume')
@patch.object(taskmanager_models.FreshInstanceTasks, '_guest_prepare') @patch.object(taskmanager_models.FreshInstanceTasks, '_guest_prepare')
@patch.object(template, 'SingleInstanceConfigTemplate') @patch.object(template, 'SingleInstanceConfigTemplate')
@patch('trove.common.clients_admin.neutron_client_trove_admin') @patch('trove.common.clients_admin.neutron_client_trove_admin')
@ -473,6 +485,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest):
mock_neutron_client, mock_neutron_client,
mock_single_instance_template, mock_single_instance_template,
mock_guest_prepare, mock_guest_prepare,
mock_create_root_volume,
mock_build_volume_info, mock_build_volume_info,
mock_create_server, mock_create_server,
mock_get_injected_files, mock_get_injected_files,
@ -516,6 +529,11 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest):
access={'is_public': True, 'allowed_cidrs': ['192.168.0.1/24']} access={'is_public': True, 'allowed_cidrs': ['192.168.0.1/24']}
) )
image_id = 'mysql-image-id'
if cfg.CONF.volume_rootdisk_support:
mock_create_root_volume.assert_called_with(
'mysql-image-id', 10, 'volume_type', None)
image_id = None
mock_build_volume_info.assert_called_with( mock_build_volume_info.assert_called_with(
'mysql', availability_zone=None, volume_size=2, 'mysql', availability_zone=None, volume_size=2,
volume_type='volume_type' volume_type='volume_type'
@ -524,7 +542,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest):
768, mock_build_volume_info(), 'mysql-server', None, None, None, 768, mock_build_volume_info(), 'mysql-server', None, None, None,
config_content, None, mock.ANY, None, None, None, ds_version=None) config_content, None, mock.ANY, None, None, None, ds_version=None)
mock_create_server.assert_called_with( mock_create_server.assert_called_with(
8, 'mysql-image-id', 'mysql', 8, image_id, 'mysql',
mock_build_volume_info()['block_device'], None, mock_build_volume_info()['block_device'], None,
[ [
{'port-id': 'fake-user-port-id'}, {'port-id': 'fake-user-port-id'},