DB & Object layer for node.shard
DB and object implementations for new node.shard key. Story: 2010768 Task: 46624 Change-Id: Ia7ef3cffc321c93501b1cc5185972a4ac1dcb212
This commit is contained in:
parent
a66208f24b
commit
36ef217fdb
@ -180,6 +180,7 @@ def node_schema():
|
|||||||
'retired': {'type': ['string', 'boolean', 'null']},
|
'retired': {'type': ['string', 'boolean', 'null']},
|
||||||
'retired_reason': {'type': ['string', 'null']},
|
'retired_reason': {'type': ['string', 'null']},
|
||||||
'secure_boot': {'type': ['string', 'boolean', 'null']},
|
'secure_boot': {'type': ['string', 'boolean', 'null']},
|
||||||
|
'shard': {'type': ['string', 'null']},
|
||||||
'storage_interface': {'type': ['string', 'null']},
|
'storage_interface': {'type': ['string', 'null']},
|
||||||
'uuid': {'type': ['string', 'null']},
|
'uuid': {'type': ['string', 'null']},
|
||||||
'vendor_interface': {'type': ['string', 'null']},
|
'vendor_interface': {'type': ['string', 'null']},
|
||||||
@ -1383,6 +1384,7 @@ def _get_fields_for_node_query(fields=None):
|
|||||||
'retired',
|
'retired',
|
||||||
'retired_reason',
|
'retired_reason',
|
||||||
'secure_boot',
|
'secure_boot',
|
||||||
|
'shard',
|
||||||
'storage_interface',
|
'storage_interface',
|
||||||
'target_power_state',
|
'target_power_state',
|
||||||
'target_provision_state',
|
'target_provision_state',
|
||||||
|
@ -516,7 +516,7 @@ RELEASE_MAPPING = {
|
|||||||
'objects': {
|
'objects': {
|
||||||
'Allocation': ['1.1'],
|
'Allocation': ['1.1'],
|
||||||
'BIOSSetting': ['1.1'],
|
'BIOSSetting': ['1.1'],
|
||||||
'Node': ['1.36'],
|
'Node': ['1.37'],
|
||||||
'NodeHistory': ['1.0'],
|
'NodeHistory': ['1.0'],
|
||||||
'NodeInventory': ['1.0'],
|
'NodeInventory': ['1.0'],
|
||||||
'Conductor': ['1.3'],
|
'Conductor': ['1.3'],
|
||||||
|
@ -72,6 +72,7 @@ class Connection(object, metaclass=abc.ABCMeta):
|
|||||||
:reserved_by_any_of: [conductor1, conductor2]
|
:reserved_by_any_of: [conductor1, conductor2]
|
||||||
:resource_class: resource class name
|
:resource_class: resource class name
|
||||||
:retired: True | False
|
:retired: True | False
|
||||||
|
:shard_in: shard (multiple possibilities)
|
||||||
:provision_state: provision state of node
|
:provision_state: provision state of node
|
||||||
:provision_state_in:
|
:provision_state_in:
|
||||||
provision state of node (multiple possibilities)
|
provision state of node (multiple possibilities)
|
||||||
@ -106,6 +107,7 @@ class Connection(object, metaclass=abc.ABCMeta):
|
|||||||
:provisioned_before:
|
:provisioned_before:
|
||||||
nodes with provision_updated_at field before this
|
nodes with provision_updated_at field before this
|
||||||
interval in seconds
|
interval in seconds
|
||||||
|
:shard: nodes with the given shard
|
||||||
:param limit: Maximum number of nodes to return.
|
:param limit: Maximum number of nodes to return.
|
||||||
:param marker: the last item of the previous page; we return the next
|
:param marker: the last item of the previous page; we return the next
|
||||||
result set.
|
result set.
|
||||||
@ -1455,3 +1457,10 @@ class Connection(object, metaclass=abc.ABCMeta):
|
|||||||
:param node_id: The integer node ID.
|
:param node_id: The integer node ID.
|
||||||
:returns: An inventory of a node.
|
:returns: An inventory of a node.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_shard_list(self):
|
||||||
|
"""Retrieve a list of shards.
|
||||||
|
|
||||||
|
:returns: list of dicts containing shard names and count
|
||||||
|
"""
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
"""create node.shard
|
||||||
|
|
||||||
|
Revision ID: 4dbec778866e
|
||||||
|
Revises: 0ac0f39bc5aa
|
||||||
|
Create Date: 2022-11-10 14:20:59.175355
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4dbec778866e'
|
||||||
|
down_revision = '0ac0f39bc5aa'
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.add_column('nodes', sa.Column('shard', sa.String(length=255),
|
||||||
|
nullable=True))
|
||||||
|
op.create_index('shard_idx', 'nodes', ['shard'], unique=False)
|
@ -2588,3 +2588,31 @@ class Connection(api.Connection):
|
|||||||
return query.one()
|
return query.one()
|
||||||
except NoResultFound:
|
except NoResultFound:
|
||||||
raise exception.NodeInventoryNotFound(node_id=node_id)
|
raise exception.NodeInventoryNotFound(node_id=node_id)
|
||||||
|
|
||||||
|
def get_shard_list(self):
|
||||||
|
"""Return a list of shards.
|
||||||
|
|
||||||
|
:returns: A list of dicts containing the keys name and count.
|
||||||
|
"""
|
||||||
|
# Note(JayF): This should never be a large enough list to require
|
||||||
|
# pagination. Furthermore, it wouldn't really be a sensible
|
||||||
|
# thing to paginate as the data it's fetching can mutate.
|
||||||
|
# So we just aren't even going to try.
|
||||||
|
shard_list = []
|
||||||
|
with _session_for_read() as session:
|
||||||
|
res = session.execute(
|
||||||
|
# Note(JayF): SQLAlchemy counts are notoriously slow because
|
||||||
|
# sometimes they will use a subquery. Be careful
|
||||||
|
# before changing this to use any magic.
|
||||||
|
sa.text(
|
||||||
|
"SELECT count(id), shard from nodes group by shard;"
|
||||||
|
)).fetchall()
|
||||||
|
|
||||||
|
if res:
|
||||||
|
res.sort(key=lambda x: x[0], reverse=True)
|
||||||
|
for shard in res:
|
||||||
|
shard_list.append(
|
||||||
|
{"name": str(shard[1]), "count": shard[0]}
|
||||||
|
)
|
||||||
|
|
||||||
|
return shard_list
|
||||||
|
@ -134,6 +134,7 @@ class NodeBase(Base):
|
|||||||
Index('reservation_idx', 'reservation'),
|
Index('reservation_idx', 'reservation'),
|
||||||
Index('conductor_group_idx', 'conductor_group'),
|
Index('conductor_group_idx', 'conductor_group'),
|
||||||
Index('resource_class_idx', 'resource_class'),
|
Index('resource_class_idx', 'resource_class'),
|
||||||
|
Index('shard_idx', 'shard'),
|
||||||
table_args())
|
table_args())
|
||||||
id = Column(Integer, primary_key=True)
|
id = Column(Integer, primary_key=True)
|
||||||
uuid = Column(String(36))
|
uuid = Column(String(36))
|
||||||
@ -214,6 +215,8 @@ class NodeBase(Base):
|
|||||||
boot_mode = Column(String(16), nullable=True)
|
boot_mode = Column(String(16), nullable=True)
|
||||||
secure_boot = Column(Boolean, nullable=True)
|
secure_boot = Column(Boolean, nullable=True)
|
||||||
|
|
||||||
|
shard = Column(String(255), nullable=True)
|
||||||
|
|
||||||
|
|
||||||
class Node(NodeBase):
|
class Node(NodeBase):
|
||||||
"""Represents a bare metal node."""
|
"""Represents a bare metal node."""
|
||||||
|
@ -78,7 +78,8 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
|||||||
# Version 1.34: Add lessee field
|
# Version 1.34: Add lessee field
|
||||||
# Version 1.35: Add network_data field
|
# Version 1.35: Add network_data field
|
||||||
# Version 1.36: Add boot_mode and secure_boot fields
|
# Version 1.36: Add boot_mode and secure_boot fields
|
||||||
VERSION = '1.36'
|
# Version 1.37: Add shard field
|
||||||
|
VERSION = '1.37'
|
||||||
|
|
||||||
dbapi = db_api.get_instance()
|
dbapi = db_api.get_instance()
|
||||||
|
|
||||||
@ -170,6 +171,7 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
|||||||
'network_data': object_fields.FlexibleDictField(nullable=True),
|
'network_data': object_fields.FlexibleDictField(nullable=True),
|
||||||
'boot_mode': object_fields.StringField(nullable=True),
|
'boot_mode': object_fields.StringField(nullable=True),
|
||||||
'secure_boot': object_fields.BooleanField(nullable=True),
|
'secure_boot': object_fields.BooleanField(nullable=True),
|
||||||
|
'shard': object_fields.StringField(nullable=True),
|
||||||
}
|
}
|
||||||
|
|
||||||
def as_dict(self, secure=False, mask_configdrive=True):
|
def as_dict(self, secure=False, mask_configdrive=True):
|
||||||
@ -656,6 +658,8 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
|||||||
should be set to empty dict (or removed).
|
should be set to empty dict (or removed).
|
||||||
Version 1.36: boot_mode, secure_boot were was added. Defaults are None.
|
Version 1.36: boot_mode, secure_boot were was added. Defaults are None.
|
||||||
For versions prior to this, it should be set to None or removed.
|
For versions prior to this, it should be set to None or removed.
|
||||||
|
Version 1.37: shard was added. Default is None. For versions prior to
|
||||||
|
this, it should be set to None or removed.
|
||||||
|
|
||||||
:param target_version: the desired version of the object
|
:param target_version: the desired version of the object
|
||||||
:param remove_unavailable_fields: True to remove fields that are
|
:param remove_unavailable_fields: True to remove fields that are
|
||||||
@ -671,7 +675,7 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat):
|
|||||||
('automated_clean', 28), ('protected_reason', 29),
|
('automated_clean', 28), ('protected_reason', 29),
|
||||||
('owner', 30), ('allocation_id', 31), ('description', 32),
|
('owner', 30), ('allocation_id', 31), ('description', 32),
|
||||||
('retired_reason', 33), ('lessee', 34), ('boot_mode', 36),
|
('retired_reason', 33), ('lessee', 34), ('boot_mode', 36),
|
||||||
('secure_boot', 36)]
|
('secure_boot', 36), ('shard', 37)]
|
||||||
|
|
||||||
for name, minor in fields:
|
for name, minor in fields:
|
||||||
self._adjust_field_to_version(name, None, target_version,
|
self._adjust_field_to_version(name, None, target_version,
|
||||||
|
@ -1257,6 +1257,10 @@ class MigrationCheckersMixin(object):
|
|||||||
self.assertIsInstance(node_inventory.c.node_id.type,
|
self.assertIsInstance(node_inventory.c.node_id.type,
|
||||||
sqlalchemy.types.Integer)
|
sqlalchemy.types.Integer)
|
||||||
|
|
||||||
|
def _check_4dbec778866e(self, engine, data):
|
||||||
|
nodes = db_utils.get_table(engine, 'nodes')
|
||||||
|
self.assertIsInstance(nodes.c.shard.type, sqlalchemy.types.String)
|
||||||
|
|
||||||
def test_upgrade_and_version(self):
|
def test_upgrade_and_version(self):
|
||||||
with patch_with_engine(self.engine):
|
with patch_with_engine(self.engine):
|
||||||
self.migration_api.upgrade('head')
|
self.migration_api.upgrade('head')
|
||||||
|
46
ironic/tests/unit/db/test_shard.py
Normal file
46
ironic/tests/unit/db/test_shard.py
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""Tests for fetching shards via the DB API"""
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from oslo_db.sqlalchemy import enginefacade
|
||||||
|
|
||||||
|
from ironic.tests.unit.db import base
|
||||||
|
from ironic.tests.unit.db import utils
|
||||||
|
|
||||||
|
|
||||||
|
class ShardTestCase(base.DbTestCase):
|
||||||
|
def setUp(self):
|
||||||
|
super(ShardTestCase, self).setUp()
|
||||||
|
self.engine = enginefacade.writer.get_engine()
|
||||||
|
|
||||||
|
def test_get_shard_list(self):
|
||||||
|
"""Validate shard list is returned, and with correct sorting."""
|
||||||
|
for i in range(1, 2):
|
||||||
|
utils.create_test_node(uuid=str(uuid.uuid4()))
|
||||||
|
for i in range(1, 3):
|
||||||
|
utils.create_test_node(uuid=str(uuid.uuid4()), shard="shard1")
|
||||||
|
for i in range(1, 4):
|
||||||
|
utils.create_test_node(uuid=str(uuid.uuid4()), shard="shard2")
|
||||||
|
|
||||||
|
res = self.dbapi.get_shard_list()
|
||||||
|
self.assertEqual(res, [
|
||||||
|
{"name": "shard2", "count": 3},
|
||||||
|
{"name": "shard1", "count": 2},
|
||||||
|
{"name": "None", "count": 1},
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_get_shard_empty_list(self):
|
||||||
|
"""Validate empty list is returned if no assigned shards."""
|
||||||
|
res = self.dbapi.get_shard_list()
|
||||||
|
self.assertEqual(res, [])
|
@ -237,6 +237,7 @@ def get_test_node(**kw):
|
|||||||
'network_data': kw.get('network_data'),
|
'network_data': kw.get('network_data'),
|
||||||
'boot_mode': kw.get('boot_mode', None),
|
'boot_mode': kw.get('boot_mode', None),
|
||||||
'secure_boot': kw.get('secure_boot', None),
|
'secure_boot': kw.get('secure_boot', None),
|
||||||
|
'shard': kw.get('shard', None)
|
||||||
}
|
}
|
||||||
|
|
||||||
for iface in drivers_base.ALL_INTERFACES:
|
for iface in drivers_base.ALL_INTERFACES:
|
||||||
|
@ -676,7 +676,7 @@ class TestObject(_LocalTest, _TestObject):
|
|||||||
# version bump. It is an MD5 hash of the object fields and remotable methods.
|
# version bump. It is an MD5 hash of the object fields and remotable methods.
|
||||||
# The fingerprint values should only be changed if there is a version bump.
|
# The fingerprint values should only be changed if there is a version bump.
|
||||||
expected_object_fingerprints = {
|
expected_object_fingerprints = {
|
||||||
'Node': '1.36-8a080e31ba89ca5f09e859bd259b54dc',
|
'Node': '1.37-6b38eb91aec57532547ea8607f95675a',
|
||||||
'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6',
|
'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6',
|
||||||
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
|
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
|
||||||
'Port': '1.11-97bf15b61224f26c65e90f007d78bfd2',
|
'Port': '1.11-97bf15b61224f26c65e90f007d78bfd2',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user