api: Reject duplicate port IDs in server create
Specifying a duplicate port ID is currently "allowed" but results in an integrity error when nova attempts to create a duplicate 'VirtualInterface' entry. Start rejecting these requests by checking for duplicate IDs and rejecting offending requests. This is arguably an API change because there isn't a HTTP 5xx error (server create is an async operation), however, users shouldn't have to opt in to non-broken behavior and the underlying instance was never actually created previously, meaning automation that relied on this "feature" was always going to fail in a later step. We're also silently failing to do what the user asked (per flow chart at [1]). [1] https://docs.openstack.org/nova/latest/contributor/microversions.html#when-do-i-need-a-new-microversion Change-Id: Ie90fb83662dd06e7188f042fc6340596f93c5ef9 Signed-off-by: Stephen Finucane <stephenfin@redhat.com> Closes-Bug: #1821088
This commit is contained in:
parent
452913a284
commit
9fe4654273
@ -409,6 +409,7 @@ class ServersController(wsgi.Controller):
|
|||||||
|
|
||||||
networks = []
|
networks = []
|
||||||
network_uuids = []
|
network_uuids = []
|
||||||
|
port_uuids = []
|
||||||
for network in requested_networks:
|
for network in requested_networks:
|
||||||
request = objects.NetworkRequest()
|
request = objects.NetworkRequest()
|
||||||
try:
|
try:
|
||||||
@ -417,18 +418,31 @@ class ServersController(wsgi.Controller):
|
|||||||
# it will use one of the available IP address from the network
|
# it will use one of the available IP address from the network
|
||||||
request.address = network.get('fixed_ip', None)
|
request.address = network.get('fixed_ip', None)
|
||||||
request.port_id = network.get('port', None)
|
request.port_id = network.get('port', None)
|
||||||
|
|
||||||
request.tag = network.get('tag', None)
|
request.tag = network.get('tag', None)
|
||||||
|
|
||||||
if request.port_id:
|
if request.port_id:
|
||||||
request.network_id = None
|
if request.port_id in port_uuids:
|
||||||
if request.address is not None:
|
msg = _(
|
||||||
msg = _("Specified Fixed IP '%(addr)s' cannot be used "
|
"Port ID '%(port)s' was specified twice: you "
|
||||||
"with port '%(port)s': the two cannot be "
|
"cannot attach a port multiple times."
|
||||||
"specified together.") % {
|
) % {
|
||||||
"addr": request.address,
|
"port": request.port_id,
|
||||||
"port": request.port_id}
|
}
|
||||||
raise exc.HTTPBadRequest(explanation=msg)
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
if request.address is not None:
|
||||||
|
msg = _(
|
||||||
|
"Specified Fixed IP '%(addr)s' cannot be used "
|
||||||
|
"with port '%(port)s': the two cannot be "
|
||||||
|
"specified together."
|
||||||
|
) % {
|
||||||
|
"addr": request.address,
|
||||||
|
"port": request.port_id,
|
||||||
|
}
|
||||||
|
raise exc.HTTPBadRequest(explanation=msg)
|
||||||
|
|
||||||
|
request.network_id = None
|
||||||
|
port_uuids.append(request.port_id)
|
||||||
else:
|
else:
|
||||||
request.network_id = network['uuid']
|
request.network_id = network['uuid']
|
||||||
self._validate_network_id(
|
self._validate_network_id(
|
||||||
|
@ -389,17 +389,36 @@ class ServersControllerTest(_ServersControllerTest):
|
|||||||
(network, None, None, None, None, None)],
|
(network, None, None, None, None, None)],
|
||||||
res.as_tuples())
|
res.as_tuples())
|
||||||
|
|
||||||
def test_requested_networks_enabled_conflict_on_fixed_ip(self):
|
def test_requested_networks_duplicate_ports(self):
|
||||||
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
"""The same port can't be specified twice."""
|
||||||
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||||
addr = '10.0.0.1'
|
requested_networks = [{'port': port}, {'port': port}]
|
||||||
requested_networks = [{'uuid': network,
|
exc = self.assertRaises(
|
||||||
'fixed_ip': addr,
|
|
||||||
'port': port}]
|
|
||||||
self.assertRaises(
|
|
||||||
webob.exc.HTTPBadRequest,
|
webob.exc.HTTPBadRequest,
|
||||||
self.controller._get_requested_networks,
|
self.controller._get_requested_networks,
|
||||||
requested_networks)
|
requested_networks,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"Port ID 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee' was specified "
|
||||||
|
"twice",
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_requested_networks_conflict_on_fixed_ip(self):
|
||||||
|
"""A fixed IP can't be specified at the same as a port ID."""
|
||||||
|
port = 'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'
|
||||||
|
addr = '10.0.0.1'
|
||||||
|
requested_networks = [{'fixed_ip': addr, 'port': port}]
|
||||||
|
exc = self.assertRaises(
|
||||||
|
webob.exc.HTTPBadRequest,
|
||||||
|
self.controller._get_requested_networks,
|
||||||
|
requested_networks,
|
||||||
|
)
|
||||||
|
self.assertIn(
|
||||||
|
"Specified Fixed IP '10.0.0.1' cannot be used with port "
|
||||||
|
"'eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee'",
|
||||||
|
str(exc),
|
||||||
|
)
|
||||||
|
|
||||||
def test_requested_networks_api_enabled_with_v2_subclass(self):
|
def test_requested_networks_api_enabled_with_v2_subclass(self):
|
||||||
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
network = 'aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa'
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
fixes:
|
||||||
|
- |
|
||||||
|
The ``POST /servers`` (create server) API will now reject attempts to
|
||||||
|
create a server with the same port specified multiple times. This was
|
||||||
|
previously accepted by the API but the instance would fail to spawn and
|
||||||
|
would instead transition to the error state.
|
Loading…
Reference in New Issue
Block a user