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>`_ - `Flavor <#flavor>`_
- `ResourceClass <#resource_class>`_ - `ResourceClass <#resource_class>`_
- `DataCenter <#data_center>`_ - `DataCenter <#data_center>`_
- `Node <#node>`_
Rack 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 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/ curl -XPOST -H 'Content-Type:application/json' -H 'Accept: application/json' http://0.0.0.0:8585/v1/data_centers/
`back to top <#index>`_ `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.controller import Controller
from tuskar.api.controllers.v1.data_center import DataCenterController from tuskar.api.controllers.v1.data_center import DataCenterController
from tuskar.api.controllers.v1.flavor import FlavorsController 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.rack import RacksController
from tuskar.api.controllers.v1.resource_class import ResourceClassesController from tuskar.api.controllers.v1.resource_class import ResourceClassesController
__all__ = (Controller, DataCenterController, FlavorsController, __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.openstack.common import log
from tuskar.api.controllers.v1.data_center import DataCenterController 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.rack import RacksController
from tuskar.api.controllers.v1.resource_class import ResourceClassesController from tuskar.api.controllers.v1.resource_class import ResourceClassesController
@ -21,6 +22,7 @@ class Controller(object):
racks = RacksController() racks = RacksController()
resource_classes = ResourceClassesController() resource_classes = ResourceClassesController()
data_centers = DataCenterController() data_centers = DataCenterController()
nodes = NodesController()
@pecan.expose('json') @pecan.expose('json')
def index(self): 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 # License for the specific language governing permissions and limitations
# under the License. # under the License.
#import wsme import pecan
from wsme import types as wtypes from wsme import types as wtypes
from tuskar.api.controllers.v1.types.base import Base from tuskar.api.controllers.v1.types.base import Base
from tuskar.api.controllers.v1.types.link import Link from tuskar.api.controllers.v1.types.link import Link
from tuskar.api.controllers.v1.types.relation import Relation
class Node(Base): class Node(Base):
"""A Node representation.""" """A Node representation."""
id = wtypes.text 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] 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) unit=c.unit)
for c in rack.capacities] 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=[ links=[
Link.build_ironic_link('node', n.node_id) Link.build('self',
pecan.request.host_url,
'nodes', n.id)
]) ])
for n in rack.nodes] for n in rack.nodes]

View File

@ -507,3 +507,19 @@ class Connection(api.Connection):
session.rollback() session.rollback()
return False return False
return True 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) id = Column(Integer, primary_key=True)
rack_id = Column(Integer, ForeignKey('racks.id')) rack_id = Column(Integer, ForeignKey('racks.id'))
node_id = Column(String(length=64), unique=True) node_id = Column(String(length=64), unique=True)
rack = relationship("Rack")
class Rack(Base): 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])