diff --git a/ironic/api/controllers/v1/port.py b/ironic/api/controllers/v1/port.py index ef04da0736..17b7498f70 100644 --- a/ironic/api/controllers/v1/port.py +++ b/ironic/api/controllers/v1/port.py @@ -87,12 +87,23 @@ class Port(base.APIBase): "A list containing a self link and associated port links" def __init__(self, **kwargs): - self.fields = objects.Port.fields.keys() + self.fields = [] + fields = list(objects.Port.fields.keys()) # NOTE(lucasagomes): node_uuid is not part of objects.Port.fields # because it's an API-only attribute - self.fields.append('node_uuid') - for k in self.fields: - setattr(self, k, kwargs.get(k)) + fields.append('node_uuid') + for field in fields: + # Skip fields we do not expose. + if not hasattr(self, field): + continue + self.fields.append(field) + setattr(self, field, kwargs.get(field)) + + # NOTE(lucasagomes): node_id is an attribute created on-the-fly + # by _set_node_uuid(), it needs to be present in the fields so + # that as_dict() will contain node_id field when converting it + # before saving it in the database. + self.fields.append('node_id') setattr(self, 'node_uuid', kwargs.get('node_id')) @classmethod @@ -307,8 +318,13 @@ class PortsController(rest.RestController): # Update only the fields that have changed for field in objects.Port.fields: - if rpc_port[field] != getattr(port, field): - rpc_port[field] = getattr(port, field) + try: + patch_val = getattr(port, field) + except AttributeError: + # Ignore fields that aren't exposed in the API + continue + if rpc_port[field] != patch_val: + rpc_port[field] = patch_val rpc_node = objects.Node.get_by_id(pecan.request.context, rpc_port.node_id) diff --git a/ironic/tests/api/v1/test_ports.py b/ironic/tests/api/v1/test_ports.py index 3b345f9442..88e2b96e2d 100644 --- a/ironic/tests/api/v1/test_ports.py +++ b/ironic/tests/api/v1/test_ports.py @@ -508,6 +508,17 @@ class TestPost(base.FunctionalTest): self.assertEqual(urlparse.urlparse(response.location).path, expected_location) + def test_create_port_doesnt_contain_id(self): + with mock.patch.object(self.dbapi, 'create_port', + wraps=self.dbapi.create_port) as cp_mock: + pdict = post_get_test_port(extra={'foo': 123}) + self.post_json('/ports', pdict) + result = self.get_json('/ports/%s' % pdict['uuid']) + self.assertEqual(pdict['extra'], result['extra']) + cp_mock.assert_called_once_with(mock.ANY) + # Check that 'id' is not in first arg of positional args + self.assertNotIn('id', cp_mock.call_args[0][0]) + def test_create_port_generate_uuid(self): pdict = post_get_test_port() del pdict['uuid']