Add dynamic portgroup fields (API 1.111)
Add two new boolean fields in API microversion 1.111: - Port.available_for_dynamic_portgroup: user-editable via POST/PATCH, default True - Portgroup.dynamic_portgroup: read-only to API consumers, default False, settable internally by Ironic These fields support dynamic portgroup assignment where ports can be marked as available for automatic portgroup membership and portgroups can be flagged as dynamically managed. Assisted-By: claude-code Change-Id: Ib10f8bd5ba9e25b60cffd7d67d1e2c45be8f8533 Signed-off-by: Jay Faulkner <jay@jvf.cc>
This commit is contained in:
@@ -2,6 +2,14 @@
|
||||
REST API Version History
|
||||
========================
|
||||
|
||||
1.111 (Gazpacho)
|
||||
----------------------
|
||||
|
||||
Add ``available_for_dynamic_portgroup`` boolean field to the port object
|
||||
(defaults to ``True``) and ``dynamic_portgroup`` boolean field to the
|
||||
portgroup object. The port field is user-editable via POST and PATCH
|
||||
requests. The portgroup field is read-only to API consumers.
|
||||
|
||||
1.110 (Gazpacho)
|
||||
----------------------
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ PORT_SCHEMA = {
|
||||
'description': {'type': ['string', 'null'], 'maxLength': 255},
|
||||
'vendor': {'type': ['string', 'null'], 'maxLength': 32},
|
||||
'category': {'type': ['string', 'null'], 'maxLength': 80},
|
||||
'available_for_dynamic_portgroup': {'type': 'boolean'},
|
||||
},
|
||||
'required': ['address'],
|
||||
'oneOf': [
|
||||
@@ -82,6 +83,7 @@ PATCH_ALLOWED_FIELDS = [
|
||||
'description',
|
||||
'vendor',
|
||||
'category',
|
||||
'available_for_dynamic_portgroup',
|
||||
]
|
||||
|
||||
PORT_VALIDATOR_EXTRA = args.dict_valid(
|
||||
@@ -92,6 +94,7 @@ PORT_VALIDATOR_EXTRA = args.dict_valid(
|
||||
portgroup_uuid=args.uuid,
|
||||
pxe_enabled=args.boolean,
|
||||
uuid=args.uuid,
|
||||
available_for_dynamic_portgroup=args.boolean,
|
||||
)
|
||||
|
||||
PORT_VALIDATOR = args.and_valid(
|
||||
@@ -149,6 +152,10 @@ def hide_fields_in_newer_versions(port):
|
||||
# if requested version is < 1.101, hide category field.
|
||||
if not api_utils.allow_port_category():
|
||||
port.pop('category', None)
|
||||
# if requested version is < 1.111, hide
|
||||
# available_for_dynamic_portgroup field.
|
||||
if not api_utils.allow_port_available_for_dynamic_portgroup():
|
||||
port.pop('available_for_dynamic_portgroup', None)
|
||||
|
||||
|
||||
def convert_with_links(rpc_port, fields=None, sanitize=True):
|
||||
@@ -168,6 +175,7 @@ def convert_with_links(rpc_port, fields=None, sanitize=True):
|
||||
'description',
|
||||
'vendor',
|
||||
'category',
|
||||
'available_for_dynamic_portgroup',
|
||||
)
|
||||
)
|
||||
if rpc_port.portgroup_id:
|
||||
@@ -423,6 +431,10 @@ class PortsController(rest.RestController):
|
||||
if ('category' in fields
|
||||
and not api_utils.allow_port_category()):
|
||||
raise exception.NotAcceptable()
|
||||
allow_dynpg = api_utils.allow_port_available_for_dynamic_portgroup
|
||||
if ('available_for_dynamic_portgroup' in fields
|
||||
and not allow_dynpg()):
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
@METRICS.timer('PortsController.get_all')
|
||||
@method.expose()
|
||||
|
||||
@@ -93,6 +93,9 @@ def hide_fields_in_newer_versions(portgroup):
|
||||
# if requested version is < 1.103, hide category
|
||||
if not api_utils.allow_portgroup_category():
|
||||
portgroup.pop('category', None)
|
||||
# if requested version is < 1.111, hide dynamic_portgroup
|
||||
if not api_utils.allow_portgroup_dynamic_portgroup():
|
||||
portgroup.pop('dynamic_portgroup', None)
|
||||
|
||||
|
||||
def portgroup_sanitize(portgroup, fields=None):
|
||||
@@ -123,7 +126,8 @@ def convert_with_links(rpc_portgroup, fields=None, sanitize=True):
|
||||
'standalone_ports_supported',
|
||||
'node_uuid',
|
||||
'physical_network',
|
||||
'category'
|
||||
'category',
|
||||
'dynamic_portgroup',
|
||||
)
|
||||
)
|
||||
url = api.request.public_url
|
||||
|
||||
@@ -946,6 +946,9 @@ def check_allowed_portgroup_fields(fields):
|
||||
if (('mode' in fields or 'properties' in fields)
|
||||
and not allow_portgroup_mode_properties()):
|
||||
raise exception.NotAcceptable()
|
||||
if ('dynamic_portgroup' in fields
|
||||
and not allow_portgroup_dynamic_portgroup()):
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
|
||||
def check_allow_management_verbs(verb):
|
||||
@@ -2345,3 +2348,23 @@ def check_allow_filter_by_instance_name(instance_name):
|
||||
if instance_name is not None and not allow_node_instance_name():
|
||||
raise exception.NotAcceptable(
|
||||
_("instance_name is not acceptable in this API version"))
|
||||
|
||||
|
||||
def allow_port_available_for_dynamic_portgroup():
|
||||
"""Check if available_for_dynamic_portgroup is allowed for ports.
|
||||
|
||||
Version 1.111 of the API added available_for_dynamic_portgroup
|
||||
field to the port object.
|
||||
"""
|
||||
return (api.request.version.minor
|
||||
>= versions.MINOR_111_DYNAMIC_PORTGROUP)
|
||||
|
||||
|
||||
def allow_portgroup_dynamic_portgroup():
|
||||
"""Check if dynamic_portgroup is allowed for portgroups.
|
||||
|
||||
Version 1.111 of the API added dynamic_portgroup field to the
|
||||
portgroup object.
|
||||
"""
|
||||
return (api.request.version.minor
|
||||
>= versions.MINOR_111_DYNAMIC_PORTGROUP)
|
||||
|
||||
@@ -148,6 +148,8 @@ BASE_VERSION = 1
|
||||
# v1.108: Add disable_ramdisk support for servicing
|
||||
# v1.109: Add health field to node object.
|
||||
# v1.110: Add support for aborting deployment in DEPLOYWAIT state
|
||||
# v1.111: Add available_for_dynamic_portgroup to port,
|
||||
# dynamic_portgroup to portgroup
|
||||
|
||||
MINOR_0_JUNO = 0
|
||||
MINOR_1_INITIAL_VERSION = 1
|
||||
@@ -260,6 +262,7 @@ MINOR_107_X_OPENSTACK_REQUEST_ID = 107
|
||||
MINOR_108_SERVICE_DISABLE_RAMDISK = 108
|
||||
MINOR_109_NODE_HEALTH = 109
|
||||
MINOR_110_DEPLOYWAIT_ABORT = 110
|
||||
MINOR_111_DYNAMIC_PORTGROUP = 111
|
||||
|
||||
# When adding another version, update:
|
||||
# - MINOR_MAX_VERSION
|
||||
@@ -269,7 +272,7 @@ MINOR_110_DEPLOYWAIT_ABORT = 110
|
||||
# - Add a comment describing the change above the list of consts
|
||||
|
||||
|
||||
MINOR_MAX_VERSION = MINOR_110_DEPLOYWAIT_ABORT
|
||||
MINOR_MAX_VERSION = MINOR_111_DYNAMIC_PORTGROUP
|
||||
|
||||
# String representations of the minor and maximum versions
|
||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||
|
||||
@@ -944,7 +944,7 @@ RELEASE_MAPPING = {
|
||||
# make it below. To release, we will preserve a version matching
|
||||
# the release as a separate block of text, like above.
|
||||
'master': {
|
||||
'api': '1.110',
|
||||
'api': '1.111',
|
||||
'rpc': '1.62',
|
||||
'networking_rpc': '1.0',
|
||||
'objects': {
|
||||
@@ -957,8 +957,8 @@ RELEASE_MAPPING = {
|
||||
'Chassis': ['1.4', '1.3'],
|
||||
'Deployment': ['1.1', '1.0'],
|
||||
'DeployTemplate': ['1.2', '1.1'],
|
||||
'Port': ['1.15', '1.14', '1.13', '1.12'],
|
||||
'Portgroup': ['1.8', '1.7', '1.6', '1.5'],
|
||||
'Port': ['1.16', '1.15', '1.14', '1.13', '1.12'],
|
||||
'Portgroup': ['1.9', '1.8', '1.7', '1.6', '1.5'],
|
||||
'Trait': ['1.1', '1.0'],
|
||||
'TraitList': ['1.2', '1.1', '1.0'],
|
||||
'VolumeConnector': ['1.1', '1.0'],
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
# 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 dynamic portgroup fields
|
||||
|
||||
Revision ID: 2a3b4c5d6e7f
|
||||
Revises: c1fd28861bb9
|
||||
Create Date: 2026-03-04 00:00:00.000000
|
||||
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '2a3b4c5d6e7f'
|
||||
down_revision = 'c1fd28861bb9'
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('ports',
|
||||
sa.Column('available_for_dynamic_portgroup',
|
||||
sa.Boolean(),
|
||||
server_default=sa.true(),
|
||||
nullable=False))
|
||||
op.add_column('portgroups',
|
||||
sa.Column('dynamic_portgroup',
|
||||
sa.Boolean(),
|
||||
nullable=True))
|
||||
@@ -25,7 +25,7 @@ from oslo_db import options as db_options
|
||||
from oslo_db.sqlalchemy import models
|
||||
from oslo_db.sqlalchemy import types as db_types
|
||||
from sqlalchemy.ext.associationproxy import association_proxy
|
||||
from sqlalchemy import Boolean, Column, DateTime, false, Index
|
||||
from sqlalchemy import Boolean, Column, DateTime, false, Index, true
|
||||
from sqlalchemy import BigInteger, ForeignKey, Integer
|
||||
from sqlalchemy import schema, String, Text
|
||||
from sqlalchemy import orm
|
||||
@@ -273,6 +273,8 @@ class Port(Base):
|
||||
description = Column(String(255), nullable=True)
|
||||
vendor = Column(String(32), nullable=True)
|
||||
category = Column(String(80), nullable=True)
|
||||
available_for_dynamic_portgroup = Column(Boolean, server_default=true(),
|
||||
nullable=False)
|
||||
|
||||
_node_uuid = orm.relationship(
|
||||
"Node",
|
||||
@@ -305,6 +307,7 @@ class Portgroup(Base):
|
||||
properties = Column(db_types.JsonEncodedDict)
|
||||
physical_network = Column(String(64), nullable=True)
|
||||
category = Column(String(80), nullable=True)
|
||||
dynamic_portgroup = Column(Boolean, nullable=True, default=False)
|
||||
|
||||
_node_uuid = orm.relationship(
|
||||
"Node",
|
||||
|
||||
@@ -49,7 +49,8 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# Version 1.13: Add vendor field
|
||||
# Version 1.14: Mark multiple methods as remotable methods.
|
||||
# Version 1.15: Add category field
|
||||
VERSION = '1.15'
|
||||
# Version 1.16: Add available_for_dynamic_portgroup field
|
||||
VERSION = '1.16'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
@@ -72,6 +73,8 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
'description': object_fields.StringField(nullable=True),
|
||||
'vendor': object_fields.StringField(nullable=True),
|
||||
'category': object_fields.StringField(nullable=True),
|
||||
'available_for_dynamic_portgroup': object_fields.BooleanField(
|
||||
default=True),
|
||||
}
|
||||
|
||||
def _convert_field_by_version(self, field_name, introduced_version,
|
||||
@@ -134,6 +137,9 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
Version 1.15: remove category for unsupported versions if
|
||||
remove_unavailable_fields is True.
|
||||
|
||||
Version 1.16: remove available_for_dynamic_portgroup for
|
||||
unsupported versions if remove_unavailable_fields is True.
|
||||
|
||||
:param target_version: the desired version of the object
|
||||
:param remove_unavailable_fields: True to remove fields that are
|
||||
unavailable in the target version; set this to True when
|
||||
@@ -171,6 +177,10 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# Convert the category field.
|
||||
self._convert_field_by_version('category', (1, 15), target_version,
|
||||
remove_unavailable_fields)
|
||||
# Convert the available_for_dynamic_portgroup field.
|
||||
self._convert_field_by_version(
|
||||
'available_for_dynamic_portgroup', (1, 16), target_version,
|
||||
remove_unavailable_fields, True)
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
def get(cls, context, port_id):
|
||||
@@ -504,7 +514,8 @@ class PortCRUDPayload(notification.NotificationPayloadBase):
|
||||
# Version 1.5: Add "description" field
|
||||
# Version 1.6: Add "vendor" field
|
||||
# Version 1.7: Add "category" field
|
||||
VERSION = '1.7'
|
||||
# Version 1.8: Add "available_for_dynamic_portgroup" field
|
||||
VERSION = '1.8'
|
||||
|
||||
SCHEMA = {
|
||||
'address': ('port', 'address'),
|
||||
@@ -520,6 +531,8 @@ class PortCRUDPayload(notification.NotificationPayloadBase):
|
||||
'description': ('port', 'description'),
|
||||
'vendor': ('port', 'vendor'),
|
||||
'category': ('port', 'category'),
|
||||
'available_for_dynamic_portgroup': (
|
||||
'port', 'available_for_dynamic_portgroup'),
|
||||
}
|
||||
|
||||
fields = {
|
||||
@@ -540,6 +553,8 @@ class PortCRUDPayload(notification.NotificationPayloadBase):
|
||||
'description': object_fields.StringField(nullable=True),
|
||||
'vendor': object_fields.StringField(nullable=True),
|
||||
'category': object_fields.StringField(nullable=True),
|
||||
'available_for_dynamic_portgroup': object_fields.BooleanField(
|
||||
default=True),
|
||||
}
|
||||
|
||||
def __init__(self, port, node_uuid, portgroup_uuid):
|
||||
|
||||
@@ -41,7 +41,8 @@ class Portgroup(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# Version 1.6: Relevant methods changed to be remotable methods.
|
||||
# Version 1.7: Add physical_network field
|
||||
# Version 1.8: Add category field
|
||||
VERSION = '1.8'
|
||||
# Version 1.9: Add dynamic_portgroup field
|
||||
VERSION = '1.9'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
@@ -59,6 +60,8 @@ class Portgroup(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
'properties': object_fields.FlexibleDictField(nullable=True),
|
||||
'physical_network': object_fields.StringField(nullable=True),
|
||||
'category': object_fields.StringField(nullable=True),
|
||||
'dynamic_portgroup': object_fields.BooleanField(
|
||||
nullable=True, default=False),
|
||||
}
|
||||
|
||||
# TODO(clif): Abstract this, already exists in Port object.
|
||||
@@ -112,6 +115,9 @@ class Portgroup(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
Version 1.8: remove category for unsupported versions if
|
||||
remove_unavailable_fields is True.
|
||||
|
||||
Version 1.9: remove dynamic_portgroup for unsupported versions
|
||||
if remove_unavailable_fields is True.
|
||||
|
||||
:param target_version: the desired version of the object
|
||||
:param remove_unavailable_fields: True to remove fields that are
|
||||
unavailable in the target version; set this to True when
|
||||
@@ -142,6 +148,10 @@ class Portgroup(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# Convert the category field.
|
||||
self._convert_field_by_version('category', (1, 8), target_version,
|
||||
remove_unavailable_fields)
|
||||
# Convert the dynamic_portgroup field.
|
||||
self._convert_field_by_version(
|
||||
'dynamic_portgroup', (1, 9), target_version,
|
||||
remove_unavailable_fields, False)
|
||||
|
||||
@object_base.remotable_classmethod
|
||||
def get(cls, context, portgroup_ident):
|
||||
@@ -418,7 +428,8 @@ class PortgroupCRUDPayload(notification.NotificationPayloadBase):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: Add physical_network field
|
||||
# Version 1.2: Add category field
|
||||
VERSION = '1.2'
|
||||
# Version 1.3: Add dynamic_portgroup field
|
||||
VERSION = '1.3'
|
||||
|
||||
SCHEMA = {
|
||||
'address': ('portgroup', 'address'),
|
||||
@@ -432,7 +443,8 @@ class PortgroupCRUDPayload(notification.NotificationPayloadBase):
|
||||
'standalone_ports_supported'),
|
||||
'created_at': ('portgroup', 'created_at'),
|
||||
'updated_at': ('portgroup', 'updated_at'),
|
||||
'uuid': ('portgroup', 'uuid')
|
||||
'uuid': ('portgroup', 'uuid'),
|
||||
'dynamic_portgroup': ('portgroup', 'dynamic_portgroup'),
|
||||
}
|
||||
|
||||
fields = {
|
||||
@@ -448,7 +460,9 @@ class PortgroupCRUDPayload(notification.NotificationPayloadBase):
|
||||
nullable=True),
|
||||
'created_at': object_fields.DateTimeField(nullable=True),
|
||||
'updated_at': object_fields.DateTimeField(nullable=True),
|
||||
'uuid': object_fields.UUIDField()
|
||||
'uuid': object_fields.UUIDField(),
|
||||
'dynamic_portgroup': object_fields.BooleanField(
|
||||
nullable=True, default=False),
|
||||
}
|
||||
|
||||
def __init__(self, portgroup, node_uuid):
|
||||
|
||||
@@ -2025,6 +2025,7 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
pdict.pop('description')
|
||||
pdict.pop('vendor')
|
||||
pdict.pop('category')
|
||||
pdict.pop('available_for_dynamic_portgroup')
|
||||
headers = {api_base.Version.string: str(api_v1.min_version())}
|
||||
response = self.post_json('/ports', pdict, headers=headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
||||
@@ -1141,6 +1141,21 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
self.assertTrue(response.json['error_message'])
|
||||
self.assertFalse(mock_upd.called)
|
||||
|
||||
def test_update_portgroup_dynamic_portgroup_not_allowed(
|
||||
self, mock_upd):
|
||||
mock_upd.return_value = self.portgroup
|
||||
response = self.patch_json(
|
||||
'/portgroups/%s' % self.portgroup.uuid,
|
||||
[{'path': '/dynamic_portgroup',
|
||||
'value': True,
|
||||
'op': 'replace'}],
|
||||
expect_errors=True,
|
||||
headers=self.headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||
self.assertTrue(response.json['error_message'])
|
||||
self.assertFalse(mock_upd.called)
|
||||
|
||||
def test_update_portgroup_mode_properties(self, mock_upd):
|
||||
mock_upd.return_value = self.portgroup
|
||||
mock_upd.return_value.mode = '802.3ad'
|
||||
@@ -1461,6 +1476,16 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertTrue(response.json['error_message'])
|
||||
|
||||
def test_create_portgroup_dynamic_portgroup_not_allowed(self):
|
||||
pdict = apiutils.post_get_test_portgroup()
|
||||
pdict['dynamic_portgroup'] = True
|
||||
response = self.post_json('/portgroups', pdict,
|
||||
expect_errors=True,
|
||||
headers=self.headers)
|
||||
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertTrue(response.json['error_message'])
|
||||
|
||||
def test_create_portgroup_mode_old_api_version(self):
|
||||
for kwarg in [{'mode': '802.3ad'}, {'properties': {'bond_prop': 123}}]:
|
||||
pdict = apiutils.post_get_test_portgroup(**kwarg)
|
||||
|
||||
@@ -292,7 +292,9 @@ def get_test_port(**kw):
|
||||
'name': kw.get('name'),
|
||||
'description': kw.get('description'),
|
||||
'vendor': kw.get('vendor'),
|
||||
'category': kw.get('category')
|
||||
'category': kw.get('category'),
|
||||
'available_for_dynamic_portgroup': kw.get(
|
||||
'available_for_dynamic_portgroup', True),
|
||||
}
|
||||
|
||||
|
||||
@@ -470,7 +472,8 @@ def get_test_portgroup(**kw):
|
||||
'mode': kw.get('mode'),
|
||||
'properties': kw.get('properties', {}),
|
||||
'physical_network': kw.get('physical_network'),
|
||||
'category': kw.get('category')
|
||||
'category': kw.get('category'),
|
||||
'dynamic_portgroup': kw.get('dynamic_portgroup', False),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -678,8 +678,8 @@ expected_object_fingerprints = {
|
||||
'Node': '1.44-c9cd729566ab4c6fd69aaa4eaa1386bf',
|
||||
'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6',
|
||||
'Chassis': '1.4-fe427272d8bad232a8d46e996a5ca42a',
|
||||
'Port': '1.15-013610c0fe2e370b14f4304e0d8aeb3a',
|
||||
'Portgroup': '1.8-c931f147ebe450b7c91cc51acfc59472',
|
||||
'Port': '1.16-f66d781ac2c9e2906531cc523be56141',
|
||||
'Portgroup': '1.9-0c02f589c88ef5a8f60248454783fbbc',
|
||||
'Conductor': '1.6-ed00540fae97aa1c9982f9017c6e8b68',
|
||||
'EventType': '1.1-aa2ba1afd38553e3880c267404e8d370',
|
||||
'NotificationPublisher': '1.0-51a09397d6c0687771fb5be9a999605d',
|
||||
@@ -699,11 +699,11 @@ expected_object_fingerprints = {
|
||||
'NodeCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'NodeCRUDPayload': '1.15-9168946f843edd5859464aaa40ad70e0',
|
||||
'PortCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'PortCRUDPayload': '1.7-aaefef8ba3a94030753c1e3b9a29741b',
|
||||
'PortCRUDPayload': '1.8-e6234f3aafda0dae874feb6f9ec8715f',
|
||||
'NodeMaintenanceNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'NodeConsoleNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'PortgroupCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'PortgroupCRUDPayload': '1.2-ea41e2dfcd5817ec4d3f9768e3f26c6e',
|
||||
'PortgroupCRUDPayload': '1.3-0d2b8747f09f71ed84a804aa6888657b',
|
||||
'VolumeConnectorCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
'VolumeConnectorCRUDPayload': '1.0-5e8dbb41e05b6149d8f7bfd4daff9339',
|
||||
'VolumeTargetCRUDNotification': '1.0-59acc533c11d306f149846f922739c15',
|
||||
|
||||
@@ -424,3 +424,60 @@ class TestConvertToVersion(db_base.DbTestCase):
|
||||
port._convert_to_version("1.9", False)
|
||||
self.assertIsNone(port.name)
|
||||
self.assertNotIn('name', port.obj_get_changes())
|
||||
|
||||
def test_available_for_dynamic_portgroup_supported_missing(self):
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
delattr(port, 'available_for_dynamic_portgroup')
|
||||
port.obj_reset_changes()
|
||||
port._convert_to_version("1.16")
|
||||
self.assertTrue(port.available_for_dynamic_portgroup)
|
||||
self.assertIn('available_for_dynamic_portgroup',
|
||||
port.obj_get_changes())
|
||||
|
||||
def test_available_for_dynamic_portgroup_supported_set(self):
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port.available_for_dynamic_portgroup = False
|
||||
port.obj_reset_changes()
|
||||
port._convert_to_version("1.16")
|
||||
self.assertFalse(port.available_for_dynamic_portgroup)
|
||||
self.assertNotIn('available_for_dynamic_portgroup',
|
||||
port.obj_get_changes())
|
||||
|
||||
def test_available_for_dynamic_portgroup_unsupported(self):
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port._convert_to_version("1.15")
|
||||
self.assertNotIn('available_for_dynamic_portgroup', port)
|
||||
|
||||
def test_available_for_dynamic_portgroup_unsupported_missing(self):
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
delattr(port, 'available_for_dynamic_portgroup')
|
||||
port.obj_reset_changes()
|
||||
port._convert_to_version("1.15")
|
||||
self.assertNotIn('available_for_dynamic_portgroup', port)
|
||||
|
||||
def test_available_for_dynamic_portgroup_unsupported_set_remove(self):
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port.available_for_dynamic_portgroup = True
|
||||
port.obj_reset_changes()
|
||||
port._convert_to_version("1.15")
|
||||
self.assertNotIn('available_for_dynamic_portgroup', port)
|
||||
|
||||
def test_available_for_dynamic_portgroup_unsupported_no_remove_non_default(
|
||||
self):
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port.available_for_dynamic_portgroup = False
|
||||
port.obj_reset_changes()
|
||||
port._convert_to_version("1.15", False)
|
||||
self.assertTrue(port.available_for_dynamic_portgroup)
|
||||
self.assertIn('available_for_dynamic_portgroup',
|
||||
port.obj_get_changes())
|
||||
|
||||
def test_available_for_dynamic_portgroup_unsupported_no_remove_default(
|
||||
self):
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port.available_for_dynamic_portgroup = True
|
||||
port.obj_reset_changes()
|
||||
port._convert_to_version("1.15", False)
|
||||
self.assertTrue(port.available_for_dynamic_portgroup)
|
||||
self.assertNotIn('available_for_dynamic_portgroup',
|
||||
port.obj_get_changes())
|
||||
|
||||
@@ -245,3 +245,54 @@ class TestConvertToVersion(db_base.DbTestCase):
|
||||
portgroup._convert_to_version('1.4', False)
|
||||
# no change
|
||||
self.assertEqual(vif2, portgroup.internal_info['tenant_vif_port_id'])
|
||||
|
||||
def test_dynamic_portgroup_supported_missing(self):
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
delattr(portgroup, 'dynamic_portgroup')
|
||||
portgroup.obj_reset_changes()
|
||||
portgroup._convert_to_version("1.9")
|
||||
self.assertFalse(portgroup.dynamic_portgroup)
|
||||
self.assertIn('dynamic_portgroup', portgroup.obj_get_changes())
|
||||
|
||||
def test_dynamic_portgroup_supported_set(self):
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup.dynamic_portgroup = True
|
||||
portgroup.obj_reset_changes()
|
||||
portgroup._convert_to_version("1.9")
|
||||
self.assertTrue(portgroup.dynamic_portgroup)
|
||||
self.assertNotIn('dynamic_portgroup', portgroup.obj_get_changes())
|
||||
|
||||
def test_dynamic_portgroup_unsupported(self):
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup._convert_to_version("1.8")
|
||||
self.assertNotIn('dynamic_portgroup', portgroup)
|
||||
|
||||
def test_dynamic_portgroup_unsupported_missing(self):
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
delattr(portgroup, 'dynamic_portgroup')
|
||||
portgroup.obj_reset_changes()
|
||||
portgroup._convert_to_version("1.8")
|
||||
self.assertNotIn('dynamic_portgroup', portgroup)
|
||||
|
||||
def test_dynamic_portgroup_unsupported_set_remove(self):
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup.dynamic_portgroup = False
|
||||
portgroup.obj_reset_changes()
|
||||
portgroup._convert_to_version("1.8")
|
||||
self.assertNotIn('dynamic_portgroup', portgroup)
|
||||
|
||||
def test_dynamic_portgroup_unsupported_no_remove_non_default(self):
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup.dynamic_portgroup = True
|
||||
portgroup.obj_reset_changes()
|
||||
portgroup._convert_to_version("1.8", False)
|
||||
self.assertFalse(portgroup.dynamic_portgroup)
|
||||
self.assertIn('dynamic_portgroup', portgroup.obj_get_changes())
|
||||
|
||||
def test_dynamic_portgroup_unsupported_no_remove_default(self):
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup.dynamic_portgroup = False
|
||||
portgroup.obj_reset_changes()
|
||||
portgroup._convert_to_version("1.8", False)
|
||||
self.assertFalse(portgroup.dynamic_portgroup)
|
||||
self.assertNotIn('dynamic_portgroup', portgroup.obj_get_changes())
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Adds two new fields for trait-based networking in REST API version
|
||||
*1.111*:
|
||||
|
||||
* ``Port.available_for_dynamic_portgroup``: a boolean field (default
|
||||
True) indicating whether the port should be considered for dynamic
|
||||
portgroup assignment. This field is operator-editable via POST and
|
||||
PATCH.
|
||||
* ``Portgroup.dynamic_portgroup``: a read-only boolean field (default
|
||||
False) that indicates whether the portgroup was dynamically created
|
||||
by the trait-based networking subsystem. This field is managed
|
||||
internally by Ironic and cannot be modified by API consumers.
|
||||
upgrade:
|
||||
- |
|
||||
Adds ``available_for_dynamic_portgroup`` and ``dynamic_portgroup``
|
||||
fields to the port and portgroup objects respectively in REST API
|
||||
version *1.111*. Upgrading to this release will set
|
||||
``available_for_dynamic_portgroup`` to True for all existing ports
|
||||
and ``dynamic_portgroup`` to False for all existing portgroups.
|
||||
Reference in New Issue
Block a user