From 99df090a857f48ae27e9c361f6835fc2297a0498 Mon Sep 17 00:00:00 2001 From: Bo Tran Date: Thu, 6 Jan 2022 14:27:25 +0700 Subject: Adapt to file injection deprecation in nova Story: #2009770 Task: #44247 Change-Id: I34d47e709a9a7478cb6a85d6e6e37da3f92e40b0 --- trove/common/cfg.py | 4 +-- trove/instance/models.py | 40 +++++++++++++++++---- trove/taskmanager/models.py | 40 ++++++++++++++------- trove/tests/unittests/taskmanager/test_models.py | 44 ++++++++++++++++-------- 4 files changed, 91 insertions(+), 37 deletions(-) diff --git a/trove/common/cfg.py b/trove/common/cfg.py index eadb8514..65c030cf 100644 --- a/trove/common/cfg.py +++ b/trove/common/cfg.py @@ -23,9 +23,9 @@ from oslo_config import cfg from oslo_config import types from oslo_config.cfg import NoSuchOptError from oslo_log import log as logging +from oslo_log import versionutils from oslo_middleware import cors from osprofiler import opts as profiler -from oslo_log import versionutils from trove.common.i18n import _ from trove.version import version_info as version @@ -245,7 +245,7 @@ common_opts = [ cfg.IntOpt('trove_conductor_workers', help='Number of workers for the Conductor service. The default ' 'will be the number of CPUs available.'), - cfg.BoolOpt('use_nova_server_config_drive', default=True, + cfg.BoolOpt('use_nova_server_config_drive', default=False, help='Use config drive for file injection when booting ' 'instance.'), cfg.StrOpt('device_path', default='/dev/vdb', diff --git a/trove/instance/models.py b/trove/instance/models.py index b2659d08..4691b6b9 100644 --- a/trove/instance/models.py +++ b/trove/instance/models.py @@ -15,12 +15,14 @@ # under the License. """Model classes that form the core of instances functionality.""" -from datetime import datetime -from datetime import timedelta +import base64 import json import os.path import re +from datetime import datetime +from datetime import timedelta + from novaclient import exceptions as nova_exceptions from oslo_config.cfg import NoSuchOptError from oslo_log import log as logging @@ -43,8 +45,8 @@ from trove.common.i18n import _ from trove.common.trove_remote import create_trove_client from trove.configuration.models import Configuration from trove.datastore import models as datastore_models -from trove.datastore.models import DatastoreVersionMetadata as dvm from trove.datastore.models import DBDatastoreVersionMetadata +from trove.datastore.models import DatastoreVersionMetadata as dvm from trove.db import get_db_api from trove.db import models as dbmodels from trove.extensions.security_group.models import SecurityGroup @@ -614,9 +616,10 @@ def load_instance(cls, context, id, needs_server=False, def update_service_status(task_status, service_status, ins_id): """Update service status as needed.""" + RESTART_REQUIRED = srvstatus.ServiceStatuses.RESTART_REQUIRED if (task_status == InstanceTasks.NONE and - service_status.status != srvstatus.ServiceStatuses.RESTART_REQUIRED and - not service_status.is_uptodate()): + service_status.status != RESTART_REQUIRED and + not service_status.is_uptodate()): LOG.warning('Guest agent heartbeat for instance %s has expried', ins_id) service_status.status = \ @@ -718,7 +721,8 @@ class BaseInstance(SimpleInstance): from trove.cluster.models import is_cluster_deleting if (self.db_info.cluster_id is not None and not - is_cluster_deleting(self.context, self.db_info.cluster_id)): + is_cluster_deleting(context=self.context, + cluster_id=self.db_info.cluster_id)): raise exception.ClusterInstanceOperationNotSupported() if self.slaves: @@ -963,6 +967,25 @@ class BaseInstance(SimpleInstance): self._server_group_loaded = True return self._server_group + def prepare_cloud_config(self, files): + userdata = ( + "#cloud-config\n" + "write_files:\n" + ) + + for filename, content in files.items(): + ud = encodeutils.safe_encode(content) + body_userdata = ( + "- encoding: b64\n" + " owner: trove:trove\n" + " path: %s\n" + " content: %s\n" % ( + filename, encodeutils.safe_decode(base64.b64encode(ud))) + ) + userdata = userdata + body_userdata + + return userdata + def get_injected_files(self, datastore_manager, datastore_version): injected_config_location = CONF.get('injected_config_location') guest_info = CONF.get('guest_info') @@ -1034,12 +1057,14 @@ class BaseInstance(SimpleInstance): class FreshInstance(BaseInstance): + @classmethod def load(cls, context, id): return load_instance(cls, context, id, needs_server=False) class BuiltInstance(BaseInstance): + @classmethod def load(cls, context, id, needs_server=True): return load_instance(cls, context, id, needs_server=needs_server) @@ -1509,7 +1534,7 @@ class Instance(BuiltInstance): if not self.slaves: raise exception.BadRequest(_("Instance %s is not a replica" - " source.") % self.id) + " source.") % self.id) service = InstanceServiceStatus.find_by(instance_id=self.id) last_heartbeat_delta = timeutils.utcnow() - service.updated_at @@ -1934,6 +1959,7 @@ class DBInstance(dbmodels.DatabaseModelBase): class instance_encryption_key_cache(object): + def __init__(self, func, lru_cache_size=10): self._table = {} self._lru = [] diff --git a/trove/taskmanager/models.py b/trove/taskmanager/models.py index 49742af8..134aecb5 100755 --- a/trove/taskmanager/models.py +++ b/trove/taskmanager/models.py @@ -229,8 +229,8 @@ class ClusterTasks(Cluster): ) def _all_instances_acquire_status( - self, instance_ids, cluster_id, shard_id, expected_status, - fast_fail_statuses=None): + self, instance_ids, cluster_id, shard_id, expected_status, + fast_fail_statuses=None): def _is_fast_fail_status(status): return ((fast_fail_statuses is not None) and @@ -244,7 +244,7 @@ class ClusterTasks(Cluster): task_status = DBInstance.find_by( id=instance_id).get_task_status() if (_is_fast_fail_status(status) or - (task_status == InstanceTasks.BUILDING_ERROR_SERVER)): + (task_status == InstanceTasks.BUILDING_ERROR_SERVER)): # if one has failed, no need to continue polling LOG.debug("Instance %(id)s has acquired a fast-fail " "status %(status)s and" @@ -269,7 +269,7 @@ class ClusterTasks(Cluster): task_status = DBInstance.find_by( id=instance_id).get_task_status() if (_is_fast_fail_status(status) or - (task_status == InstanceTasks.BUILDING_ERROR_SERVER)): + (task_status == InstanceTasks.BUILDING_ERROR_SERVER)): failed_instance_ids.append(instance_id) return failed_instance_ids @@ -373,8 +373,8 @@ class ClusterTasks(Cluster): context.notification = ( DBaaSInstanceUpgrade(context, **request_info)) with StartNotification( - context, instance_id=instance.id, - datastore_version_id=datastore_version.id): + context, instance_id=instance.id, + datastore_version_id=datastore_version.id): with EndNotification(context): instance.update_db( datastore_version_id=datastore_version.id, @@ -781,8 +781,8 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin): status = service.get_status() if (status == srvstatus.ServiceStatuses.RUNNING or - status == srvstatus.ServiceStatuses.INSTANCE_READY or - status == srvstatus.ServiceStatuses.HEALTHY): + status == srvstatus.ServiceStatuses.INSTANCE_READY or + status == srvstatus.ServiceStatuses.HEALTHY): return True elif status not in [srvstatus.ServiceStatuses.NEW, srvstatus.ServiceStatuses.BUILDING, @@ -986,6 +986,16 @@ class FreshInstanceTasks(FreshInstance, NotifyMixin, ConfigurationMixin): config_drive = CONF.use_nova_server_config_drive key_name = CONF.nova_keypair + # Use config_drive instead by userdata + # We will inject guest config by cloud-config + if not config_drive: + if not userdata: + userdata = self.prepare_cloud_config(files) + else: + userdata = userdata + self.prepare_cloud_config(files) + + files = {} + server = self.nova_client.servers.create( self.name, image_id, flavor_id, key_name=key_name, nics=nics, block_device_mapping_v2=bdmap_v2, @@ -1432,6 +1442,7 @@ class BuiltInstanceTasks(Instance, NotifyMixin, ConfigurationMixin): class BackupTasks(object): + @classmethod def _parse_manifest(cls, manifest): # manifest is in the format 'container/prefix' @@ -1530,11 +1541,11 @@ class ModuleTasks(object): for instance_module in instance_modules: instance_id = instance_module.instance_id if (instance_module.md5 != current_md5 or force) and ( - not md5 or md5 == instance_module.md5): + not md5 or md5 == instance_module.md5): instance = BuiltInstanceTasks.load(context, instance_id, needs_server=False) if instance and ( - include_clustered or not instance.cluster_id): + include_clustered or not instance.cluster_id): try: module_models.Modules.validate( modules, instance.datastore.id, @@ -1550,8 +1561,8 @@ class ModuleTasks(object): # Sleep if we've fired off too many in a row. if (batch_size and - not reapply_count % batch_size and - (reapply_count + skipped_count) < total_count): + not reapply_count % batch_size and + (reapply_count + skipped_count) < total_count): LOG.debug("Applied module to %(cnt)d of %(total)d " "instances - sleeping for %(batch)ds", {'cnt': reapply_count, @@ -1908,7 +1919,7 @@ class ResizeActionBase(object): self._perform_nova_action() finally: if self.instance.db_info.task_status != ( - inst_models.InstanceTasks.NONE): + inst_models.InstanceTasks.NONE): self.instance.reset_task_status() def _guest_is_awake(self): @@ -1988,6 +1999,7 @@ class ResizeActionBase(object): class ResizeAction(ResizeActionBase): + def __init__(self, instance, old_flavor, new_flavor): """ :type instance: trove.taskmanager.models.BuiltInstanceTasks @@ -2043,6 +2055,7 @@ class ResizeAction(ResizeActionBase): class MigrateAction(ResizeActionBase): + def __init__(self, instance, host=None): super(MigrateAction, self).__init__(instance) self.instance = instance @@ -2070,6 +2083,7 @@ class MigrateAction(ResizeActionBase): class RebuildAction(ResizeActionBase): + def __init__(self, instance, image_id): """The action to perform rebuild. diff --git a/trove/tests/unittests/taskmanager/test_models.py b/trove/tests/unittests/taskmanager/test_models.py index fde42e35..5167bc04 100644 --- a/trove/tests/unittests/taskmanager/test_models.py +++ b/trove/tests/unittests/taskmanager/test_models.py @@ -12,43 +12,46 @@ # License for the specific language governing permissions and limitations # under the License. import os + from tempfile import NamedTemporaryFile from unittest import mock -from unittest.mock import call from unittest.mock import MagicMock from unittest.mock import Mock -from unittest.mock import patch from unittest.mock import PropertyMock +from unittest.mock import call +from unittest.mock import patch -from cinderclient import exceptions as cinder_exceptions -from cinderclient.v3 import volumes as cinderclient_volumes import cinderclient.v3.client as cinderclient 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 +import trove.db.models +import trove.guestagent.api + from trove import rpc from trove.backup import models as backup_models from trove.backup import state -import trove.backup.models +from trove.common import exception from trove.common import timeutils from trove.common import utils -import trove.common.context -from trove.common import exception from trove.common.exception import GuestError from trove.common.exception import PollTimeOut from trove.common.exception import TroveError -import trove.common.template as template from trove.datastore import models as datastore_models -import trove.db.models from trove.extensions.common import models as common_models from trove.extensions.mysql import models as mysql_models -import trove.guestagent.api from trove.instance.models import BaseInstance from trove.instance.models import DBInstance from trove.instance.models import InstanceServiceStatus @@ -64,6 +67,7 @@ VOLUME_ID = 'volume-id-1' class FakeOptGroup(object): + def __init__(self, tcp_ports=['3306', '3301-3307'], udp_ports=[], icmp=False): self.tcp_ports = tcp_ports @@ -72,6 +76,7 @@ class FakeOptGroup(object): class fake_Server(object): + def __init__(self): self.id = None self.name = None @@ -108,6 +113,7 @@ class fake_ServerManager(object): class fake_nova_client(object): + def __init__(self): self.servers = fake_ServerManager() @@ -236,7 +242,8 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): server = self.freshinstancetasks._create_server( None, None, datastore_manager, None, None, None) - self.assertEqual(server.userdata, self.userdata) + userdata = self.userdata + "#cloud-config\nwrite_files:\n" + self.assertEqual(server.userdata, userdata) def test_create_instance_with_keypair(self): cfg.CONF.set_override('nova_keypair', 'fake_keypair') @@ -310,7 +317,8 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): def test_servers_create_block_device_mapping_v2(self, mock_hostname, mock_name): - self.freshinstancetasks.prepare_userdata = Mock(return_value=None) + self.freshinstancetasks.prepare_userdata = Mock( + return_value="#cloud-config\nwrite_files:\n") mock_nova_client = self.freshinstancetasks.nova_client = Mock() mock_servers_create = mock_nova_client.servers.create self.freshinstancetasks._create_server('fake-flavor', 'fake-image', @@ -318,14 +326,18 @@ class FreshInstanceTasksTest(BaseFreshInstanceTasksTest): meta = {'trove_project_id': self.freshinstancetasks.tenant_id, 'trove_user_id': 'test_user', 'trove_instance_id': self.freshinstancetasks.id} + + userdata = self.freshinstancetasks.prepare_userdata('mysql') + userdata = userdata + \ + self.freshinstancetasks.prepare_cloud_config({}) mock_servers_create.assert_called_with( 'fake-name', 'fake-image', 'fake-flavor', files={}, - userdata=None, + userdata=userdata, block_device_mapping_v2=None, availability_zone=None, nics=None, - config_drive=True, + config_drive=False, scheduler_hints=None, key_name=None, meta=meta, @@ -599,6 +611,7 @@ class ResizeVolumeTest(trove_testtools.TestCase): self.new_vol_size) class FakeGroup(object): + def __init__(self): self.mount_point = 'var/lib/mysql' self.device_path = '/dev/vdb' @@ -1105,6 +1118,7 @@ class BackupTasksTest(trove_testtools.TestCase): class NotifyMixinTest(trove_testtools.TestCase): + def test_get_service_id(self): id_map = { 'mysql': '123', -- cgit v1.2.1