diff --git a/ironic/db/sqlalchemy/alembic/versions/bcdd431ba0bf_add_fields_for_all_interfaces.py b/ironic/db/sqlalchemy/alembic/versions/bcdd431ba0bf_add_fields_for_all_interfaces.py new file mode 100644 index 0000000000..6c5db9224e --- /dev/null +++ b/ironic/db/sqlalchemy/alembic/versions/bcdd431ba0bf_add_fields_for_all_interfaces.py @@ -0,0 +1,45 @@ +# 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 fields for all interfaces + +Revision ID: bcdd431ba0bf +Revises: 60cf717201bc +Create Date: 2016-11-11 16:44:52.823881 + +""" + +# revision identifiers, used by Alembic. +revision = 'bcdd431ba0bf' +down_revision = '60cf717201bc' + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('nodes', sa.Column('boot_interface', + sa.String(length=255), nullable=True)) + op.add_column('nodes', sa.Column('console_interface', + sa.String(length=255), nullable=True)) + op.add_column('nodes', sa.Column('deploy_interface', + sa.String(length=255), nullable=True)) + op.add_column('nodes', sa.Column('inspect_interface', + sa.String(length=255), nullable=True)) + op.add_column('nodes', sa.Column('management_interface', + sa.String(length=255), nullable=True)) + op.add_column('nodes', sa.Column('power_interface', + sa.String(length=255), nullable=True)) + op.add_column('nodes', sa.Column('raid_interface', + sa.String(length=255), nullable=True)) + op.add_column('nodes', sa.Column('vendor_interface', + sa.String(length=255), nullable=True)) diff --git a/ironic/db/sqlalchemy/models.py b/ironic/db/sqlalchemy/models.py index b3a5e46ff0..2fbe87d7ce 100644 --- a/ironic/db/sqlalchemy/models.py +++ b/ironic/db/sqlalchemy/models.py @@ -144,7 +144,15 @@ class Node(Base): inspection_started_at = Column(DateTime, nullable=True) extra = Column(db_types.JsonEncodedDict) + boot_interface = Column(String(255), nullable=True) + console_interface = Column(String(255), nullable=True) + deploy_interface = Column(String(255), nullable=True) + inspect_interface = Column(String(255), nullable=True) + management_interface = Column(String(255), nullable=True) network_interface = Column(String(255), nullable=True) + raid_interface = Column(String(255), nullable=True) + power_interface = Column(String(255), nullable=True) + vendor_interface = Column(String(255), nullable=True) class Port(Base): diff --git a/ironic/drivers/base.py b/ironic/drivers/base.py index f1285095cf..2d49f6fe2e 100644 --- a/ironic/drivers/base.py +++ b/ironic/drivers/base.py @@ -171,6 +171,13 @@ class BareDriver(BaseDriver): self.core_interfaces.append('network') +ALL_INTERFACES = set(BareDriver().all_interfaces) +"""Constant holding all known interfaces. + +Includes interfaces not exposed via BaseDriver.all_interfaces. +""" + + @six.add_metaclass(abc.ABCMeta) class BaseInterface(object): """A base interface implementing common functions for Driver Interfaces.""" diff --git a/ironic/objects/node.py b/ironic/objects/node.py index 40092d91b8..2f8b64311a 100644 --- a/ironic/objects/node.py +++ b/ironic/objects/node.py @@ -58,7 +58,10 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): # Version 1.16: Add network_interface field # Version 1.17: Add resource_class field # Version 1.18: Add default setting for network_interface - VERSION = '1.18' + # Version 1.19: Add fields: boot_interface, console_interface, + # deploy_interface, inspect_interface, management_interface, + # power_interface, raid_interface, vendor_interface + VERSION = '1.19' dbapi = db_api.get_instance() @@ -118,8 +121,16 @@ class Node(base.IronicObject, object_base.VersionedObjectDictCompat): 'extra': object_fields.FlexibleDictField(nullable=True), + 'boot_interface': object_fields.StringField(nullable=True), + 'console_interface': object_fields.StringField(nullable=True), + 'deploy_interface': object_fields.StringField(nullable=True), + 'inspect_interface': object_fields.StringField(nullable=True), + 'management_interface': object_fields.StringField(nullable=True), 'network_interface': object_fields.StringFieldThatAcceptsCallable( nullable=False, default=_default_network_interface), + 'power_interface': object_fields.StringField(nullable=True), + 'raid_interface': object_fields.StringField(nullable=True), + 'vendor_interface': object_fields.StringField(nullable=True), } def _validate_property_values(self, properties): diff --git a/ironic/tests/unit/api/utils.py b/ironic/tests/unit/api/utils.py index 3f70d7410b..e9193a524a 100644 --- a/ironic/tests/unit/api/utils.py +++ b/ironic/tests/unit/api/utils.py @@ -23,6 +23,7 @@ from ironic.api.controllers.v1 import chassis as chassis_controller from ironic.api.controllers.v1 import node as node_controller from ironic.api.controllers.v1 import port as port_controller from ironic.api.controllers.v1 import portgroup as portgroup_controller +from ironic.drivers import base as drivers_base from ironic.tests.unit.db import utils ADMIN_TOKEN = '4562138218392831' @@ -99,8 +100,10 @@ def node_post_data(**kw): # NOTE(jroll): pop out fields that were introduced in later API versions, # unless explicitly requested. Otherwise, these will cause tests using # older API versions to fail. - if 'network_interface' not in kw: - node.pop('network_interface') + for iface in drivers_base.ALL_INTERFACES: + name = '%s_interface' % iface + if name not in kw: + node.pop(name) if 'resource_class' not in kw: node.pop('resource_class') diff --git a/ironic/tests/unit/db/sqlalchemy/test_migrations.py b/ironic/tests/unit/db/sqlalchemy/test_migrations.py index e499f4284b..e0c464b590 100644 --- a/ironic/tests/unit/db/sqlalchemy/test_migrations.py +++ b/ironic/tests/unit/db/sqlalchemy/test_migrations.py @@ -53,6 +53,7 @@ import sqlalchemy.exc from ironic.common.i18n import _LE from ironic.db.sqlalchemy import migration from ironic.db.sqlalchemy import models +from ironic.drivers import base as base_driver from ironic.tests import base LOG = logging.getLogger(__name__) @@ -537,6 +538,15 @@ class MigrationCheckersMixin(object): (sqlalchemy.types.Boolean, sqlalchemy.types.Integer)) + def _check_bcdd431ba0bf(self, engine, data): + nodes = db_utils.get_table(engine, 'nodes') + col_names = [column.name for column in nodes.c] + for iface in base_driver.ALL_INTERFACES: + name = '%s_interface' % iface + self.assertIn(name, col_names) + self.assertIsInstance(getattr(nodes.c, name).type, + sqlalchemy.types.String) + def test_upgrade_and_version(self): with patch_with_engine(self.engine): self.migration_api.upgrade('head') diff --git a/ironic/tests/unit/db/utils.py b/ironic/tests/unit/db/utils.py index fc626a87ae..72fe659fa8 100644 --- a/ironic/tests/unit/db/utils.py +++ b/ironic/tests/unit/db/utils.py @@ -19,6 +19,7 @@ from oslo_utils import timeutils from ironic.common import states from ironic.db import api as db_api +from ironic.drivers import base as drivers_base def get_test_ipmi_info(): @@ -214,7 +215,7 @@ def get_test_node(**kw): fake_internal_info = { "private_state": "secret value" } - return { + result = { 'id': kw.get('id', 123), 'name': kw.get('name', None), 'uuid': kw.get('uuid', '1be26c0b-03f2-4d2e-ae87-c02d7f33c123'), @@ -248,9 +249,14 @@ def get_test_node(**kw): 'target_raid_config': kw.get('target_raid_config'), 'tags': kw.get('tags', []), 'resource_class': kw.get('resource_class'), - 'network_interface': kw.get('network_interface'), } + for iface in drivers_base.ALL_INTERFACES: + name = '%s_interface' % iface + result[name] = kw.get(name) + + return result + def create_test_node(**kw): """Create test node entry in DB and return Node DB object. diff --git a/ironic/tests/unit/objects/test_objects.py b/ironic/tests/unit/objects/test_objects.py index 8fdc583e79..f5afdeeb13 100644 --- a/ironic/tests/unit/objects/test_objects.py +++ b/ironic/tests/unit/objects/test_objects.py @@ -404,7 +404,7 @@ class TestObject(_LocalTest, _TestObject): # version bump. It is md5 hash of object fields and remotable methods. # The fingerprint values should only be changed if there is a version bump. expected_object_fingerprints = { - 'Node': '1.18-37a1d39ba8a4957f505dda936ac9146b', + 'Node': '1.19-e8b294016d8d5b322df813f790d092b4', 'MyObj': '1.5-4f5efe8f0fcaf182bbe1c7fe3ba858db', 'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905', 'Port': '1.6-609504503d68982a10f495659990084b', diff --git a/releasenotes/notes/interface-fields-f4b9384fdda6189a.yaml b/releasenotes/notes/interface-fields-f4b9384fdda6189a.yaml new file mode 100644 index 0000000000..0989f4721f --- /dev/null +++ b/releasenotes/notes/interface-fields-f4b9384fdda6189a.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - Add database migration to add new fields corresponding to all interfaces + to the node table.