DB: inspection rules migration
Change-Id: I83b7e37ad44dca395144ce14510a1de0f6eeefa5
This commit is contained in:
parent
5262536417
commit
d6a692e3fa
ironic
common
db
objects
tests/unit/objects
@ -1085,3 +1085,13 @@ class ImageServiceAuthenticationRequired(ImageUnacceptable):
|
||||
_msg_fmt = _("The requested image %(image_ref)s requires "
|
||||
"authentication which has not been provided. "
|
||||
"Unable to proceed.")
|
||||
|
||||
|
||||
class InspectionRuleAlreadyExists(Conflict):
|
||||
"""Rule requested already exists in the database."""
|
||||
_msg_fmt = _("A rule with UUID %(uuid)s already exists.")
|
||||
|
||||
|
||||
class InspectionRuleNotFound(NotFound):
|
||||
"""The requested rule was not found."""
|
||||
_msg_fmt = _("Rule %(rule)s could not be found.")
|
||||
|
@ -796,6 +796,7 @@ RELEASE_MAPPING = {
|
||||
'VolumeTarget': ['1.0'],
|
||||
'FirmwareComponent': ['1.0'],
|
||||
'Runbook': ['1.0'],
|
||||
'InspectionRule': ['1.0'],
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -1637,3 +1637,63 @@ class Connection(object, metaclass=abc.ABCMeta):
|
||||
should include child nodes with their own power supplies.
|
||||
:returns: A list of tuples.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def create_inspection_rule(self, values):
|
||||
"""Create an inspection rule.
|
||||
|
||||
:param values: A dict describing the rule.
|
||||
:raises: InspectionRuleAlreadyExists if an inspection rule with the
|
||||
same UUID exists.
|
||||
:returns: A rule.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def update_inspection_rule(self, inspection_rule_id, values):
|
||||
"""Update an inspection rule.
|
||||
|
||||
:param inspection_rule_id: The id or uuid of an inspection rule.
|
||||
:param values: Dict of values to update.
|
||||
:raises: InspectionRuleNotFound if the rule does not exist.
|
||||
:returns: A rule.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_inspection_rule_by_uuid(self, inspection_rule_uuid):
|
||||
"""Retrieve an inspection rule by UUID.
|
||||
|
||||
:param inspection_rule_uuid: UUID of the rule to retrieve.
|
||||
:raises: InspectionRuleNotFound if the rule does not exist.
|
||||
:returns: A rule.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_inspection_rule_by_id(self, inspection_rule_id):
|
||||
"""Retrieve an inspection rule by id.
|
||||
|
||||
:param inspection_rule_id: id of the rule to retrieve.
|
||||
:raises: InspectionRuleNotFound if the rule does not exist.
|
||||
:returns: A rule.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_inspection_rule_list(self, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Retrieve a list of inspection rules.
|
||||
|
||||
:param limit: Maximum number of rules to return.
|
||||
:param marker: The last item of the previous page; we return the next
|
||||
result set.
|
||||
:param sort_key: Attribute by which results should be sorted.
|
||||
:param sort_dir: Direction in which results should be sorted.
|
||||
(asc, desc)
|
||||
:returns: A list of inspection rules.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def destroy_inspection_rule(self, inspection_rule_id):
|
||||
"""Destroy an inspection rule.
|
||||
|
||||
:param inspection_rule_id: ID of the inspection_rule to destroy.
|
||||
:raises: inspection_ruleNotFound if the inspection_rule does not exist.
|
||||
"""
|
||||
|
@ -0,0 +1,54 @@
|
||||
# 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.
|
||||
|
||||
"""Add inspection rules
|
||||
|
||||
Revision ID: 21c48150dea9
|
||||
Revises: 66bd9c5604d5
|
||||
Create Date: 2024-08-14 14:13:24.462303
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
from oslo_db.sqlalchemy import types
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '21c48150dea9'
|
||||
down_revision = '6e9cf6acce0b'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
'inspection_rules',
|
||||
sa.Column('version', sa.String(length=15), nullable=True),
|
||||
sa.Column('created_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('updated_at', sa.DateTime(), nullable=True),
|
||||
sa.Column('id', sa.Integer(), nullable=False,
|
||||
autoincrement=True),
|
||||
sa.Column('uuid', sa.String(36), nullable=False),
|
||||
sa.Column('priority', sa.Integer(), nullable=False, default=0),
|
||||
sa.Column('description', sa.String(255), nullable=True),
|
||||
sa.Column('scope', sa.String(255), nullable=True),
|
||||
sa.Column('sensitive', sa.Boolean(), default=False),
|
||||
sa.Column('phase', sa.String(16), nullable=True),
|
||||
sa.Column('conditions', types.JsonEncodedList(mysql_as_long=True),
|
||||
nullable=True),
|
||||
sa.Column('actions', types.JsonEncodedList(mysql_as_long=True),
|
||||
nullable=False),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
sa.UniqueConstraint('uuid', name='uniq_inspection_rules0uuid'),
|
||||
sa.Index('inspection_rule_scope_idx', 'scope'),
|
||||
sa.Index('inspection_rule_phase_idx', 'phase'),
|
||||
mysql_ENGINE='InnoDB',
|
||||
mysql_DEFAULT_CHARSET='UTF8'
|
||||
)
|
@ -3171,3 +3171,76 @@ class Connection(api.Connection):
|
||||
continue
|
||||
nodes.append(r[0])
|
||||
return nodes
|
||||
|
||||
def create_inspection_rule(self, values):
|
||||
"""Create new rule"""
|
||||
inspection_rule = models.InspectionRule()
|
||||
inspection_rule.update(values)
|
||||
|
||||
with _session_for_write() as session:
|
||||
try:
|
||||
session.add(inspection_rule)
|
||||
session.flush()
|
||||
except db_exc.DBDuplicateEntry:
|
||||
raise exception.InspectionRuleAlreadyExists(
|
||||
uuid=values['uuid'])
|
||||
return inspection_rule
|
||||
|
||||
def update_inspection_rule(self, rule_uuid, values):
|
||||
"""Update an existing inspection rule.
|
||||
|
||||
:param values: Dict of values to update with.
|
||||
:param rule_id: The rule id.
|
||||
"""
|
||||
with _session_for_write() as session:
|
||||
query = session.query(models.InspectionRule).filter_by(
|
||||
uuid=rule_uuid)
|
||||
try:
|
||||
ref = query.with_for_update().one()
|
||||
except NoResultFound:
|
||||
raise exception.InspectionRuleNotFound(
|
||||
rule=rule_uuid)
|
||||
ref.update(values)
|
||||
return ref
|
||||
|
||||
def _get_inspection_rule(self, field, value):
|
||||
"""Helper method for retrieving an inspection rule."""
|
||||
query = sa.select(models.InspectionRule).where(field == value)
|
||||
try:
|
||||
with _session_for_read() as session:
|
||||
res = session.execute(query).one()[0]
|
||||
return res
|
||||
except NoResultFound:
|
||||
raise exception.InspectionRuleNotFound(rule=value)
|
||||
|
||||
def get_inspection_rule_by_uuid(self, inspection_rule_uuid):
|
||||
return self._get_inspection_rule(models.InspectionRule.uuid,
|
||||
inspection_rule_uuid)
|
||||
|
||||
def get_inspection_rule_by_id(self, inspection_rule_id):
|
||||
return self._get_inspection_rule(models.InspectionRule.id,
|
||||
inspection_rule_id)
|
||||
|
||||
def get_inspection_rule_list(self, limit=None, marker=None, filters=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
query = (sa.select(models.InspectionRule))
|
||||
if filters is None:
|
||||
filters = dict()
|
||||
supported_filters = {'phase', 'scope'}
|
||||
unsupported_filters = set(filters).difference(supported_filters)
|
||||
if unsupported_filters:
|
||||
msg = _("SqlAlchemy API does not support "
|
||||
"filtering by %s") % ', '.join(unsupported_filters)
|
||||
raise ValueError(msg)
|
||||
for field in filters:
|
||||
query = query.filter_by(**{field: filters[field]})
|
||||
return _paginate_query(models.InspectionRule, limit, marker,
|
||||
sort_key, sort_dir, query)
|
||||
|
||||
def destroy_inspection_rule(self, inspection_rule_id):
|
||||
with _session_for_write() as session:
|
||||
count = session.query(models.InspectionRule).filter_by(
|
||||
id=inspection_rule_id).delete()
|
||||
if count == 0:
|
||||
raise exception.InspectionRuleNotFound(
|
||||
rule=inspection_rule_id)
|
||||
|
@ -563,6 +563,26 @@ class RunbookStep(Base):
|
||||
)
|
||||
|
||||
|
||||
class InspectionRule(Base):
|
||||
__tablename__ = 'inspection_rules'
|
||||
__table_args__ = (
|
||||
schema.UniqueConstraint('uuid', name='uniq_inspection_rules0uuid'),
|
||||
Index('inspection_rule_scope_idx', 'scope'),
|
||||
Index('inspection_rule_phase_idx', 'phase'),
|
||||
table_args())
|
||||
id = Column(Integer, primary_key=True)
|
||||
uuid = Column(String(36), nullable=False)
|
||||
priority = Column(Integer, nullable=False, default=0)
|
||||
description = Column(String(255), nullable=True)
|
||||
scope = Column(String(255), nullable=True)
|
||||
sensitive = Column(Boolean, default=False)
|
||||
phase = Column(String(16), nullable=True, default='main')
|
||||
conditions = Column(db_types.JsonEncodedList(mysql_as_long=True),
|
||||
nullable=True)
|
||||
actions = Column(db_types.JsonEncodedList(mysql_as_long=True),
|
||||
nullable=False)
|
||||
|
||||
|
||||
def get_class(model_name):
|
||||
"""Returns the model class with the specified name.
|
||||
|
||||
|
@ -31,6 +31,7 @@ def register_all():
|
||||
__import__('ironic.objects.deploy_template')
|
||||
__import__('ironic.objects.deployment')
|
||||
__import__('ironic.objects.firmware')
|
||||
__import__('ironic.objects.inspection_rule')
|
||||
__import__('ironic.objects.node')
|
||||
__import__('ironic.objects.node_history')
|
||||
__import__('ironic.objects.node_inventory')
|
||||
|
211
ironic/objects/inspection_rule.py
Normal file
211
ironic/objects/inspection_rule.py
Normal file
@ -0,0 +1,211 @@
|
||||
# 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.
|
||||
from oslo_versionedobjects import base as object_base
|
||||
|
||||
from ironic.db import api as db_api
|
||||
from ironic.objects import base
|
||||
from ironic.objects import fields as object_fields
|
||||
from ironic.objects import notification
|
||||
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
class InspectionRule(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
dbapi = db_api.get_instance()
|
||||
|
||||
fields = {
|
||||
'id': object_fields.IntegerField(),
|
||||
'uuid': object_fields.UUIDField(nullable=False),
|
||||
'priority': object_fields.IntegerField(default=0),
|
||||
'description': object_fields.StringField(nullable=True),
|
||||
'sensitive': object_fields.BooleanField(default=False),
|
||||
'phase': object_fields.StringField(nullable=True, default='main'),
|
||||
'scope': object_fields.StringField(nullable=True),
|
||||
'actions': object_fields.ListOfFlexibleDictsField(nullable=False),
|
||||
'conditions': object_fields.ListOfFlexibleDictsField(nullable=True),
|
||||
}
|
||||
|
||||
# NOTE(mgoddard): We don't want to enable RPC on this call just yet.
|
||||
# Remotable methods can be used in the future to replace current explicit
|
||||
# RPC calls. Implications of calling new remote procedures should be
|
||||
# thought through.
|
||||
# @object_base.remotable
|
||||
def create(self, context=None):
|
||||
"""Create a InspectionRule record in the DB.
|
||||
|
||||
:param context: security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: InspectionRule(context).
|
||||
:raises: InspectionRuleName if a inspection rule with the same
|
||||
name exists.
|
||||
:raises: InspectionRuleAlreadyExists if a inspection rule with the same
|
||||
UUID exists.
|
||||
"""
|
||||
values = self.do_version_changes_for_db()
|
||||
db_rule = self.dbapi.create_inspection_rule(values)
|
||||
self._from_db_object(self._context, self, db_rule)
|
||||
|
||||
def save(self, context=None):
|
||||
"""Save updates to this InspectionRule.
|
||||
|
||||
Column-wise updates will be made based on the result of
|
||||
self.what_changed().
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api,
|
||||
but, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: InspectionRule(context)
|
||||
:raises: InspectionRuleNotFound if the inspection rule does not exist.
|
||||
"""
|
||||
updates = self.do_version_changes_for_db()
|
||||
db_rule = self.dbapi.update_inspection_rule(self.uuid, updates)
|
||||
self._from_db_object(self._context, self, db_rule)
|
||||
|
||||
def destroy(self):
|
||||
"""Delete the InspectionRule from the DB.
|
||||
|
||||
:param context: security context. NOTE: This should only
|
||||
be used internally by the indirection_api,
|
||||
but, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: InspectionRule(context).
|
||||
:raises: InspectionRuleNotFound if the inspection_rule no longer
|
||||
appears in the database.
|
||||
"""
|
||||
self.dbapi.destroy_inspection_rule(self.id)
|
||||
self.obj_reset_changes()
|
||||
|
||||
# NOTE(mgoddard): We don't want to enable RPC on this call just yet.
|
||||
# Remotable methods can be used in the future to replace current explicit
|
||||
# RPC calls. Implications of calling new remote procedures should be
|
||||
# thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def get_by_uuid(cls, context, uuid):
|
||||
"""Find a inspection rule based on its UUID.
|
||||
|
||||
:param context: security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: InspectionRule(context).
|
||||
:param uuid: The UUID of a inspection rule.
|
||||
:raises: InspectionRuleNotFound if the inspection rule no longer
|
||||
appears in the database.
|
||||
:returns: a :class:`InspectionRule` object.
|
||||
"""
|
||||
db_rule = cls.dbapi.get_inspection_rule_by_uuid(uuid)
|
||||
rule = cls._from_db_object(context, cls(), db_rule)
|
||||
return rule
|
||||
|
||||
# NOTE(mgoddard): We don't want to enable RPC on this call just yet.
|
||||
# Remotable methods can be used in the future to replace current explicit
|
||||
# RPC calls. Implications of calling new remote procedures should be
|
||||
# thought through.
|
||||
# @object_base.remotable_classmethod
|
||||
@classmethod
|
||||
def list(cls, context, limit=None, marker=None, sort_key=None,
|
||||
sort_dir=None, filters=None):
|
||||
"""Return a list of InspectionRule objects.
|
||||
|
||||
:param context: security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: InspectionRule(context).
|
||||
:param limit: maximum number of resources to return in a single result.
|
||||
:param marker: pagination marker for large data sets.
|
||||
:param sort_key: column to sort results by.
|
||||
:param sort_dir: direction to sort. "asc" or "desc".
|
||||
:returns: a list of :class:`InspectionRule` objects.
|
||||
"""
|
||||
db_rules = cls.dbapi.get_inspection_rule_list(
|
||||
limit=limit, marker=marker, sort_key=sort_key, sort_dir=sort_dir,
|
||||
filters=filters)
|
||||
return cls._from_db_object_list(context, db_rules)
|
||||
|
||||
def refresh(self, context=None):
|
||||
"""Loads updates for this inspection rule.
|
||||
|
||||
Loads a inspection rule with the same uuid from the database and
|
||||
checks for updated attributes. Updates are applied from
|
||||
the loaded rule column by column, if there are any updates.
|
||||
|
||||
:param context: Security context. NOTE: This should only
|
||||
be used internally by the indirection_api.
|
||||
Unfortunately, RPC requires context as the first
|
||||
argument, even though we don't use it.
|
||||
A context should be set when instantiating the
|
||||
object, e.g.: Port(context)
|
||||
:raises: InspectionRuleNotFound if the inspection rule no longer
|
||||
appears in the database.
|
||||
"""
|
||||
current = self.get_by_uuid(self._context, uuid=self.uuid)
|
||||
self.obj_refresh(current)
|
||||
self.obj_reset_changes()
|
||||
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
class InspectionRuleCRUDNotification(notification.NotificationBase):
|
||||
"""Notification emitted on inspection rule API operations."""
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
fields = {
|
||||
'payload': object_fields.ObjectField('InspectionRuleCRUDPayload')
|
||||
}
|
||||
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
class InspectionRuleCRUDPayload(notification.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
VERSION = '1.0'
|
||||
|
||||
SCHEMA = {
|
||||
'created_at': ('inspection_rule', 'created_at'),
|
||||
'description': ('inspection_rule', 'description'),
|
||||
'phase': ('inspection_rule', 'phase'),
|
||||
'priority': ('inspection_rule', 'priority'),
|
||||
'scope': ('inspection_rule', 'scope'),
|
||||
'sensitive': ('inspection_rule', 'sensitive'),
|
||||
'actions': ('inspection_rule', 'actions'),
|
||||
'conditions': ('inspection_rule', 'conditions'),
|
||||
'updated_at': ('inspection_rule', 'updated_at'),
|
||||
'uuid': ('inspection_rule', 'uuid')
|
||||
}
|
||||
|
||||
fields = {
|
||||
'created_at': object_fields.DateTimeField(nullable=True),
|
||||
'description': object_fields.StringField(nullable=True),
|
||||
'phase': object_fields.StringField(nullable=True, default='main'),
|
||||
'priority': object_fields.IntegerField(default=0),
|
||||
'scope': object_fields.StringField(nullable=True),
|
||||
'sensitive': object_fields.BooleanField(default=False),
|
||||
'actions': object_fields.ListOfFlexibleDictsField(nullable=False),
|
||||
'conditions': object_fields.ListOfFlexibleDictsField(nullable=True),
|
||||
'updated_at': object_fields.DateTimeField(nullable=True),
|
||||
'uuid': object_fields.UUIDField()
|
||||
}
|
||||
|
||||
def __init__(self, inspection_rule, **kwargs):
|
||||
super(InspectionRuleCRUDPayload, self).__init__(**kwargs)
|
||||
self.populate_schema(inspection_rule=inspection_rule)
|
@ -727,6 +727,9 @@ expected_object_fingerprints = {
|
||||
'Runbook': '1.0-7a9c65b49b5f7b45686b6a674e703629',
|
||||
'RunbookCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'RunbookCRUDPayload': '1.0-f0c97f4ff29eb3401e53b34550a95e30',
|
||||
'InspectionRule': '1.0-517185d327442696e408781343c2b83f',
|
||||
'InspectionRuleCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'InspectionRuleCRUDPayload': '1.0-85d1cf2105308534a630299a897bf562',
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user