ports API allows node IDs in addition to node UUIDs

Bug #1301046 reported by Chris Behrens
20
This bug affects 3 people
Affects Status Importance Assigned to Milestone
Ironic
Triaged
Low
Unassigned

Bug Description

The API should only expose node UUIDs. The Port node_uuid is declared in such a way that it doesn't validate that a UUID is passed. It calls dbapi.get_node() (or the object version if another patch lands) which allows both ID and UUID lookups.

It also appears the tests are somehow relying on this. The JSON passed in the tests is correct, however internally the DB object is created and ends up being passed to Port().. The port code ends up taking the node_id from the DB and sets it as Port.node_uuid as can be seen in the traceback below:

File "ironic/tests/api/v1/test_ports.py", line 368, in test_create_port
    response = self.post_json('/ports', pdict)
  File "ironic/tests/api/base.py", line 134, in post_json
    status=status, method="post")
  File "ironic/tests/api/base.py", line 95, in _request_json
    expect_errors=expect_errors
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/webtest/utils.py", line 41, in wrapper
    return self._gen_request(method, url, **kw)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/webtest/app.py", line 626, in _gen_request
    expect_errors=expect_errors)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/webtest/app.py", line 495, in do_request
    res = req.get_response(app, catch_exc_info=True)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/webob/request.py", line 1316, in send
    application, catch_exc_info=True)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/webob/request.py", line 1284, in call_application
    app_iter = application(self.environ, start_response)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/webtest/lint.py", line 198, in lint_app
    iterator = application(environ, start_response_wrapper)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/pecan/middleware/recursive.py", line 56, in __call__
    return self.application(environ, start_response)
  File "ironic/api/middleware/parsable_error.py", line 68, in __call__
    app_iter = self.app(environ, replacement_start_response)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/pecan/core.py", line 570, in __call__
    self.handle_request(req, resp)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/pecan/core.py", line 508, in handle_request
    result = controller(*args, **kwargs)
  File "/home/cbehrens/git/ironic/os.git/.tox/py27/local/lib/python2.7/site-packages/wsmeext/pecan.py", line 77, in callfunction
    result = f(self, *args, **kwargs)
  File "ironic/api/controllers/v1/port.py", line 254, in post
    return Port.convert_with_links(new_port)
  File "ironic/api/controllers/v1/port.py", line 99, in convert_with_links
    port = Port(**rpc_port.as_dict())
  File "ironic/api/controllers/v1/port.py", line 95, in __init__
    setattr(self, 'node_uuid', kwargs.get('node_id'))

Dmitry Tantsur (divius)
Changed in ironic:
status: New → Triaged
importance: Undecided → Low
Revision history for this message
John Stafford (john-stafford) wrote :

1:07 PM <jroll> BadCub: lol, I picked that up at the end of kilo and never finished it, I know Haomeng was working on the same thing but maybe fined a different bug?
1:09 PM <BadCub> Haomeng: are you working on something similar to/or dup of ^^^
1:10 PM <BadCub> thanks jroll :-) I will wait to hear from Haomeng on that one

Revision history for this message
Mario Villaplana (mario-villaplana-j) wrote :

I'll take a look at this if HaoMeng and jroll haven't patched yet.

Changed in ironic:
assignee: nobody → Mario Villaplana (mario-villaplana-j)
Revision history for this message
Jim Rollenhagen (jim-rollenhagen) wrote :

That comment looks like it's on the wrong bug, that was in the context of an i18n thing.

Revision history for this message
Mario Villaplana (mario-villaplana-j) wrote :

I'm looking at one test case in-depth to see how the tests rely on this. Multiple test cases are failing with the same error message when I change Port._set_node_uuid to only allow the passed value to be a node UUID (changing objects.Node.get to objects.Node.get_by_uuid within the function).

The test case I'm examining is test_get_one in tests/api/v1/test_ports.

The part of the test that's failing with this change is the GET request to retrieve the port. It looks like the port retrieved via RPC in get_one in the ports API controller doesn't have a _node_uuid attached but does have a node ID attached.

Like your stack trace shows, having the node ID in the RPC (or anything that passes a node id to the Port constructor in the ports API controller) causes a NodeNotFound error. convert_with_links creates a new Port from the RPC port with the node ID still attached, causing the error.

I think the best next step is to examine what the RPC call to retrieve a port returns in a successful invocation of get_one with the small change described above. This will show us what the RPC port *should* look like and isolate what the tests are doing differently that causes them to fail. If there are no possible successful invocations of get_one with the change described above, I might have to reconsider some assumptions about what _set_node_uuid expects and guarantees.

Revision history for this message
Mario Villaplana (mario-villaplana-j) wrote :

It looks like the actual API may rely on this, too - not just the tests. Changing this to Node.get_by_uuid makes devstack fail to boot correctly: https://github.com/openstack/ironic/blob/master/ironic/api/controllers/v1/port.py#L60

I'm going to add some logging / use pdb and see what happens when I use the original code to perform port API operations with the node ID. I'll also try seeing if the same thing is happening as in the tests when switching to get_by_uuid.

Revision history for this message
Mario Villaplana (mario-villaplana-j) wrote :

The logging didn't offer that much insight. The only consistent thing that I was able to glean was that on devstack startup the Port constructor is called and tries to set the node UUID to a real node UUID first, then it gets called again down the stack with the internal node ID. I'm not sure why there are two calls being made.

I attached the logs created by a test run with the logs defined in this commit: https://github.com/supermari0/ironic/blob/2b2bd8999daac64b476fa4c6f54f5a0d727e9026/ironic/api/controllers/v1/port.py

I eventually figured out that when any function needs to call the convert_with_links function on a port, the node UUID isn't present in the RPC node, since the node UUID isn't retained in the port from the DB. This is what prevents _set_node_uuid from calling objects.Node.get_by_uuid instead of objects.Node.get since the constructor attempts to set node_uuid to node_id. The setter eventually figures out what the correct node UUID should be after retrieving the node, and that's why calling the setter with the node ID doesn't cause failures.

I tried to retrieve the Node by its ID in convert_with_links and set the node UUID on the port before it's passed to the constructor, but tests were still failing when replacing objects.Node.get with objects.Node.get_by_uuid. The commit with that change is here: https://github.com/supermari0/ironic/commit/499e20e5270430a5d5765297a85f5709cc9f788b

I'm going to take a break from this bug to work on other things, but feel free to reassign if someone else wants to take it.

Changed in ironic:
assignee: Mario Villaplana (mario-villaplana-j) → nobody
Joanna Taryma (jtaryma)
Changed in ironic:
assignee: nobody → Joanna Taryma (jtaryma)
Joanna Taryma (jtaryma)
Changed in ironic:
status: Triaged → In Progress
Revision history for this message
OpenStack Infra (hudson-openstack) wrote : Fix proposed to ironic (master)

Fix proposed to branch: master
Review: https://review.openstack.org/403910

Vladyslav Drok (vdrok)
Changed in ironic:
status: In Progress → Triaged
assignee: Joanna Taryma (jtaryma) → nobody
Revision history for this message
Julia Kreger (juliaashleykreger) wrote :

Given a patch has already been proposed that is just in merge conflict, it seems like someone could pick this up and move it forward as a low hanging fruit item. The contributor would just need to rebase and continue change 403910.

tags: added: low-hanging-fruit
To post a comment you must log in.
This report contains Public information  
Everyone can see this information.

Other bug subscribers

Remote bug watches

Bug watches keep track of this bug in other bug trackers.