Copy port[group] VIF info from extra to internal_info
For API versions >= 1.28, Port & portgroup's .extra['vif_port_id'] was deprecated in Ocata. Before we can remove support for this, we need to copy that information to the object's internal_info['tenant_vif_port_id']. This copy/migration is done at the API layer when the user specifies the .extra[] value, as well as when the 'ironic db-sync online_data-migrations' is run. In order to know whether the ports and port groups have been migrated, their IronicObject versions are incremented. This also fixes it so that for API versions < 1.28, the deprecation warning is not shown, since we still need to support extra['vif_port_id'] in this case. When a port or portgroup's .extra['vif_port_id'] is removed via a PATCH API request, that VIF is removed from that object's internal_info. Change-Id: I69468c935e68dd9d37a474c318c3ceb9cdfc5868 Partial-Bug: 1722850
This commit is contained in:
parent
5816e50766
commit
37b85b6a39
@ -34,7 +34,6 @@ from ironic.api import expose
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import policy
|
||||
from ironic.common import utils as common_utils
|
||||
from ironic import objects
|
||||
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
@ -563,10 +562,7 @@ class PortsController(rest.RestController):
|
||||
# request.
|
||||
raise exception.NotAcceptable()
|
||||
|
||||
extra = pdict.get('extra')
|
||||
vif = extra.get('vif_port_id') if extra else None
|
||||
if vif:
|
||||
common_utils.warn_about_deprecated_extra_vif_port_id()
|
||||
vif = api_utils.handle_post_port_like_extra_vif(pdict)
|
||||
|
||||
if (pdict.get('portgroup_uuid') and
|
||||
(pdict.get('pxe_enabled') or vif)):
|
||||
@ -654,6 +650,8 @@ class PortsController(rest.RestController):
|
||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
||||
raise exception.PatchError(patch=patch, reason=e)
|
||||
|
||||
api_utils.handle_patch_port_like_extra_vif(rpc_port, port, patch)
|
||||
|
||||
if api_utils.is_path_removed(patch, '/portgroup_uuid'):
|
||||
rpc_port.portgroup_id = None
|
||||
|
||||
|
@ -30,7 +30,6 @@ from ironic.api import expose
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.common import policy
|
||||
from ironic.common import utils as common_utils
|
||||
from ironic import objects
|
||||
|
||||
METRICS = metrics_utils.get_metrics_logger(__name__)
|
||||
@ -456,9 +455,8 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
error_msg, status_code=http_client.BAD_REQUEST)
|
||||
|
||||
pg_dict = portgroup.as_dict()
|
||||
vif = pg_dict.get('extra', {}).get('vif_port_id')
|
||||
if vif:
|
||||
common_utils.warn_about_deprecated_extra_vif_port_id()
|
||||
|
||||
api_utils.handle_post_port_like_extra_vif(pg_dict)
|
||||
|
||||
# NOTE(yuriyz): UUID is mandatory for notifications payload
|
||||
if not pg_dict.get('uuid'):
|
||||
@ -529,6 +527,9 @@ class PortgroupsController(pecan.rest.RestController):
|
||||
except api_utils.JSONPATCH_EXCEPTIONS as e:
|
||||
raise exception.PatchError(patch=patch, reason=e)
|
||||
|
||||
api_utils.handle_patch_port_like_extra_vif(rpc_portgroup, portgroup,
|
||||
patch)
|
||||
|
||||
# Update only the fields that have changed
|
||||
for field in objects.Portgroup.fields:
|
||||
try:
|
||||
|
@ -719,3 +719,86 @@ def allow_traits():
|
||||
Version 1.37 of the API allows traits for the node.
|
||||
"""
|
||||
return pecan.request.version.minor >= versions.MINOR_37_NODE_TRAITS
|
||||
|
||||
|
||||
def handle_post_port_like_extra_vif(p_dict):
|
||||
"""Handle a Post request that sets .extra['vif_port_id'].
|
||||
|
||||
This handles attach of VIFs via specifying the VIF port ID
|
||||
in a port or port group's extra['vif_port_id'] field.
|
||||
|
||||
:param p_dict: a dictionary with field names/values for the port or
|
||||
port group
|
||||
:return: VIF or None
|
||||
"""
|
||||
vif = p_dict.get('extra', {}).get('vif_port_id')
|
||||
if vif:
|
||||
# TODO(rloo): in Stein cycle: if API version >= 1.28, remove
|
||||
# warning and support for extra[]; else (< 1.28)
|
||||
# still support it; continue copying to internal_info
|
||||
# (see bug 1722850). i.e., change the 7 lines of code
|
||||
# below to something like:
|
||||
# if not api_utils.allow_vifs_subcontroller():
|
||||
# internal_info = {'tenant_vif_port_id': vif}
|
||||
# pg_dict['internal_info'] = internal_info
|
||||
if allow_vifs_subcontroller():
|
||||
utils.warn_about_deprecated_extra_vif_port_id()
|
||||
# NOTE(rloo): this value should really be in .internal_info[..]
|
||||
# which is what would happen if they had used the
|
||||
# POST /v1/nodes/<node>/vifs API.
|
||||
internal_info = {'tenant_vif_port_id': vif}
|
||||
p_dict['internal_info'] = internal_info
|
||||
return vif
|
||||
|
||||
|
||||
def handle_patch_port_like_extra_vif(rpc_object, api_object, patch):
|
||||
"""Handle a Patch request that modifies .extra['vif_port_id'].
|
||||
|
||||
This handles attach/detach of VIFs via the VIF port ID
|
||||
in a port or port group's extra['vif_port_id'] field.
|
||||
|
||||
:param rpc_object: a Port or Portgroup RPC object
|
||||
:param api_object: the corresponding Port or Portgroup API object
|
||||
:param patch: the JSON patch in the API request
|
||||
"""
|
||||
vif_list = get_patch_values(patch, '/extra/vif_port_id')
|
||||
vif = None
|
||||
if vif_list:
|
||||
# if specified more than once, use the last value
|
||||
vif = vif_list[-1]
|
||||
|
||||
# TODO(rloo): in Stein cycle: if API version >= 1.28, remove this
|
||||
# warning and don't copy to internal_info; else (<1.28) still
|
||||
# support it; continue copying to internal_info (see bug 1722850).
|
||||
# i.e., change the 8 lines of code below to something like:
|
||||
# if not allow_vifs_subcontroller():
|
||||
# int_info = rpc_object.internal_info.get('tenant_vif_port_id')
|
||||
# if (not int_info or
|
||||
# int_info == rpc_object.extra.get('vif_port_id')):
|
||||
# api_object.internal_info['tenant_vif_port_id'] = vif
|
||||
if allow_vifs_subcontroller():
|
||||
utils.warn_about_deprecated_extra_vif_port_id()
|
||||
# NOTE(rloo): if the user isn't also using the REST API
|
||||
# 'POST nodes/<node>/vifs', we are safe to copy the
|
||||
# .extra[] value to the .internal_info location
|
||||
int_info = rpc_object.internal_info.get('tenant_vif_port_id')
|
||||
if (not int_info or int_info == rpc_object.extra.get('vif_port_id')):
|
||||
api_object.internal_info['tenant_vif_port_id'] = vif
|
||||
|
||||
elif is_path_removed(patch, '/extra/vif_port_id'):
|
||||
# TODO(rloo): in Stein cycle: if API version >= 1.28, remove this
|
||||
# warning and don't remove from internal_info; else (<1.28) still
|
||||
# support it; remove from internal_info (see bug 1722850).
|
||||
# i.e., change the 8 lines of code below to something like:
|
||||
# if not allow_vifs_subcontroller():
|
||||
# int_info = rpc_object.internal_info.get('tenant_vif...')
|
||||
# if (int_info and int_info==rpc_object.extra.get('vif_port_id')):
|
||||
# api_object.internal_info['tenant_vif_port_id'] = None
|
||||
if allow_vifs_subcontroller():
|
||||
utils.warn_about_deprecated_extra_vif_port_id()
|
||||
# NOTE(rloo): if the user isn't also using the REST API
|
||||
# 'POST nodes/<node>/vifs', we are safe to remove the
|
||||
# .extra[] value from the .internal_info location
|
||||
int_info = rpc_object.internal_info.get('tenant_vif_port_id')
|
||||
if (int_info and int_info == rpc_object.extra.get('vif_port_id')):
|
||||
api_object.internal_info.pop('tenant_vif_port_id')
|
||||
|
@ -29,6 +29,8 @@ from ironic.common import service
|
||||
from ironic.conf import CONF
|
||||
from ironic.db import api as db_api
|
||||
from ironic.db import migration
|
||||
from ironic.objects import port
|
||||
from ironic.objects import portgroup
|
||||
from ironic import version
|
||||
|
||||
|
||||
@ -64,6 +66,12 @@ dbapi = db_api.get_instance()
|
||||
ONLINE_MIGRATIONS = (
|
||||
# TODO(dtantsur): remove when classic drivers are removed (Rocky?)
|
||||
(dbapi, 'migrate_to_hardware_types'),
|
||||
# Added in Rocky
|
||||
# TODO(rloo): remove in Stein
|
||||
(port, 'migrate_vif_port_id'),
|
||||
# Added in Rocky
|
||||
# TODO(rloo): remove in Stein
|
||||
(portgroup, 'migrate_vif_port_id'),
|
||||
)
|
||||
|
||||
|
||||
|
@ -106,8 +106,8 @@ RELEASE_MAPPING = {
|
||||
'Node': ['1.23'],
|
||||
'Conductor': ['1.2'],
|
||||
'Chassis': ['1.3'],
|
||||
'Port': ['1.7'],
|
||||
'Portgroup': ['1.3'],
|
||||
'Port': ['1.8'],
|
||||
'Portgroup': ['1.4'],
|
||||
'Trait': ['1.0'],
|
||||
'TraitList': ['1.0'],
|
||||
'VolumeConnector': ['1.0'],
|
||||
|
@ -504,7 +504,8 @@ def warn_about_deprecated_extra_vif_port_id():
|
||||
global warn_deprecated_extra_vif_port_id
|
||||
if not warn_deprecated_extra_vif_port_id:
|
||||
warn_deprecated_extra_vif_port_id = True
|
||||
LOG.warning("Attaching VIF to a port/portgroup via "
|
||||
"extra['vif_port_id'] is deprecated and will not "
|
||||
"be supported in Pike release. API endpoint "
|
||||
"v1/nodes/<node>/vifs should be used instead.")
|
||||
LOG.warning("Starting with API version 1.28, attaching/detaching VIF "
|
||||
"to/from a port or port group via extra['vif_port_id'] is "
|
||||
"deprecated and will not be supported starting in the "
|
||||
"Stein release. API endpoint v1/nodes/<node>/vifs should "
|
||||
"be used instead.")
|
||||
|
@ -887,6 +887,16 @@ class Connection(object):
|
||||
ident does not exist.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_not_versions(self, model_name, versions):
|
||||
"""Returns objects with versions that are not the specified versions.
|
||||
|
||||
:param model_name: the name of the model (class) of desired objects
|
||||
:param versions: list of versions of objects not to be returned
|
||||
:returns: list of the DB objects
|
||||
:raises: IronicException if there is no class associated with the name
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def check_versions(self):
|
||||
"""Checks the whole database for incompatible objects.
|
||||
|
@ -1168,6 +1168,29 @@ class Connection(api.Connection):
|
||||
if count == 0:
|
||||
raise exception.VolumeTargetNotFound(target=ident)
|
||||
|
||||
def get_not_versions(self, model_name, versions):
|
||||
"""Returns objects with versions that are not the specified versions.
|
||||
|
||||
This returns objects with versions that are not the specified versions.
|
||||
Objects with null versions (there shouldn't be any) are also returned.
|
||||
|
||||
:param model_name: the name of the model (class) of desired objects
|
||||
:param versions: list of versions of objects not to be returned
|
||||
:returns: list of the DB objects
|
||||
:raises: IronicException if there is no class associated with the name
|
||||
"""
|
||||
if not versions:
|
||||
return []
|
||||
|
||||
model = models.get_class(model_name)
|
||||
|
||||
# NOTE(rloo): .notin_ does not handle null:
|
||||
# http://docs.sqlalchemy.org/en/latest/core/sqlelement.html#sqlalchemy.sql.operators.ColumnOperators.notin_
|
||||
query = model_query(model).filter(
|
||||
sql.or_(model.version == sql.null(),
|
||||
model.version.notin_(versions)))
|
||||
return query.all()
|
||||
|
||||
def check_versions(self):
|
||||
"""Checks the whole database for incompatible objects.
|
||||
|
||||
|
@ -30,6 +30,8 @@ from sqlalchemy import schema, String, Text
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from sqlalchemy import orm
|
||||
|
||||
from ironic.common import exception
|
||||
from ironic.common.i18n import _
|
||||
from ironic.conf import CONF
|
||||
|
||||
_DEFAULT_SQL_CONNECTION = 'sqlite:///' + path.join('$state_path',
|
||||
@ -296,3 +298,18 @@ class NodeTrait(Base):
|
||||
primaryjoin='and_(NodeTrait.node_id == Node.id)',
|
||||
foreign_keys=node_id
|
||||
)
|
||||
|
||||
|
||||
def get_class(model_name):
|
||||
"""Returns the model class with the specified name.
|
||||
|
||||
:param model_name: the name of the class
|
||||
:returns: the class with the specified name
|
||||
:raises: Exception if there is no class associated with the name
|
||||
"""
|
||||
for model in Base.__subclasses__():
|
||||
if model.__name__ == model_name:
|
||||
return model
|
||||
|
||||
raise exception.IronicException(
|
||||
_("Cannot find model with name: %s") % model_name)
|
||||
|
@ -26,7 +26,6 @@ from ironic.common import network
|
||||
from ironic.common import neutron
|
||||
from ironic.common.pxe_utils import DHCP_CLIENT_ID
|
||||
from ironic.common import states
|
||||
from ironic.common import utils
|
||||
from ironic import objects
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -410,10 +409,7 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
|
||||
if 'extra' in port_obj.obj_what_changed():
|
||||
original_port = objects.Port.get_by_id(context, port_obj.id)
|
||||
updated_client_id = port_obj.extra.get('client-id')
|
||||
if (port_obj.extra.get('vif_port_id') and
|
||||
(port_obj.extra['vif_port_id'] !=
|
||||
original_port.extra.get('vif_port_id'))):
|
||||
utils.warn_about_deprecated_extra_vif_port_id()
|
||||
|
||||
if (original_port.extra.get('client-id') !=
|
||||
updated_client_id):
|
||||
# DHCP Option with opt_value=None will remove it
|
||||
@ -462,7 +458,6 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
|
||||
:raises: FailedToUpdateDHCPOptOnPort, Conflict
|
||||
"""
|
||||
|
||||
context = task.context
|
||||
portgroup_uuid = portgroup_obj.uuid
|
||||
# NOTE(vsaienko) address is not mandatory field in portgroup.
|
||||
# Do not touch neutron port if we removed address on portgroup.
|
||||
@ -473,14 +468,6 @@ class NeutronVIFPortIDMixin(VIFPortIDMixin):
|
||||
neutron.update_port_address(pg_vif, portgroup_obj.address,
|
||||
context=task.context)
|
||||
|
||||
if 'extra' in portgroup_obj.obj_what_changed():
|
||||
original_portgroup = objects.Portgroup.get_by_id(context,
|
||||
portgroup_obj.id)
|
||||
if (portgroup_obj.extra.get('vif_port_id') and
|
||||
portgroup_obj.extra['vif_port_id'] !=
|
||||
original_portgroup.extra.get('vif_port_id')):
|
||||
utils.warn_about_deprecated_extra_vif_port_id()
|
||||
|
||||
if ('standalone_ports_supported' in
|
||||
portgroup_obj.obj_what_changed()):
|
||||
if not portgroup_obj.standalone_ports_supported:
|
||||
|
@ -26,6 +26,36 @@ from ironic.objects import fields as object_fields
|
||||
from ironic.objects import notification
|
||||
|
||||
|
||||
def migrate_vif_port_id(context, max_count):
|
||||
"""Copy port's VIF info from extra to internal_info.
|
||||
|
||||
:param context: The context.
|
||||
:param max_count: The maximum number of objects to migrate. Must be
|
||||
>= 0. If zero, all the objects will be migrated.
|
||||
:returns: A 2-tuple -- the total number of objects that need to be
|
||||
migrated (at the beginning of this call) and the number
|
||||
of migrated objects.
|
||||
"""
|
||||
# TODO(rloo): remove this method in Stein, when we remove from dbsync.py
|
||||
|
||||
# NOTE(rloo): if we introduce newer port versions in the same cycle,
|
||||
# we could add those versions along with 1.8. This is only so we don't
|
||||
# duplicate work; it isn't necessary.
|
||||
db_ports = Port.dbapi.get_not_versions('Port', ['1.8'])
|
||||
total = len(db_ports)
|
||||
max_count = max_count or total
|
||||
done = 0
|
||||
for db_port in db_ports:
|
||||
# NOTE(rloo): this will indirectly invoke Port._convert_to_version()
|
||||
# which does all the real work
|
||||
port = Port._from_db_object(context, Port(), db_port)
|
||||
port.save()
|
||||
done += 1
|
||||
if done == max_count:
|
||||
break
|
||||
return total, done
|
||||
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
@ -38,7 +68,10 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# local_link_connection, portgroup_id and pxe_enabled
|
||||
# Version 1.6: Add internal_info field
|
||||
# Version 1.7: Add physical_network field
|
||||
VERSION = '1.7'
|
||||
# Version 1.8: Migrate/copy extra['vif_port_id'] to
|
||||
# internal_info['tenant_vif_port_id'] (not an explicit db
|
||||
# change)
|
||||
VERSION = '1.8'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
@ -68,6 +101,11 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
None. For versions prior to this, it should be set to None (or
|
||||
removed).
|
||||
|
||||
Version 1.8: if extra['vif_port_id'] is specified (non-null) and
|
||||
internal_info['tenant_vif_port_id'] is not specified, copy the
|
||||
.extra value to internal_info. There is nothing to do here when
|
||||
downgrading to an older version.
|
||||
|
||||
: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
|
||||
@ -75,6 +113,17 @@ class Port(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
values; set this to False for DB interactions.
|
||||
"""
|
||||
target_version = versionutils.convert_version_to_tuple(target_version)
|
||||
if target_version >= (1, 8):
|
||||
if self.obj_attr_is_set('extra'):
|
||||
vif = self.extra.get('vif_port_id')
|
||||
if vif:
|
||||
internal_info = (self.internal_info
|
||||
if self.obj_attr_is_set('internal_info')
|
||||
else {})
|
||||
if 'tenant_vif_port_id' not in internal_info:
|
||||
internal_info['tenant_vif_port_id'] = vif
|
||||
self.internal_info = internal_info
|
||||
|
||||
# Convert the physical_network field.
|
||||
physnet_is_set = self.obj_attr_is_set('physical_network')
|
||||
if target_version >= (1, 7):
|
||||
|
@ -16,6 +16,7 @@
|
||||
from oslo_utils import netutils
|
||||
from oslo_utils import strutils
|
||||
from oslo_utils import uuidutils
|
||||
from oslo_utils import versionutils
|
||||
from oslo_versionedobjects import base as object_base
|
||||
|
||||
from ironic.common import exception
|
||||
@ -26,13 +27,47 @@ from ironic.objects import fields as object_fields
|
||||
from ironic.objects import notification
|
||||
|
||||
|
||||
def migrate_vif_port_id(context, max_count):
|
||||
"""Copy portgroup's VIF info from extra to internal_info.
|
||||
|
||||
:param context: The context.
|
||||
:param max_count: The maximum number of objects to migrate. Must be
|
||||
>= 0. If zero, all the objects will be migrated.
|
||||
:returns: A 2-tuple -- the total number of objects that need to be
|
||||
migrated (at the beginning of this call) and the number
|
||||
of migrated objects.
|
||||
"""
|
||||
# TODO(rloo): remove this method in Stein, when we remove from dbsync.py
|
||||
|
||||
# NOTE(rloo): if we introduce newer portgroup versions in the same cycle,
|
||||
# we could add those versions along with 1.4. This is only so we don't
|
||||
# duplicate work; it isn't necessary.
|
||||
db_objs = Portgroup.dbapi.get_not_versions('Portgroup', ['1.4'])
|
||||
total = len(db_objs)
|
||||
max_count = max_count or total
|
||||
done = 0
|
||||
for db_obj in db_objs:
|
||||
# NOTE(rloo): this will indirectly invoke
|
||||
# Portgroup._convert_to_version() which does all the real
|
||||
# work
|
||||
portgroup = Portgroup._from_db_object(context, Portgroup(), db_obj)
|
||||
portgroup.save()
|
||||
done += 1
|
||||
if done == max_count:
|
||||
break
|
||||
return total, done
|
||||
|
||||
|
||||
@base.IronicObjectRegistry.register
|
||||
class Portgroup(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
# Version 1.0: Initial version
|
||||
# Version 1.1: Add internal_info field
|
||||
# Version 1.2: Add standalone_ports_supported field
|
||||
# Version 1.3: Add mode and properties fields
|
||||
VERSION = '1.3'
|
||||
# Version 1.4: Migrate/copy extra['vif_port_id'] to
|
||||
# internal_info['tenant_vif_port_id'] (not an explicit db
|
||||
# change)
|
||||
VERSION = '1.4'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
@ -49,6 +84,37 @@ class Portgroup(base.IronicObject, object_base.VersionedObjectDictCompat):
|
||||
'properties': object_fields.FlexibleDictField(nullable=True),
|
||||
}
|
||||
|
||||
def _convert_to_version(self, target_version,
|
||||
remove_unavailable_fields=True):
|
||||
"""Convert to the target version.
|
||||
|
||||
Convert the object to the target version. The target version may be
|
||||
the same, older, or newer than the version of the object. This is
|
||||
used for DB interactions as well as for serialization/deserialization.
|
||||
|
||||
Version 1.4: if extra['vif_port_id'] is specified (non-null) and
|
||||
internal_info['tenant_vif_port_id'] is not specified, copy the
|
||||
.extra value to internal_info. There is nothing to do here when
|
||||
downgrading to an older version.
|
||||
|
||||
: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
|
||||
(de)serializing. False to set the unavailable fields to appropriate
|
||||
values; set this to False for DB interactions.
|
||||
"""
|
||||
target_version = versionutils.convert_version_to_tuple(target_version)
|
||||
if target_version >= (1, 4):
|
||||
if self.obj_attr_is_set('extra'):
|
||||
vif = self.extra.get('vif_port_id')
|
||||
if vif:
|
||||
internal_info = (self.internal_info
|
||||
if self.obj_attr_is_set('internal_info')
|
||||
else {})
|
||||
if 'tenant_vif_port_id' not in internal_info:
|
||||
internal_info['tenant_vif_port_id'] = vif
|
||||
self.internal_info = internal_info
|
||||
|
||||
# NOTE(xek): 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.
|
||||
|
@ -1355,6 +1355,155 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
self.assertEqual(http_client.FORBIDDEN, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
|
||||
def _test_add_extra_vif_port_id(self, port, headers, mock_warn, mock_upd):
|
||||
response = self.patch_json(
|
||||
'/ports/%s' % port.uuid,
|
||||
[{'path': '/extra/vif_port_id', 'value': 'foo', 'op': 'add'},
|
||||
{'path': '/extra/vif_port_id', 'value': 'bar', 'op': 'add'}],
|
||||
headers=headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.OK, response.status_code)
|
||||
self.assertEqual({'vif_port_id': 'bar'},
|
||||
response.json['extra'])
|
||||
self.assertTrue(mock_upd.called)
|
||||
return response
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_add_extra_vif_port_id(self, mock_warn, mock_upd):
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:55:00:cf:2d:31')
|
||||
expected_intern_info = port.internal_info
|
||||
expected_intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_add_extra_vif_port_id(port, headers,
|
||||
mock_warn, mock_upd)
|
||||
self.assertEqual(expected_intern_info, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_add_extra_vif_port_id_no_internal(self, mock_warn, mock_upd):
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:55:00:cf:2d:31')
|
||||
expected_intern_info = port.internal_info
|
||||
expected_intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_add_extra_vif_port_id(port, headers,
|
||||
mock_warn, mock_upd)
|
||||
self.assertEqual(expected_intern_info, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_add_extra_vif_port_id_deprecated(self, mock_warn, mock_upd):
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:55:00:cf:2d:31')
|
||||
expected_intern_info = port.internal_info
|
||||
expected_intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
headers = {api_base.Version.string: '1.34'}
|
||||
response = self._test_add_extra_vif_port_id(port, headers,
|
||||
mock_warn, mock_upd)
|
||||
self.assertEqual(expected_intern_info, response.json['internal_info'])
|
||||
self.assertTrue(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_replace_extra_vif_port_id(self, mock_warn, mock_upd):
|
||||
extra = {'vif_port_id': 'original'}
|
||||
internal_info = {'tenant_vif_port_id': 'original'}
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:55:00:cf:2d:31',
|
||||
extra=extra,
|
||||
internal_info=internal_info)
|
||||
expected_intern_info = port.internal_info
|
||||
expected_intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_add_extra_vif_port_id(port, headers,
|
||||
mock_warn, mock_upd)
|
||||
self.assertEqual(expected_intern_info, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_add_extra_vif_port_id_diff_internal(self, mock_warn, mock_upd):
|
||||
internal_info = {'tenant_vif_port_id': 'original'}
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:55:00:cf:2d:31',
|
||||
internal_info=internal_info)
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_add_extra_vif_port_id(port, headers,
|
||||
mock_warn, mock_upd)
|
||||
self.assertEqual(internal_info, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
def _test_remove_extra_vif_port_id(self, port, headers, mock_warn,
|
||||
mock_upd):
|
||||
response = self.patch_json(
|
||||
'/ports/%s' % port.uuid,
|
||||
[{'path': '/extra/vif_port_id', 'op': 'remove'}],
|
||||
headers=headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.OK, response.status_code)
|
||||
self.assertEqual({}, response.json['extra'])
|
||||
self.assertTrue(mock_upd.called)
|
||||
return response
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_remove_extra_vif_port_id(self, mock_warn, mock_upd):
|
||||
internal_info = {'tenant_vif_port_id': 'bar'}
|
||||
extra = {'vif_port_id': 'bar'}
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:55:00:cf:2d:31',
|
||||
internal_info=internal_info,
|
||||
extra=extra)
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_remove_extra_vif_port_id(port, headers,
|
||||
mock_warn, mock_upd)
|
||||
self.assertEqual({}, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_remove_extra_vif_port_id_not_same(self, mock_warn, mock_upd):
|
||||
# .internal_info['tenant_vif_port_id'] != .extra['vif_port_id']
|
||||
internal_info = {'tenant_vif_port_id': 'bar'}
|
||||
extra = {'vif_port_id': 'foo'}
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:55:00:cf:2d:31',
|
||||
internal_info=internal_info,
|
||||
extra=extra)
|
||||
headers = {api_base.Version.string: '1.28'}
|
||||
response = self._test_remove_extra_vif_port_id(port, headers,
|
||||
mock_warn, mock_upd)
|
||||
self.assertEqual(internal_info, response.json['internal_info'])
|
||||
self.assertTrue(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_remove_extra_vif_port_id_not_internal(self, mock_warn, mock_upd):
|
||||
# no .internal_info['tenant_vif_port_id']
|
||||
internal_info = {'foo': 'bar'}
|
||||
extra = {'vif_port_id': 'bar'}
|
||||
port = obj_utils.create_test_port(self.context, node_id=self.node.id,
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:55:00:cf:2d:31',
|
||||
internal_info=internal_info,
|
||||
extra=extra)
|
||||
headers = {api_base.Version.string: '1.28'}
|
||||
response = self._test_remove_extra_vif_port_id(port, headers,
|
||||
mock_warn, mock_upd)
|
||||
self.assertEqual(internal_info, response.json['internal_info'])
|
||||
self.assertTrue(mock_warn.called)
|
||||
|
||||
|
||||
@mock.patch.object(rpcapi.ConductorAPI, 'create_port', autospec=True,
|
||||
side_effect=_rpcapi_create_port)
|
||||
@ -1803,18 +1952,35 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
self.assertEqual(http_client.FORBIDDEN, response.status_int)
|
||||
self.assertFalse(mock_create.called)
|
||||
|
||||
def _test_create_port_with_extra_vif_port_id(self, headers, mock_warn,
|
||||
mock_create):
|
||||
pdict = post_get_test_port(pxe_enabled=False,
|
||||
extra={'vif_port_id': 'foo'})
|
||||
pdict.pop('physical_network')
|
||||
response = self.post_json('/ports', pdict, headers=headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.CREATED, response.status_int)
|
||||
self.assertEqual({'vif_port_id': 'foo'}, response.json['extra'])
|
||||
self.assertEqual({'tenant_vif_port_id': 'foo'},
|
||||
response.json['internal_info'])
|
||||
mock_create.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY,
|
||||
'test-topic')
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_create_port_with_extra_vif_port_id(self, mock_warn, mock_create):
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
self._test_create_port_with_extra_vif_port_id(headers, mock_warn,
|
||||
mock_create)
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_create_port_with_extra_vif_port_id_deprecated(self, mock_warn,
|
||||
mock_create):
|
||||
pdict = post_get_test_port(pxe_enabled=False,
|
||||
extra={'vif_port_id': 'foo'})
|
||||
response = self.post_json('/ports', pdict, headers=self.headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.CREATED, response.status_int)
|
||||
self.assertEqual(1, mock_warn.call_count)
|
||||
mock_create.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY,
|
||||
'test-topic')
|
||||
self._test_create_port_with_extra_vif_port_id(self.headers, mock_warn,
|
||||
mock_create)
|
||||
self.assertTrue(mock_warn.called)
|
||||
|
||||
def _test_create_port(self, mock_create, has_vif=False, in_portgroup=False,
|
||||
pxe_enabled=True, standalone_ports=True,
|
||||
@ -1844,6 +2010,9 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
self.assertEqual(expected_portgroup_uuid,
|
||||
response.json['portgroup_uuid'])
|
||||
self.assertEqual(extra, response.json['extra'])
|
||||
if has_vif:
|
||||
expected = {'tenant_vif_port_id': extra['vif_port_id']}
|
||||
self.assertEqual(expected, response.json['internal_info'])
|
||||
mock_create.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY,
|
||||
'test-topic')
|
||||
else:
|
||||
|
@ -41,6 +41,16 @@ from ironic.tests.unit.api import utils as apiutils
|
||||
from ironic.tests.unit.objects import utils as obj_utils
|
||||
|
||||
|
||||
def _rpcapi_update_portgroup(self, context, portgroup, topic):
|
||||
"""Fake used to mock out the conductor RPCAPI's update_portgroup method.
|
||||
|
||||
Saves the updated portgroup object and returns the updated portgroup
|
||||
as-per the real method.
|
||||
"""
|
||||
portgroup.save()
|
||||
return portgroup
|
||||
|
||||
|
||||
class TestPortgroupObject(base.TestCase):
|
||||
|
||||
def test_portgroup_init(self):
|
||||
@ -965,6 +975,136 @@ class TestPatch(test_api_base.BaseApiTest):
|
||||
self.assertFalse(mock_upd.called)
|
||||
|
||||
|
||||
@mock.patch.object(rpcapi.ConductorAPI, 'update_portgroup', autospec=True,
|
||||
side_effect=_rpcapi_update_portgroup)
|
||||
class TestPatchExtraVifPortId(test_api_base.BaseApiTest):
|
||||
headers = {api_base.Version.string: str(api_v1.max_version())}
|
||||
|
||||
def setUp(self):
|
||||
super(TestPatchExtraVifPortId, self).setUp()
|
||||
self.node = obj_utils.create_test_node(self.context)
|
||||
self.portgroup = obj_utils.create_test_portgroup(self.context,
|
||||
node_id=self.node.id)
|
||||
p = mock.patch.object(rpcapi.ConductorAPI, 'get_topic_for')
|
||||
self.mock_gtf = p.start()
|
||||
self.mock_gtf.return_value = 'test-topic'
|
||||
self.addCleanup(p.stop)
|
||||
|
||||
def _test_add_extra_vif_port_id(self, headers, mock_warn, mock_upd):
|
||||
extra = {'vif_port_id': 'bar'}
|
||||
response = self.patch_json(
|
||||
'/portgroups/%s' % self.portgroup.uuid,
|
||||
[{'path': '/extra/vif_port_id', 'value': 'foo', 'op': 'add'},
|
||||
{'path': '/extra/vif_port_id', 'value': 'bar', 'op': 'add'}],
|
||||
headers=headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.OK, response.status_code)
|
||||
self.assertEqual(extra, response.json['extra'])
|
||||
return response
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_add_extra_vif_port_id(self, mock_warn, mock_upd):
|
||||
expected_intern_info = self.portgroup.internal_info
|
||||
expected_intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_add_extra_vif_port_id(headers, mock_warn,
|
||||
mock_upd)
|
||||
self.assertEqual(expected_intern_info, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_add_extra_vif_port_id_deprecated(self, mock_warn, mock_upd):
|
||||
expected_intern_info = self.portgroup.internal_info
|
||||
expected_intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
response = self._test_add_extra_vif_port_id(self.headers, mock_warn,
|
||||
mock_upd)
|
||||
self.assertEqual(expected_intern_info, response.json['internal_info'])
|
||||
self.assertTrue(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_replace_extra_vif_port_id(self, mock_warn, mock_upd):
|
||||
self.portgroup.extra = {'vif_port_id': 'original'}
|
||||
self.portgroup.internal_info = {'tenant_vif_port_id': 'original'}
|
||||
self.portgroup.save()
|
||||
expected_intern_info = self.portgroup.internal_info
|
||||
expected_intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_add_extra_vif_port_id(headers, mock_warn,
|
||||
mock_upd)
|
||||
self.assertEqual(expected_intern_info, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_add_extra_vif_port_id_diff_internal(self, mock_warn, mock_upd):
|
||||
internal_info = {'tenant_vif_port_id': 'original'}
|
||||
self.portgroup.internal_info = internal_info
|
||||
self.portgroup.save()
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_add_extra_vif_port_id(headers, mock_warn,
|
||||
mock_upd)
|
||||
# not changed
|
||||
self.assertEqual(internal_info, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
def _test_remove_extra_vif_port_id(self, headers, mock_warn, mock_upd):
|
||||
response = self.patch_json(
|
||||
'/portgroups/%s' % self.portgroup.uuid,
|
||||
[{'path': '/extra/vif_port_id', 'op': 'remove'}],
|
||||
headers=headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.OK, response.status_code)
|
||||
self.assertEqual({}, response.json['extra'])
|
||||
self.assertTrue(mock_upd.called)
|
||||
return response
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_remove_extra_vif_port_id(self, mock_warn, mock_upd):
|
||||
self.portgroup.extra = {'vif_port_id': 'bar'}
|
||||
orig_info = self.portgroup.internal_info.copy()
|
||||
intern_info = self.portgroup.internal_info
|
||||
intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
self.portgroup.internal_info = intern_info
|
||||
self.portgroup.save()
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
response = self._test_remove_extra_vif_port_id(headers, mock_warn,
|
||||
mock_upd)
|
||||
self.assertEqual(orig_info, response.json['internal_info'])
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_remove_extra_vif_port_id_not_same(self, mock_warn, mock_upd):
|
||||
# .internal_info['tenant_vif_port_id'] != .extra['vif_port_id']
|
||||
self.portgroup.extra = {'vif_port_id': 'foo'}
|
||||
intern_info = self.portgroup.internal_info
|
||||
intern_info.update({'tenant_vif_port_id': 'bar'})
|
||||
self.portgroup.internal_info = intern_info
|
||||
self.portgroup.save()
|
||||
headers = {api_base.Version.string: '1.28'}
|
||||
response = self._test_remove_extra_vif_port_id(headers, mock_warn,
|
||||
mock_upd)
|
||||
self.assertEqual(intern_info, response.json['internal_info'])
|
||||
self.assertTrue(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_remove_extra_vif_port_id_not_internal(self, mock_warn, mock_upd):
|
||||
# no portgroup.internal_info['tenant_vif_port_id']
|
||||
self.portgroup.extra = {'vif_port_id': 'foo'}
|
||||
self.portgroup.save()
|
||||
intern_info = self.portgroup.internal_info
|
||||
headers = {api_base.Version.string: '1.28'}
|
||||
response = self._test_remove_extra_vif_port_id(headers, mock_warn,
|
||||
mock_upd)
|
||||
self.assertEqual(intern_info, response.json['internal_info'])
|
||||
self.assertTrue(mock_warn.called)
|
||||
|
||||
|
||||
class TestPost(test_api_base.BaseApiTest):
|
||||
headers = {api_base.Version.string: str(api_v1.max_version())}
|
||||
|
||||
@ -1085,15 +1225,30 @@ class TestPost(test_api_base.BaseApiTest):
|
||||
headers=self.headers)
|
||||
self.assertEqual(pdict['extra'], result['extra'])
|
||||
|
||||
def _test_create_portgroup_with_extra_vif_port_id(self, headers,
|
||||
mock_warn):
|
||||
pgdict = apiutils.post_get_test_portgroup(extra={'vif_port_id': 'foo'})
|
||||
response = self.post_json('/portgroups', pgdict, headers=headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.CREATED, response.status_int)
|
||||
self.assertEqual({'vif_port_id': 'foo'}, response.json['extra'])
|
||||
self.assertEqual({'tenant_vif_port_id': 'foo'},
|
||||
response.json['internal_info'])
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_create_portgroup_with_extra_vif_port_id(self, mock_warn):
|
||||
headers = {api_base.Version.string: '1.27'}
|
||||
self._test_create_portgroup_with_extra_vif_port_id(headers, mock_warn)
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
def test_create_portgroup_with_extra_vif_port_id_deprecated(
|
||||
self, mock_warn):
|
||||
pgdict = apiutils.post_get_test_portgroup(extra={'vif_port_id': 'foo'})
|
||||
response = self.post_json('/portgroups', pgdict, headers=self.headers)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(http_client.CREATED, response.status_int)
|
||||
self.assertEqual(1, mock_warn.call_count)
|
||||
self._test_create_portgroup_with_extra_vif_port_id(
|
||||
self.headers, mock_warn)
|
||||
self.assertTrue(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
|
30
ironic/tests/unit/db/sqlalchemy/test_models.py
Normal file
30
ironic/tests/unit/db/sqlalchemy/test_models.py
Normal file
@ -0,0 +1,30 @@
|
||||
# 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 ironic.common import exception
|
||||
from ironic.db.sqlalchemy import models
|
||||
from ironic.tests import base as test_base
|
||||
|
||||
|
||||
class TestGetClass(test_base.TestCase):
|
||||
|
||||
def test_get_class(self):
|
||||
ret = models.get_class('Chassis')
|
||||
self.assertEqual(models.Chassis, ret)
|
||||
|
||||
for model in models.Base.__subclasses__():
|
||||
ret = models.get_class(model.__name__)
|
||||
self.assertEqual(model, ret)
|
||||
|
||||
def test_get_class_bad(self):
|
||||
self.assertRaises(exception.IronicException,
|
||||
models.get_class, "DoNotExist")
|
@ -12,9 +12,11 @@
|
||||
|
||||
import mock
|
||||
from oslo_utils import uuidutils
|
||||
from testtools import matchers
|
||||
|
||||
from ironic.common import context
|
||||
from ironic.common import driver_factory
|
||||
from ironic.common import exception
|
||||
from ironic.common import release_mappings
|
||||
from ironic.db import api as db_api
|
||||
from ironic.tests.unit.db import base
|
||||
@ -97,3 +99,42 @@ class MigrateToHardwareTypesTestCase(base.DbTestCase):
|
||||
self.assertEqual((1, 1), result)
|
||||
node = self.dbapi.get_node_by_id(self.node.id)
|
||||
self.assertEqual('classic_driver', node.driver)
|
||||
|
||||
|
||||
class GetNotVersionsTestCase(base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(GetNotVersionsTestCase, self).setUp()
|
||||
self.dbapi = db_api.get_instance()
|
||||
|
||||
def test_get_not_versions(self):
|
||||
versions = ['1.1', '1.2', '1.3']
|
||||
node_uuids = []
|
||||
for v in versions:
|
||||
node = utils.create_test_node(uuid=uuidutils.generate_uuid(),
|
||||
version=v)
|
||||
node_uuids.append(node.uuid)
|
||||
self.assertEqual([], self.dbapi.get_not_versions('Node', versions))
|
||||
|
||||
res = self.dbapi.get_not_versions('Node', ['2.0'])
|
||||
self.assertThat(res, matchers.HasLength(len(node_uuids)))
|
||||
res_uuids = [n.uuid for n in res]
|
||||
self.assertEqual(node_uuids, res_uuids)
|
||||
|
||||
res = self.dbapi.get_not_versions('Node', versions[1:])
|
||||
self.assertThat(res, matchers.HasLength(1))
|
||||
self.assertEqual(node_uuids[0], res[0].uuid)
|
||||
|
||||
def test_get_not_versions_null(self):
|
||||
node = utils.create_test_node(uuid=uuidutils.generate_uuid(),
|
||||
version=None)
|
||||
node = self.dbapi.get_node_by_id(node.id)
|
||||
self.assertIsNone(node.version)
|
||||
res = self.dbapi.get_not_versions('Node', ['1.6'])
|
||||
self.assertThat(res, matchers.HasLength(1))
|
||||
self.assertEqual(node.uuid, res[0].uuid)
|
||||
|
||||
def test_get_not_versions_no_model(self):
|
||||
utils.create_test_node(uuid=uuidutils.generate_uuid(), version='1.4')
|
||||
self.assertRaises(exception.IronicException,
|
||||
self.dbapi.get_not_versions, 'NotExist', ['1.6'])
|
||||
|
@ -17,7 +17,6 @@ from oslo_utils import uuidutils
|
||||
from ironic.common import exception
|
||||
from ironic.common import neutron as neutron_common
|
||||
from ironic.common import states
|
||||
from ironic.common import utils as common_utils
|
||||
from ironic.conductor import task_manager
|
||||
from ironic.drivers.modules.network import common
|
||||
from ironic.tests.unit.conductor import mgr_utils
|
||||
@ -949,10 +948,8 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase):
|
||||
mock_get.assert_called_once_with(task, 'fake_vif_id')
|
||||
mock_clear.assert_called_once_with(self.port)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch.object(neutron_common, 'update_port_address', autospec=True)
|
||||
def test_port_changed_address(self, mac_update_mock, mock_warn):
|
||||
def test_port_changed_address(self, mac_update_mock):
|
||||
new_address = '11:22:33:44:55:bb'
|
||||
self.port.address = new_address
|
||||
with task_manager.acquire(self.context, self.node.id) as task:
|
||||
@ -960,7 +957,6 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase):
|
||||
mac_update_mock.assert_called_once_with(
|
||||
self.port.extra['vif_port_id'], new_address,
|
||||
context=task.context)
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(neutron_common, 'update_port_address', autospec=True)
|
||||
def test_port_changed_address_VIF_MAC_update_fail(self, mac_update_mock):
|
||||
@ -995,21 +991,8 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase):
|
||||
dhcp_update_mock.assert_called_once_with(
|
||||
'fake-id', expected_dhcp_opts, context=task.context)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts')
|
||||
def test_port_changed_vif(self, dhcp_update_mock, mock_warn):
|
||||
expected_extra = {'vif_port_id': 'new_ake-id', 'client-id': 'fake1'}
|
||||
self.port.extra = expected_extra
|
||||
with task_manager.acquire(self.context, self.node.id) as task:
|
||||
self.interface.port_changed(task, self.port)
|
||||
self.assertFalse(dhcp_update_mock.called)
|
||||
self.assertEqual(1, mock_warn.call_count)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts')
|
||||
def test_port_changed_extra_add_new_key(self, dhcp_update_mock, mock_warn):
|
||||
def test_port_changed_extra_add_new_key(self, dhcp_update_mock):
|
||||
self.port.extra = {'vif_port_id': 'fake-id'}
|
||||
self.port.save()
|
||||
expected_extra = self.port.extra
|
||||
@ -1018,20 +1001,6 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase):
|
||||
with task_manager.acquire(self.context, self.node.id) as task:
|
||||
self.interface.port_changed(task, self.port)
|
||||
self.assertFalse(dhcp_update_mock.called)
|
||||
self.assertEqual(0, mock_warn.call_count)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts')
|
||||
def test_port_changed_extra_no_deprecation_if_removing_vif(
|
||||
self, dhcp_update_mock, mock_warn):
|
||||
self.port.extra = {'vif_port_id': 'foo'}
|
||||
self.port.save()
|
||||
self.port.extra = {'foo': 'bar'}
|
||||
with task_manager.acquire(self.context, self.node.id) as task:
|
||||
self.interface.port_changed(task, self.port)
|
||||
self.assertFalse(dhcp_update_mock.called)
|
||||
self.assertEqual(0, mock_warn.call_count)
|
||||
|
||||
@mock.patch('ironic.dhcp.neutron.NeutronDHCPApi.update_port_dhcp_opts')
|
||||
def test_port_changed_client_id_fail(self, dhcp_update_mock):
|
||||
@ -1225,48 +1194,6 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase):
|
||||
self.interface.portgroup_changed(task, pg)
|
||||
self.assertFalse(mac_update_mock.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch.object(neutron_common, 'update_port_address', autospec=True)
|
||||
def test_update_portgroup_vif(self, mac_update_mock, mock_warn):
|
||||
pg = obj_utils.create_test_portgroup(
|
||||
self.context, node_id=self.node.id)
|
||||
extra = {'vif_port_id': 'foo'}
|
||||
pg.extra = extra
|
||||
with task_manager.acquire(self.context, self.node.id) as task:
|
||||
self.interface.portgroup_changed(task, pg)
|
||||
self.assertFalse(mac_update_mock.called)
|
||||
self.assertEqual(1, mock_warn.call_count)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch.object(neutron_common, 'update_port_address', autospec=True)
|
||||
def test_update_portgroup_vif_removal_no_deprecation(self, mac_update_mock,
|
||||
mock_warn):
|
||||
pg = obj_utils.create_test_portgroup(
|
||||
self.context, node_id=self.node.id, extra={'vif_port_id': 'foo'})
|
||||
pg.extra = {}
|
||||
with task_manager.acquire(self.context, self.node.id) as task:
|
||||
self.interface.portgroup_changed(task, pg)
|
||||
self.assertFalse(mac_update_mock.called)
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch.object(neutron_common, 'update_port_address', autospec=True)
|
||||
def test_update_portgroup_extra_new_key(self, mac_update_mock, mock_warn):
|
||||
pg = obj_utils.create_test_portgroup(
|
||||
self.context, node_id=self.node.id,
|
||||
extra={'vif_port_id': 'vif-id'})
|
||||
expected_extra = pg.extra
|
||||
expected_extra['foo'] = 'bar'
|
||||
pg.extra = expected_extra
|
||||
with task_manager.acquire(self.context, self.node.id) as task:
|
||||
self.interface.portgroup_changed(task, pg)
|
||||
self.assertFalse(mac_update_mock.called)
|
||||
self.assertFalse(mock_warn.called)
|
||||
self.assertEqual(expected_extra, pg.extra)
|
||||
|
||||
@mock.patch.object(neutron_common, 'update_port_address', autospec=True)
|
||||
def test_update_portgroup_address_fail(self, mac_update_mock):
|
||||
pg = obj_utils.create_test_portgroup(
|
||||
@ -1283,10 +1210,8 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase):
|
||||
mac_update_mock.assert_called_once_with(
|
||||
'fake-id', new_address, context=task.context)
|
||||
|
||||
@mock.patch.object(common_utils, 'warn_about_deprecated_extra_vif_port_id',
|
||||
autospec=True)
|
||||
@mock.patch.object(neutron_common, 'update_port_address', autospec=True)
|
||||
def test_update_portgroup_address_no_vif(self, mac_update_mock, mock_warn):
|
||||
def test_update_portgroup_address_no_vif(self, mac_update_mock):
|
||||
pg = obj_utils.create_test_portgroup(
|
||||
self.context, node_id=self.node.id)
|
||||
new_address = '11:22:33:44:55:bb'
|
||||
@ -1295,7 +1220,6 @@ class TestNeutronVifPortIDMixin(db_base.DbTestCase):
|
||||
self.interface.portgroup_changed(task, pg)
|
||||
self.assertEqual(new_address, pg.address)
|
||||
self.assertFalse(mac_update_mock.called)
|
||||
self.assertFalse(mock_warn.called)
|
||||
|
||||
@mock.patch.object(neutron_common, 'update_port_address', autospec=True)
|
||||
def test_update_portgroup_nostandalone_ports_pxe_ports_exc(
|
||||
|
@ -666,8 +666,8 @@ expected_object_fingerprints = {
|
||||
'Node': '1.23-6bebf8dbcd2ce15407c946bd091f80b4',
|
||||
'MyObj': '1.5-9459d30d6954bffc7a9afd347a807ca6',
|
||||
'Chassis': '1.3-d656e039fd8ae9f34efc232ab3980905',
|
||||
'Port': '1.7-898a47921f4a1f53fcdddd4eeb179e0b',
|
||||
'Portgroup': '1.3-71923a81a86743b313b190f5c675e258',
|
||||
'Port': '1.8-898a47921f4a1f53fcdddd4eeb179e0b',
|
||||
'Portgroup': '1.4-71923a81a86743b313b190f5c675e258',
|
||||
'Conductor': '1.2-5091f249719d4a465062a1b3dc7f860d',
|
||||
'EventType': '1.1-aa2ba1afd38553e3880c267404e8d370',
|
||||
'NotificationPublisher': '1.0-51a09397d6c0687771fb5be9a999605d',
|
||||
|
@ -18,6 +18,7 @@ import types
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import uuidutils
|
||||
from testtools import matchers
|
||||
|
||||
from ironic.common import exception
|
||||
@ -169,7 +170,9 @@ class TestConvertToVersion(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestConvertToVersion, self).setUp()
|
||||
self.fake_port = db_utils.get_test_port()
|
||||
self.vif_id = 'some_uuid'
|
||||
extra = {'vif_port_id': self.vif_id}
|
||||
self.fake_port = db_utils.get_test_port(extra=extra)
|
||||
|
||||
def test_physnet_supported_missing(self):
|
||||
# Physical network not set, should be set to default.
|
||||
@ -224,3 +227,73 @@ class TestConvertToVersion(db_base.DbTestCase):
|
||||
port._convert_to_version("1.6", False)
|
||||
self.assertIsNone(port.physical_network)
|
||||
self.assertEqual({}, port.obj_get_changes())
|
||||
|
||||
def test_vif_in_extra_lower_version(self):
|
||||
# no conversion
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port._convert_to_version("1.7", False)
|
||||
self.assertFalse('tenant_vif_port_id' in port.internal_info)
|
||||
|
||||
def test_vif_in_extra(self):
|
||||
for v in ['1.8', '1.9']:
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port._convert_to_version(v, False)
|
||||
self.assertEqual(self.vif_id,
|
||||
port.internal_info['tenant_vif_port_id'])
|
||||
|
||||
def test_vif_in_extra_not_in_extra(self):
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port.extra.pop('vif_port_id')
|
||||
port._convert_to_version('1.8', False)
|
||||
self.assertFalse('tenant_vif_port_id' in port.internal_info)
|
||||
|
||||
def test_vif_in_extra_in_internal_info(self):
|
||||
vif2 = 'another_uuid'
|
||||
port = objects.Port(self.context, **self.fake_port)
|
||||
port.internal_info['tenant_vif_port_id'] = vif2
|
||||
port._convert_to_version('1.8', False)
|
||||
# no change
|
||||
self.assertEqual(vif2, port.internal_info['tenant_vif_port_id'])
|
||||
|
||||
|
||||
class TestMigrateVifPortId(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMigrateVifPortId, self).setUp()
|
||||
self.vif_id = 'some_uuid'
|
||||
self.db_ports = []
|
||||
extra = {'vif_port_id': self.vif_id}
|
||||
for i in range(3):
|
||||
port = db_utils.create_test_port(
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
address='52:54:00:cf:2d:3%s' % i,
|
||||
extra=extra, version='1.7')
|
||||
self.db_ports.append(port)
|
||||
|
||||
@mock.patch.object(objects.Port, '_convert_to_version', autospec=True)
|
||||
def test_migrate_vif_port_id_all(self, mock_convert):
|
||||
with mock.patch.object(self.dbapi, 'get_not_versions',
|
||||
autospec=True) as mock_get_not_versions:
|
||||
mock_get_not_versions.return_value = self.db_ports
|
||||
total, done = objects.port.migrate_vif_port_id(self.context, 0)
|
||||
self.assertEqual(3, total)
|
||||
self.assertEqual(3, done)
|
||||
mock_get_not_versions.assert_called_once_with('Port', ['1.8'])
|
||||
calls = 3 * [
|
||||
mock.call(mock.ANY, '1.8', remove_unavailable_fields=False),
|
||||
]
|
||||
self.assertEqual(calls, mock_convert.call_args_list)
|
||||
|
||||
@mock.patch.object(objects.Port, '_convert_to_version', autospec=True)
|
||||
def test_migrate_vif_port_id_one(self, mock_convert):
|
||||
with mock.patch.object(self.dbapi, 'get_not_versions',
|
||||
autospec=True) as mock_get_not_versions:
|
||||
mock_get_not_versions.return_value = self.db_ports
|
||||
total, done = objects.port.migrate_vif_port_id(self.context, 1)
|
||||
self.assertEqual(3, total)
|
||||
self.assertEqual(1, done)
|
||||
mock_get_not_versions.assert_called_once_with('Port', ['1.8'])
|
||||
calls = [
|
||||
mock.call(mock.ANY, '1.8', remove_unavailable_fields=False),
|
||||
]
|
||||
self.assertEqual(calls, mock_convert.call_args_list)
|
||||
|
@ -13,6 +13,7 @@
|
||||
import datetime
|
||||
|
||||
import mock
|
||||
from oslo_utils import uuidutils
|
||||
from testtools import matchers
|
||||
|
||||
from ironic.common import exception
|
||||
@ -165,3 +166,85 @@ class TestPortgroupObject(db_base.DbTestCase, obj_utils.SchemasTestMixIn):
|
||||
def test_payload_schemas(self):
|
||||
self._check_payload_schemas(objects.portgroup,
|
||||
objects.Portgroup.fields)
|
||||
|
||||
|
||||
class TestConvertToVersion(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestConvertToVersion, self).setUp()
|
||||
self.vif_id = 'some_uuid'
|
||||
extra = {'vif_port_id': self.vif_id}
|
||||
self.fake_portgroup = db_utils.get_test_portgroup(extra=extra)
|
||||
|
||||
def test_vif_in_extra_lower_version(self):
|
||||
# no conversion
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup._convert_to_version("1.3", False)
|
||||
self.assertFalse('tenant_vif_port_id' in portgroup.internal_info)
|
||||
|
||||
def test_vif_in_extra(self):
|
||||
for v in ['1.4', '1.5']:
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup._convert_to_version(v, False)
|
||||
self.assertEqual(self.vif_id,
|
||||
portgroup.internal_info['tenant_vif_port_id'])
|
||||
|
||||
def test_vif_in_extra_not_in_extra(self):
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup.extra.pop('vif_port_id')
|
||||
portgroup._convert_to_version('1.4', False)
|
||||
self.assertFalse('tenant_vif_port_id' in portgroup.internal_info)
|
||||
|
||||
def test_vif_in_extra_in_internal_info(self):
|
||||
vif2 = 'another_uuid'
|
||||
portgroup = objects.Portgroup(self.context, **self.fake_portgroup)
|
||||
portgroup.internal_info['tenant_vif_port_id'] = vif2
|
||||
portgroup._convert_to_version('1.4', False)
|
||||
# no change
|
||||
self.assertEqual(vif2, portgroup.internal_info['tenant_vif_port_id'])
|
||||
|
||||
|
||||
class TestMigrateVifPortId(db_base.DbTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestMigrateVifPortId, self).setUp()
|
||||
self.vif_id = 'some_uuid'
|
||||
self.db_portgroups = []
|
||||
extra = {'vif_port_id': self.vif_id}
|
||||
for i in range(3):
|
||||
portgroup = db_utils.create_test_portgroup(
|
||||
uuid=uuidutils.generate_uuid(),
|
||||
name='pg%s' % i,
|
||||
address='52:54:00:cf:2d:3%s' % i,
|
||||
extra=extra, version='1.3')
|
||||
self.db_portgroups.append(portgroup)
|
||||
|
||||
@mock.patch.object(objects.Portgroup, '_convert_to_version', autospec=True)
|
||||
def test_migrate_vif_port_id_all(self, mock_convert):
|
||||
with mock.patch.object(self.dbapi, 'get_not_versions',
|
||||
autospec=True) as mock_get_not_versions:
|
||||
mock_get_not_versions.return_value = self.db_portgroups
|
||||
total, done = objects.portgroup.migrate_vif_port_id(
|
||||
self.context, 0)
|
||||
self.assertEqual(3, total)
|
||||
self.assertEqual(3, done)
|
||||
mock_get_not_versions.assert_called_once_with('Portgroup', ['1.4'])
|
||||
calls = 3 * [
|
||||
mock.call(mock.ANY, '1.4', remove_unavailable_fields=False),
|
||||
]
|
||||
self.assertEqual(calls, mock_convert.call_args_list)
|
||||
|
||||
@mock.patch.object(objects.Portgroup, '_convert_to_version', autospec=True)
|
||||
def test_migrate_vif_port_id_one(self, mock_convert):
|
||||
with mock.patch.object(self.dbapi, 'get_not_versions',
|
||||
autospec=True) as mock_get_not_versions:
|
||||
mock_get_not_versions.return_value = self.db_portgroups
|
||||
total, done = objects.portgroup.migrate_vif_port_id(
|
||||
self.context, 1)
|
||||
self.assertEqual(3, total)
|
||||
self.assertEqual(1, done)
|
||||
mock_get_not_versions.assert_called_once_with('Portgroup', ['1.4'])
|
||||
calls = [
|
||||
mock.call(mock.ANY, '1.4', remove_unavailable_fields=False),
|
||||
]
|
||||
self.assertEqual(calls, mock_convert.call_args_list)
|
||||
|
13
releasenotes/notes/migrate_vif_port_id-5e1496638240933d.yaml
Normal file
13
releasenotes/notes/migrate_vif_port_id-5e1496638240933d.yaml
Normal file
@ -0,0 +1,13 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
``ironic-dbsync online_data_migrations`` will migrate any port's and
|
||||
port group's extra['vif_port_id'] value to their
|
||||
internal_info['tenant_vif_port_id'].
|
||||
For API versions >= 1.28, the ability to attach/detach the VIF via
|
||||
the port's or port group's extra['vif_port_id'] will not be supported
|
||||
starting with the Stein release.
|
||||
|
||||
Any out-of-tree network interface implementation that had a different
|
||||
behavior in support of attach/detach VIFs via the port or port group's
|
||||
extra['vif_port_id'] must be updated appropriately.
|
Loading…
Reference in New Issue
Block a user