From 8b6ff821a13888336c141f9b006e648bf75a1744 Mon Sep 17 00:00:00 2001 From: Bo Tran Date: Wed, 29 Dec 2021 22:59:13 +0700 Subject: [PATCH] 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 --- ...-create-instance-with-rootdisk-cinder.yaml | 10 ++++ trove/common/cfg.py | 6 ++- trove/taskmanager/models.py | 53 ++++++++++++++++++- .../unittests/taskmanager/test_models.py | 28 ++++++++-- 4 files changed, 89 insertions(+), 8 deletions(-) create mode 100644 releasenotes/notes/add-support-create-instance-with-rootdisk-cinder.yaml diff --git a/releasenotes/notes/add-support-create-instance-with-rootdisk-cinder.yaml b/releasenotes/notes/add-support-create-instance-with-rootdisk-cinder.yaml new file mode 100644 index 0000000000..fcc2a37dcf --- /dev/null +++ b/releasenotes/notes/add-support-create-instance-with-rootdisk-cinder.yaml @@ -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. \ No newline at end of file diff --git a/trove/common/cfg.py b/trove/common/cfg.py index 1cf6666ed5..63e2d6ddc7 100644 --- a/trove/common/cfg.py +++ b/trove/common/cfg.py @@ -63,8 +63,12 @@ common_opts = [ help='Port the API server will listen on.'), cfg.StrOpt('api_paste_config', default="api-paste.ini", 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.'), + 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'], help='Roles to add to an admin user.'), cfg.BoolOpt('update_status_on_fail', default=True, diff --git a/trove/taskmanager/models.py b/trove/taskmanager/models.py index c0f437646a..d29762592d 100755 --- a/trove/taskmanager/models.py +++ b/trove/taskmanager/models.py @@ -880,7 +880,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin): def _create_server_volume(self, flavor_id, image_id, datastore_manager, volume_size, availability_zone, nics, files, 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 volume_info = self._build_volume_info( datastore_manager, @@ -888,6 +888,17 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin): volume_type=volume_type, availability_zone=availability_zone) 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: server = self._create_server( flavor_id, image_id, @@ -904,7 +915,7 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin): exc_fmt = _("Failed to create server for instance %s") err = inst_models.InstanceTasks.BUILDING_ERROR_SERVER 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 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) 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, availability_zone): LOG.debug("Begin _create_volume for id: %s", self.id) diff --git a/trove/tests/unittests/taskmanager/test_models.py b/trove/tests/unittests/taskmanager/test_models.py index e0a1208ce4..777fba7128 100644 --- a/trove/tests/unittests/taskmanager/test_models.py +++ b/trove/tests/unittests/taskmanager/test_models.py @@ -13,6 +13,7 @@ # under the License. import json import os + from tempfile import NamedTemporaryFile from unittest import mock from unittest.mock import call @@ -21,17 +22,19 @@ from unittest.mock import Mock from unittest.mock import patch from unittest.mock import PropertyMock -from cinderclient import exceptions as cinder_exceptions import cinderclient.v3.client as cinderclient -from cinderclient.v3 import volumes as cinderclient_volumes import neutronclient.v2_0.client as neutronclient -from novaclient import exceptions as nova_exceptions import novaclient.v2.flavors 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 swiftclient.client import ClientException from testtools.matchers import Equals from testtools.matchers import Is + import trove.backup.models import trove.common.context 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_secgroup') @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(template, 'SingleInstanceConfigTemplate') @patch('trove.taskmanager.models.FreshInstanceTasks._create_port') @@ -412,6 +416,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): mock_create_port, mock_single_instance_template, mock_guest_prepare, + mock_create_root_volume, mock_build_volume_info, mock_create_secgroup, mock_create_server, @@ -445,6 +450,12 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): is_mgmt=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( 'mysql', availability_zone=None, volume_size=2, volume_type='volume_type' @@ -454,7 +465,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): config_content, None, overrides, None, None, None, ds_version=None ) mock_create_server.assert_called_with( - 8, 'mysql-image-id', 'mysql', + 8, image_id, 'mysql', mock_build_volume_info()['block_device'], None, [{'port-id': 'fake-port-id'}], mock_get_injected_files(), @@ -466,6 +477,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): @patch.object(taskmanager_models.FreshInstanceTasks, 'get_injected_files') @patch.object(taskmanager_models.FreshInstanceTasks, '_create_server') @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(template, 'SingleInstanceConfigTemplate') @patch('trove.common.clients_admin.neutron_client_trove_admin') @@ -473,6 +485,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): mock_neutron_client, mock_single_instance_template, mock_guest_prepare, + mock_create_root_volume, mock_build_volume_info, mock_create_server, mock_get_injected_files, @@ -516,6 +529,11 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): 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( 'mysql', availability_zone=None, volume_size=2, volume_type='volume_type' @@ -524,7 +542,7 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): 768, mock_build_volume_info(), 'mysql-server', None, None, None, config_content, None, mock.ANY, None, None, None, ds_version=None) mock_create_server.assert_called_with( - 8, 'mysql-image-id', 'mysql', + 8, image_id, 'mysql', mock_build_volume_info()['block_device'], None, [ {'port-id': 'fake-user-port-id'},