Merge "Adapt to file injection deprecation in nova"

This commit is contained in:
Zuul 2022-02-13 11:25:23 +00:00 committed by Gerrit Code Review
commit 10ac7a6138
4 changed files with 92 additions and 38 deletions
trove
common
instance
taskmanager
tests/unittests/taskmanager

@ -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',

@ -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 = []

@ -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.

@ -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
import cinderclient.v3.client as cinderclient
import neutronclient.v2_0.client as neutronclient
import novaclient.v2.flavors
import novaclient.v2.servers
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 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',