Expose Nodes as a top-level object

Rack information is also shared when fetching node information

Change-Id: I02361a6a89cf3b0bb031732eae893dbd5c0d46ea
This commit is contained in:
Matt Wagner 2013-09-17 13:00:39 -04:00
parent ba1fd47cc9
commit ee8443c885
9 changed files with 227 additions and 6 deletions

View File

@ -9,6 +9,7 @@ Resources
- `Flavor <#flavor>`_
- `ResourceClass <#resource_class>`_
- `DataCenter <#data_center>`_
- `Node <#node>`_
Rack
----
@ -325,9 +326,9 @@ delete
::
curl -X DELETE http://0.0.0.0:8585/v1/resource_classes/1`` `back to
curl -X DELETE http://0.0.0.0:8585/v1/resource_classes/1
top <#index>`_
`back to top <#index>`_
DataCenter
----------
@ -343,3 +344,78 @@ Tuskar.
curl -XPOST -H 'Content-Type:application/json' -H 'Accept: application/json' http://0.0.0.0:8585/v1/data_centers/
`back to top <#index>`_
Node
----
Get Collection
~~~~~~~~~~~~~~
::
curl http://0.0.0.0:8585/v1/nodes/
response
^^^^^^^^
::
[
{
"nova_baremetal_node_id": "0e3ab3d3-bd85-40bd-b6a1-fae484040825",
"id": "1",
"links": [
{
"href": "http://127.0.0.1:8585/v1/nodes/1",
"rel": "self"
}
],
"rack": {
"id": 1,
"links":
[
{
"href": "http://127.0.0.1:8585/v1/racks/1",
"rel": "self"
}
]
}
}
]
Retrieve a single Node
~~~~~~~~~~~~~~~~~~~~~~
::
curl http://0.0.0.0:8585/v1/nodes/1
response
^^^^^^^^
::
{
"nova_baremetal_node_id": "0e3ab3d3-bd85-40bd-b6a1-fae484040825",
"id": "1",
"links":
[
{
"href": "http://127.0.0.1:8585/v1/nodes/1",
"rel": "self"
}
],
"rack":
{
"id": 1,
"links":
[
{
"href": "http://127.0.0.1:8585/v1/racks/1",
"rel": "self"
}
]
}
}
`back to top <#index>`_

View File

@ -14,8 +14,10 @@
from tuskar.api.controllers.v1.controller import Controller
from tuskar.api.controllers.v1.data_center import DataCenterController
from tuskar.api.controllers.v1.flavor import FlavorsController
from tuskar.api.controllers.v1.node import NodesController
from tuskar.api.controllers.v1.rack import RacksController
from tuskar.api.controllers.v1.resource_class import ResourceClassesController
__all__ = (Controller, DataCenterController, FlavorsController,
RacksController, ResourceClassesController)
RacksController, ResourceClassesController,
NodesController)

View File

@ -11,6 +11,7 @@ import pecan
#from tuskar.openstack.common import log
from tuskar.api.controllers.v1.data_center import DataCenterController
from tuskar.api.controllers.v1.node import NodesController
from tuskar.api.controllers.v1.rack import RacksController
from tuskar.api.controllers.v1.resource_class import ResourceClassesController
@ -21,6 +22,7 @@ class Controller(object):
racks = RacksController()
resource_classes = ResourceClassesController()
data_centers = DataCenterController()
nodes = NodesController()
@pecan.expose('json')
def index(self):

View File

@ -0,0 +1,42 @@
import pecan
from pecan import rest
import wsmeext.pecan as wsme_pecan
from tuskar.api.controllers.v1.types import Error
from tuskar.api.controllers.v1.types import Node
from tuskar.common import exception
from tuskar.openstack.common import log
from wsme import api
LOG = log.getLogger(__name__)
class NodesController(rest.RestController):
"""REST controller for Node."""
@wsme_pecan.wsexpose([Node])
def get_all(self):
"""Retrieve a list of all nodes."""
result = []
db_api = pecan.request.dbapi
for node in db_api.get_nodes(None):
result.append(Node.convert(node))
return result
@wsme_pecan.wsexpose(Node, unicode)
def get_one(self, node_id):
"""Retrieve an instance of a Node."""
db_api = pecan.request.dbapi
try:
node = db_api.get_node(node_id)
except exception.TuskarException, e:
response = api.Response(None,
error=Error(faultcode=e.code,
faultstring=str(e)),
status_code=e.code)
return response
return Node.convert(node)

View File

@ -12,15 +12,32 @@
# License for the specific language governing permissions and limitations
# under the License.
#import wsme
import pecan
from wsme import types as wtypes
from tuskar.api.controllers.v1.types.base import Base
from tuskar.api.controllers.v1.types.link import Link
from tuskar.api.controllers.v1.types.relation import Relation
class Node(Base):
"""A Node representation."""
id = wtypes.text
# FIXME: We expose this as nova_baremetal_node_id, but are not yet changing
# the column name in the database, because this is a more involved change.
nova_baremetal_node_id = wtypes.text
rack = Relation
links = [Link]
@classmethod
def convert(self, node):
kwargs = node.as_dict()
links = [Link.build('self', pecan.request.host_url, 'nodes',
node.id)]
rack_link = [Link.build('self', pecan.request.host_url,
'racks', node.rack_id)]
kwargs['rack'] = Relation(id=node.rack_id, links=rack_link)
kwargs['id'] = str(node.id)
kwargs['nova_baremetal_node_id'] = str(node.node_id)
return Node(links=links, **kwargs)

View File

@ -60,9 +60,12 @@ class Rack(Base):
unit=c.unit)
for c in rack.capacities]
kwargs['nodes'] = [Node(id=n.node_id,
kwargs['nodes'] = [Node(id=str(n.id),
node_id=n.node_id,
links=[
Link.build_ironic_link('node', n.node_id)
Link.build('self',
pecan.request.host_url,
'nodes', n.id)
])
for n in rack.nodes]

View File

@ -507,3 +507,19 @@ class Connection(api.Connection):
session.rollback()
return False
return True
def get_nodes(self, columns):
session = get_session()
result = session.query(models.Node).options(
joinedload('rack')).all()
session.close()
return result
def get_node(self, node_id):
session = get_session()
try:
result = session.query(models.Node).options(
joinedload('rack')).filter_by(id=node_id).one()
except NoResultFound:
raise exception.NodeNotFound(node=node_id)
return result

View File

@ -123,6 +123,7 @@ class Node(Base):
id = Column(Integer, primary_key=True)
rack_id = Column(Integer, ForeignKey('racks.id'))
node_id = Column(String(length=64), unique=True)
rack = relationship("Rack")
class Rack(Base):

View File

@ -0,0 +1,62 @@
from tuskar.api.controllers.v1.types import Capacity
from tuskar.api.controllers.v1.types import Chassis
from tuskar.api.controllers.v1.types import Node
from tuskar.api.controllers.v1.types import Rack
from tuskar.db.sqlalchemy import api as dbapi
from tuskar.tests.api import api
class TestNodes(api.FunctionalTest):
test_node = None
db = dbapi.get_backend()
def setUp(self):
"""Create 'test_rack'."""
super(TestNodes, self).setUp()
# self.test_resource_class = None
self.test_node = Node(id='1', name='test_node', node_id='1')
self.test_rack = self.db.create_rack(
Rack(name='test-rack',
slots=1,
subnet='10.0.0.0/24',
location='nevada',
chassis=Chassis(id='123'),
capacities=[Capacity(name='cpu', value='10',
unit='count')],
nodes=[self.test_node]
))
def tearDown(self):
self.db.delete_rack(self.test_rack.id)
super(TestNodes, self).tearDown()
def valid_node_json(self, node_json, test_node=None):
node = self.test_node if test_node is None else test_node
self.assertEqual(node_json['nova_baremetal_node_id'], node.node_id)
self.assertEqual(node_json['id'], node.id)
def test_it_returns_single_node(self):
response = self.get_json('/nodes/' + str(self.test_node.id),
expect_errors=True)
self.assertEqual(response.status_int, 200)
self.assertEqual(response.content_type, 'application/json')
self.valid_node_json(response.json)
# it should contain a rack
rack = response.json['rack']
self.assertEqual(rack['id'], self.test_rack.id)
def test_it_returns_node_list(self):
response = self.get_json('/nodes/', expect_errors=True)
self.assertEqual(response.status_int, 200)
self.assertEqual(response.content_type, 'application/json')
# It should consist solely of one node:
self.assertEqual(len(response.json), 1)
# And that node should pass our JSON test:
self.valid_node_json(response.json[0])