Remove direct mapping from API -> DB

In order to complete the eventlet migration, we need to
be able to use the indirection_api surface to be able to
disjoint the API surface from the message bus, so we're
not fighting between processes for database locks.

Previously, the calls were directly routed directly
to the database layer to streamline performance.
This attachment allows the calls to run through
the indirection layer to the object, and return
the data to the caller.

As a result, API RPC versions are incremented.

Change-Id: I7358ac2a70198c78a0a9ba48511fc1289c64294f
Signed-off-by: Julia Kreger <juliaashleykreger@gmail.com>
This commit is contained in:
Julia Kreger
2025-08-17 06:38:30 -07:00
parent 2e3fdd828c
commit 8fd1cc8c97
8 changed files with 91 additions and 14 deletions
+2 -1
View File
@@ -446,7 +446,8 @@ class AllocationsController(pecan.rest.RestController):
# if any. The result is processes authenticating with system
# scope will not be impacted, where as project scoped requests
# will need additional authorization.
converted = api.request.dbapi.check_node_list(
converted = objects.Allocation.check_node_list(
context,
allocation['candidate_nodes'],
project=owner)
except exception.NodeNotFound as exc:
+11 -6
View File
@@ -26,7 +26,7 @@ from ironic.common import exception
from ironic.common.i18n import _
from ironic.common import metrics_utils
from ironic.drivers import base as driver_base
from ironic import objects
METRICS = metrics_utils.get_metrics_logger(__name__)
@@ -130,8 +130,10 @@ def convert_with_links(name, hosts, detail=False, interface_info=None,
if detail:
if interface_info is None:
# TODO(jroll) objectify this
interface_info = (api.request.dbapi
.list_hardware_type_interfaces([name]))
interface_info = (objects.Conductor
.list_hardware_type_interfaces_dict(
api.request.context,
[name]))
for iface_type in driver_base.ALL_INTERFACES:
default = None
enabled = set()
@@ -191,7 +193,8 @@ def list_convert_with_links(hardware_types, detail=False, fields=None):
# This is checked in Driver.convert_with_links(), however also
# checking here can save us a DB query.
if api_utils.allow_dynamic_drivers() and detail:
iface_info = api.request.dbapi.list_hardware_type_interfaces(
iface_info = objects.Conductor.list_hardware_type_interfaces_dict(
api.request.context,
list(hardware_types))
else:
iface_info = []
@@ -348,7 +351,8 @@ class DriversController(rest.RestController):
'if specified.'))
if type is None or type == 'dynamic':
hw_type_dict = api.request.dbapi.get_active_hardware_type_dict()
hw_type_dict = objects.Conductor.get_active_hardware_type_dict(
api.request.context)
else:
# NOTE(dtantsur): we don't support classic drivers starting with
# the Rocky release.
@@ -369,7 +373,8 @@ class DriversController(rest.RestController):
_check_allow_driver_fields(fields)
hw_type_dict = api.request.dbapi.get_active_hardware_type_dict()
hw_type_dict = objects.Conductor.get_active_hardware_type_dict(
api.request.context)
for name, hosts in hw_type_dict.items():
if name == driver_name:
return convert_with_links(name, list(hosts),
+2 -1
View File
@@ -21,6 +21,7 @@ from ironic.api.schemas.v1 import shard as schema
from ironic.api import validation
from ironic.common.i18n import _
from ironic.common import metrics_utils
from ironic import objects
CONF = cfg.CONF
@@ -47,7 +48,7 @@ class ShardController(pecan.rest.RestController):
api_utils.check_policy('baremetal:shards:get')
return {
'shards': api.request.dbapi.get_shard_list(),
'shards': objects.Conductor.get_shard_list(api.request.context),
}
@METRICS.timer('ShardController.get_one')
+2 -2
View File
@@ -899,12 +899,12 @@ RELEASE_MAPPING = {
'api': '1.100',
'rpc': '1.61',
'objects': {
'Allocation': ['1.1'],
'Allocation': ['1.2', '1.1'],
'BIOSSetting': ['1.1'],
'Node': ['1.41'],
'NodeHistory': ['1.0'],
'NodeInventory': ['1.0'],
'Conductor': ['1.4'],
'Conductor': ['1.5', '1.4'],
'Chassis': ['1.3'],
'Deployment': ['1.0'],
'DeployTemplate': ['1.1'],
+13 -1
View File
@@ -27,7 +27,8 @@ from ironic.objects import notification
class Allocation(base.IronicObject, object_base.VersionedObjectDictCompat):
# Version 1.0: Initial version
# Version 1.1: Add owner field
VERSION = '1.1'
# Version 1.2: Add remotable method check_node_list
VERSION = '1.2'
dbapi = dbapi.get_instance()
@@ -272,6 +273,17 @@ class Allocation(base.IronicObject, object_base.VersionedObjectDictCompat):
self.obj_refresh(current)
self.obj_reset_changes()
@base.remotable_classmethod
def check_node_list(cls, context, candidate_nodes, project):
"""Provides a pass-through to the database for allocation node lists.
Calls the database directly as opposed to allowing the API to directly
call the database when an indirection API is set.
"""
return cls.dbapi.check_node_list(
candidate_nodes,
project=project)
@base.IronicObjectRegistry.register
class AllocationCRUDNotification(notification.NotificationBase):
+2
View File
@@ -26,6 +26,8 @@ from ironic.objects import fields as object_fields
LOG = log.getLogger(__name__)
remotable_classmethod = object_base.remotable_classmethod
def max_version(versions):
"""Return the maximum version in the list.
+57 -1
View File
@@ -14,6 +14,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from oslo_versionedobjects import base as object_base
from ironic.common.i18n import _
@@ -32,7 +34,10 @@ class Conductor(base.IronicObject, object_base.VersionedObjectDictCompat):
# unregister_all_hardware_interfaces()
# Version 1.3: Add conductor_group field.
# Version 1.4: Add online field.
VERSION = '1.4'
# Version 1.5: Add new remotable methods
# get_shard_list, list_hardware_type_interfaces,
# and get_active_hardware_type_dict
VERSION = '1.5'
dbapi = db_api.get_instance()
@@ -172,3 +177,54 @@ class Conductor(base.IronicObject, object_base.VersionedObjectDictCompat):
def unregister_all_hardware_interfaces(self):
"""Unregister all hardware interfaces for this conductor."""
self.dbapi.unregister_conductor_hardware_interfaces(self.id)
@base.remotable_classmethod
def get_active_hardware_type_dict(cls, context, use_groups=False):
"""Provides a hardware type list as it relates to the conductors.
This method provides a pass-through call mechanism on an attached
object for insight into the state of hardware managers by conductors
and does so as a direct call for compatibility with lightweight
API method.
"""
return cls.dbapi.get_active_hardware_type_dict(use_groups=use_groups)
@base.remotable_classmethod
def list_hardware_type_interfaces_dict(cls, context, names):
"""Provides a list of hardware type interface names from conductors.
This method provides a pass-through call mechanism on an object as
opposed to direct API call functionality.
"""
# NOTE(TheJulia): SQLAlchemy hands us a hybrid object which also
# works like a dictionary, and the consumer of this call treats
# it as such but we can't hand it across the message bus as a
# DB object.
db_resp = cls.dbapi.list_hardware_type_interfaces(names)
resp = []
for row in db_resp:
entry = {}
for col_key in row.keys():
if isinstance(row[col_key], datetime.datetime):
# SQLAclchemy hands response objects with nested
# datetime objects, so they need to be converted
# before trying to serialize as opposed before
# sending the API response out.
entry[col_key] = row[col_key].isoformat()
else:
entry[col_key] = row[col_key]
resp.append(entry)
return resp
@base.remotable_classmethod
def get_shard_list(cls, context):
"""Provides a shard list as it relates to conductors.
This method provides a pass-through all mechanism on an attached
object for insight into the list of represented shards in a deployment
which is sourced in the database combined with runtime configurations.
The primary prupose of this being be lightweight and enable
indirection_api call usage instead of trying to directly invoke the
database.
"""
return cls.dbapi.get_shard_list()
+2 -2
View File
@@ -680,7 +680,7 @@ expected_object_fingerprints = {
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
'Port': '1.13-f79db5cc15189d3e8b257db582b2329e',
'Portgroup': '1.5-df4dc15967f67114d51176a98a901a83',
'Conductor': '1.4-a9703208fdab5fab8f1cec420be1b4a7',
'Conductor': '1.5-c241d0c83c05c263b12c9b0289995462',
'EventType': '1.1-aa2ba1afd38553e3880c267404e8d370',
'NotificationPublisher': '1.0-51a09397d6c0687771fb5be9a999605d',
'NodePayload': '1.17-4022bb737b058d426a7ff878b1875e5c',
@@ -712,7 +712,7 @@ expected_object_fingerprints = {
'TraitList': '1.0-33a2e1bb91ad4082f9f63429b77c1244',
'BIOSSetting': '1.1-1137db88675a4e2d7f7bcc3a0d52345a',
'BIOSSettingList': '1.0-33a2e1bb91ad4082f9f63429b77c1244',
'Allocation': '1.1-38937f2854722f1057ec667b12878708',
'Allocation': '1.2-9aae5cc874a292af10ba08918ba21b13',
'AllocationCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
'AllocationCRUDPayload': '1.1-3c8849932b80380bb96587ff62e8f087',
'DeployTemplate': '1.1-4e30c8e9098595e359bb907f095bf1a9',