Merge "Add support for node name in port creation"
This commit is contained in:
commit
f76b006b3f
@ -119,6 +119,9 @@ This method requires a Node UUID and the physical hardware address for the Port
|
|||||||
of ``vtep-logical-switch``, ``vtep-physical-switch`` and ``port_id``
|
of ``vtep-logical-switch``, ``vtep-physical-switch`` and ``port_id``
|
||||||
to identify ovn vtep switches.
|
to identify ovn vtep switches.
|
||||||
|
|
||||||
|
.. versionadded:: 1.94
|
||||||
|
Added support to create ports passing in either the node name or UUID.
|
||||||
|
|
||||||
Normal response code: 201
|
Normal response code: 201
|
||||||
|
|
||||||
Request
|
Request
|
||||||
@ -126,7 +129,7 @@ Request
|
|||||||
|
|
||||||
.. rest_parameters:: parameters.yaml
|
.. rest_parameters:: parameters.yaml
|
||||||
|
|
||||||
- node_uuid: req_node_uuid
|
- node_ident: node_ident
|
||||||
- address: req_port_address
|
- address: req_port_address
|
||||||
- portgroup_uuid: req_portgroup_uuid
|
- portgroup_uuid: req_portgroup_uuid
|
||||||
- name: req_port_name
|
- name: req_port_name
|
||||||
@ -137,6 +140,9 @@ Request
|
|||||||
- is_smartnic: req_is_smartnic
|
- is_smartnic: req_is_smartnic
|
||||||
- uuid: req_uuid
|
- uuid: req_uuid
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Either `node_ident` or `node_uuid` is a valid parameter.
|
||||||
|
|
||||||
**Example Port creation request:**
|
**Example Port creation request:**
|
||||||
|
|
||||||
.. literalinclude:: samples/port-create-request.json
|
.. literalinclude:: samples/port-create-request.json
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"node_uuid": "6d85703a-565d-469a-96ce-30b6de53079d",
|
"node_ident": "6d85703a-565d-469a-96ce-30b6de53079d",
|
||||||
"portgroup_uuid": "e43c722c-248e-4c6e-8ce8-0d8ff129387a",
|
"portgroup_uuid": "e43c722c-248e-4c6e-8ce8-0d8ff129387a",
|
||||||
"name": "port1",
|
"name": "port1",
|
||||||
"address": "11:11:11:11:11:11",
|
"address": "11:11:11:11:11:11",
|
||||||
|
@ -2,6 +2,11 @@
|
|||||||
REST API Version History
|
REST API Version History
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
1.94 (Epoxy)
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
Add support to create ports passing in either the node name or UUID.
|
||||||
|
|
||||||
1.92 (Dalmatian)
|
1.92 (Dalmatian)
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
@ -10,9 +15,9 @@ nodes associated via traits and used in place of an explicit
|
|||||||
list of steps for manual cleaning or servicing, to enable
|
list of steps for manual cleaning or servicing, to enable
|
||||||
self-service of maintenance items by project members.
|
self-service of maintenance items by project members.
|
||||||
|
|
||||||
* Adds a new REST API endpoint `/v1/runbooks/` with basic CRUD support.
|
* Adds a new REST API endpoint ``/v1/runbooks/`` with basic CRUD support.
|
||||||
* Extends the `/v1/nodes/<node>/states/provision` API to accept a runbook
|
* Extends the ``/v1/nodes/<node>/states/provision`` API to accept a runbook
|
||||||
identifier (name or UUID) instead of `clean_steps` or `service_steps` for
|
identifier (name or UUID) instead of ``clean_steps`` or ``service_steps`` for
|
||||||
servicing or manual cleaning.
|
servicing or manual cleaning.
|
||||||
* Implements RBAC-aware lifecycle management for runbooks, allowing projects
|
* Implements RBAC-aware lifecycle management for runbooks, allowing projects
|
||||||
to limit who can CRUD and use a runbook.
|
to limit who can CRUD and use a runbook.
|
||||||
|
@ -46,6 +46,7 @@ PORT_SCHEMA = {
|
|||||||
'extra': {'type': ['object', 'null']},
|
'extra': {'type': ['object', 'null']},
|
||||||
'is_smartnic': {'type': ['string', 'boolean', 'null']},
|
'is_smartnic': {'type': ['string', 'boolean', 'null']},
|
||||||
'local_link_connection': {'type': ['null', 'object']},
|
'local_link_connection': {'type': ['null', 'object']},
|
||||||
|
'node_ident': {'type': 'string'},
|
||||||
'node_uuid': {'type': 'string'},
|
'node_uuid': {'type': 'string'},
|
||||||
'physical_network': {'type': ['string', 'null'], 'maxLength': 64},
|
'physical_network': {'type': ['string', 'null'], 'maxLength': 64},
|
||||||
'portgroup_uuid': {'type': ['string', 'null']},
|
'portgroup_uuid': {'type': ['string', 'null']},
|
||||||
@ -53,7 +54,11 @@ PORT_SCHEMA = {
|
|||||||
'uuid': {'type': ['string', 'null']},
|
'uuid': {'type': ['string', 'null']},
|
||||||
'name': {'type': ['string', 'null']},
|
'name': {'type': ['string', 'null']},
|
||||||
},
|
},
|
||||||
'required': ['address', 'node_uuid'],
|
'required': ['address'],
|
||||||
|
'oneOf': [
|
||||||
|
{'required': ['node_ident']},
|
||||||
|
{'required': ['node_uuid']},
|
||||||
|
],
|
||||||
'additionalProperties': False,
|
'additionalProperties': False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,6 +70,7 @@ PATCH_ALLOWED_FIELDS = [
|
|||||||
'extra',
|
'extra',
|
||||||
'is_smartnic',
|
'is_smartnic',
|
||||||
'local_link_connection',
|
'local_link_connection',
|
||||||
|
'node_ident',
|
||||||
'node_uuid',
|
'node_uuid',
|
||||||
'physical_network',
|
'physical_network',
|
||||||
'portgroup_uuid',
|
'portgroup_uuid',
|
||||||
@ -554,8 +560,17 @@ class PortsController(rest.RestController):
|
|||||||
node = None
|
node = None
|
||||||
owner = None
|
owner = None
|
||||||
lessee = None
|
lessee = None
|
||||||
node_uuid = port.get('node_uuid')
|
node_uuid = port.get('node_uuid', None)
|
||||||
|
node_ident = port.get('node_ident', None)
|
||||||
|
|
||||||
|
if node_ident:
|
||||||
|
if not api_utils.allow_node_ident_as_param_for_port_creation():
|
||||||
|
raise exception.NotAcceptable()
|
||||||
|
|
||||||
|
ident = node_ident or node_uuid
|
||||||
try:
|
try:
|
||||||
|
node = api_utils.get_rpc_node(ident)
|
||||||
|
port['node_uuid'] = node['uuid']
|
||||||
node = api_utils.replace_node_uuid_with_id(port)
|
node = api_utils.replace_node_uuid_with_id(port)
|
||||||
owner = node.owner
|
owner = node.owner
|
||||||
lessee = node.lessee
|
lessee = node.lessee
|
||||||
|
@ -2214,3 +2214,8 @@ def allow_attach_detach_vmedia():
|
|||||||
def allow_get_vmedia():
|
def allow_get_vmedia():
|
||||||
"""Check if we should support get virtual media action."""
|
"""Check if we should support get virtual media action."""
|
||||||
return api.request.version.minor >= versions.MINOR_93_GET_VMEDIA
|
return api.request.version.minor >= versions.MINOR_93_GET_VMEDIA
|
||||||
|
|
||||||
|
|
||||||
|
def allow_node_ident_as_param_for_port_creation():
|
||||||
|
"""Check if 'node_ident' parameter is allowed for port creation."""
|
||||||
|
return api.request.version.minor >= versions.MINOR_94_PORT_NODENAME
|
||||||
|
@ -131,6 +131,7 @@ BASE_VERSION = 1
|
|||||||
# v1.91: Remove special treatment of .json for API objects
|
# v1.91: Remove special treatment of .json for API objects
|
||||||
# v1.92: Add runbooks API
|
# v1.92: Add runbooks API
|
||||||
# v1.93: Add GET API for virtual media
|
# v1.93: Add GET API for virtual media
|
||||||
|
# v1.94: Add node name support for port creation
|
||||||
|
|
||||||
MINOR_0_JUNO = 0
|
MINOR_0_JUNO = 0
|
||||||
MINOR_1_INITIAL_VERSION = 1
|
MINOR_1_INITIAL_VERSION = 1
|
||||||
@ -226,6 +227,7 @@ MINOR_90_OVN_VTEP = 90
|
|||||||
MINOR_91_DOT_JSON = 91
|
MINOR_91_DOT_JSON = 91
|
||||||
MINOR_92_RUNBOOKS = 92
|
MINOR_92_RUNBOOKS = 92
|
||||||
MINOR_93_GET_VMEDIA = 93
|
MINOR_93_GET_VMEDIA = 93
|
||||||
|
MINOR_94_PORT_NODENAME = 94
|
||||||
|
|
||||||
# When adding another version, update:
|
# When adding another version, update:
|
||||||
# - MINOR_MAX_VERSION
|
# - MINOR_MAX_VERSION
|
||||||
@ -233,7 +235,7 @@ MINOR_93_GET_VMEDIA = 93
|
|||||||
# explanation of what changed in the new version
|
# explanation of what changed in the new version
|
||||||
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
# - common/release_mappings.py, RELEASE_MAPPING['master']['api']
|
||||||
|
|
||||||
MINOR_MAX_VERSION = MINOR_93_GET_VMEDIA
|
MINOR_MAX_VERSION = MINOR_94_PORT_NODENAME
|
||||||
|
|
||||||
# String representations of the minor and maximum versions
|
# String representations of the minor and maximum versions
|
||||||
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)
|
||||||
|
@ -776,7 +776,7 @@ RELEASE_MAPPING = {
|
|||||||
# make it below. To release, we will preserve a version matching
|
# make it below. To release, we will preserve a version matching
|
||||||
# the release as a separate block of text, like above.
|
# the release as a separate block of text, like above.
|
||||||
'master': {
|
'master': {
|
||||||
'api': '1.93',
|
'api': '1.94',
|
||||||
'rpc': '1.61',
|
'rpc': '1.61',
|
||||||
'objects': {
|
'objects': {
|
||||||
'Allocation': ['1.1'],
|
'Allocation': ['1.1'],
|
||||||
|
@ -1958,6 +1958,75 @@ class TestPost(test_api_base.BaseApiTest):
|
|||||||
mock_create.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY,
|
mock_create.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY,
|
||||||
'test-topic')
|
'test-topic')
|
||||||
|
|
||||||
|
def test_create_port_missing_address_fails(self, mock_create):
|
||||||
|
pdict = post_get_test_port(node_uuid=self.node.uuid)
|
||||||
|
del pdict['address']
|
||||||
|
|
||||||
|
response = self.post_json('/ports', pdict, headers=self.headers,
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
self.assertIn("'address' is a required property",
|
||||||
|
response.json['error_message'])
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
def test_create_port_with_node_uuid(self, mock_create):
|
||||||
|
pdict = post_get_test_port(node_uuid=self.node.uuid)
|
||||||
|
response = self.post_json('/ports', pdict, headers=self.headers)
|
||||||
|
self.assertEqual(http_client.CREATED, response.status_int)
|
||||||
|
result = self.get_json('/ports/%s' % response.json['uuid'],
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(self.node.uuid, result['node_uuid'])
|
||||||
|
mock_create.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY,
|
||||||
|
'test-topic')
|
||||||
|
|
||||||
|
def test_create_port_with_node_ident(self, mock_create):
|
||||||
|
self.node.name = 'test-node-name'
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
pdict = post_get_test_port()
|
||||||
|
pdict['node_ident'] = self.node.name
|
||||||
|
del pdict['node_uuid']
|
||||||
|
|
||||||
|
response = self.post_json('/ports', pdict, headers=self.headers)
|
||||||
|
self.assertEqual(http_client.CREATED, response.status_int)
|
||||||
|
result = self.get_json('/ports/%s' % response.json['uuid'],
|
||||||
|
headers=self.headers)
|
||||||
|
self.assertEqual(self.node.uuid, result['node_uuid'])
|
||||||
|
mock_create.assert_called_once_with(mock.ANY, mock.ANY, mock.ANY,
|
||||||
|
'test-topic')
|
||||||
|
|
||||||
|
def test_create_port_with_both_node_ident_and_node_uuid(self,
|
||||||
|
mock_create):
|
||||||
|
self.node.name = 'test-node-name'
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
pdict = post_get_test_port(node_uuid=self.node.uuid)
|
||||||
|
pdict['node_ident'] = self.node.name
|
||||||
|
response = self.post_json('/ports', pdict, headers=self.headers,
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
|
def test_create_port_without_node_or_node_uuid(self, mock_create):
|
||||||
|
pdict = post_get_test_port(node_uuid=self.node.uuid)
|
||||||
|
del pdict['node_uuid']
|
||||||
|
response = self.post_json('/ports', pdict, headers=self.headers,
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.BAD_REQUEST, response.status_int)
|
||||||
|
|
||||||
|
def test_create_port_with_node_ident_unsupported_api_version(self,
|
||||||
|
mock_create):
|
||||||
|
headers = {api_base.Version.string: '1.93'}
|
||||||
|
self.node.name = 'test-node-name'
|
||||||
|
self.node.save()
|
||||||
|
|
||||||
|
pdict = post_get_test_port(node_uuid=self.node.uuid)
|
||||||
|
pdict['node_ident'] = self.node.name
|
||||||
|
del pdict['node_uuid']
|
||||||
|
|
||||||
|
response = self.post_json('/ports', pdict, headers=headers,
|
||||||
|
expect_errors=True)
|
||||||
|
self.assertEqual(http_client.NOT_ACCEPTABLE, response.status_int)
|
||||||
|
|
||||||
@mock.patch.object(notification_utils, '_emit_api_notification',
|
@mock.patch.object(notification_utils, '_emit_api_notification',
|
||||||
autospec=True)
|
autospec=True)
|
||||||
def test_create_port_error(self, mock_notify, mock_create):
|
def test_create_port_error(self, mock_notify, mock_create):
|
||||||
|
@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
Add support for passing either a node's name or UUID through the
|
||||||
|
'node_ident' parameter during port creation. The 'node_uuid' parameter is
|
||||||
|
now deprecated.
|
Loading…
Reference in New Issue
Block a user