Add API endpoint for overcloud Keystone

The logic of querying the overcloud entrypoints are specific to the
TripleO/Tuskar Heat templates. This API endpoint provides a way for the client
to be independent of this logic.

Change-Id: I552d007d4e1bd3a7558d16138ef96a23a25394ea
Implements: blueprint tuskar-api-return-endpoints
This commit is contained in:
Imre Farkas 2013-10-17 14:09:40 +02:00
parent 38e39fba8a
commit 8de738f3d4
8 changed files with 191 additions and 6 deletions

View File

@ -10,6 +10,7 @@ Resources
- `ResourceClass <#resource_class>`_ - `ResourceClass <#resource_class>`_
- `DataCenter <#data_center>`_ - `DataCenter <#data_center>`_
- `Node <#node>`_ - `Node <#node>`_
- `Overcloud <#overcloud>`_
Rack Rack
---- ----
@ -418,4 +419,29 @@ response
} }
} }
Overcloud
----------
get Keystone URL for an overcloud
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
::
curl -X GET -H 'Content-Type:application/json' -H 'Accept: application/json' http://0.0.0.0:8585/v1/overclouds/cloudname
response
^^^^^^^^
::
{
"stack_name": "cloudname",
"links": [
{
"rel": "keystone",
"href": "http://192.0.2.5:5000/v2.0/"
}
]
}
`back to top <#index>`_ `back to top <#index>`_

View File

@ -15,9 +15,10 @@ 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.node import NodesController
from tuskar.api.controllers.v1.overcloud import OvercloudsController
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, OvercloudsController, RacksController, ResourceClassesController,
NodesController) NodesController)

View File

@ -12,6 +12,7 @@ import pecan
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.node import NodesController
from tuskar.api.controllers.v1.overcloud import OvercloudsController
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
@ -22,6 +23,7 @@ class Controller(object):
racks = RacksController() racks = RacksController()
resource_classes = ResourceClassesController() resource_classes = ResourceClassesController()
data_centers = DataCenterController() data_centers = DataCenterController()
overclouds = OvercloudsController()
nodes = NodesController() nodes = NodesController()
@pecan.expose('json') @pecan.expose('json')

View File

@ -0,0 +1,66 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pecan
from wsme import api
from wsmeext import pecan as wsme_pecan
import heatclient.exc
from tuskar.api.controllers.v1.types import Error
from tuskar.api.controllers.v1.types import Link
from tuskar.api.controllers.v1.types import Overcloud
import tuskar.heat.client
from tuskar.openstack.common.gettextutils import _
class OvercloudsController(pecan.rest.RestController):
"""Controller for Overcloud."""
@wsme_pecan.wsexpose(Overcloud, unicode)
def get_one(self, stack_name):
heat = tuskar.heat.client.HeatClient()
try:
stack = heat.get_stack(stack_name)
except heatclient.exc.HTTPNotFound as ex:
response = api.Response(
None,
error=Error(faultcode=ex.code, faultstring=str(ex)),
status_code=ex.code)
return response
if not hasattr(stack, 'outputs'):
faultstring = _('Failed to find Keystone URL.')
response = api.Response(
None,
error=Error(faultcode=404, faultstring=faultstring),
status_code=404)
return response
outputs = stack.outputs
keystone_param = filter(lambda x: x['output_key'] == 'KeystoneURL',
outputs)
if len(keystone_param) == 0:
faultstring = _('Failed to find Keystone URL.')
response = api.Response(
None,
error=Error(faultcode=404, faultstring=faultstring),
status_code=404)
return response
keystone_link = Link(rel='keystone',
href=keystone_param[0]['output_value'])
overcloud = Overcloud(stack_name=stack_name,
links=[keystone_link])
return overcloud

View File

@ -18,9 +18,10 @@ from tuskar.api.controllers.v1.types.error import Error
from tuskar.api.controllers.v1.types.flavor import Flavor from tuskar.api.controllers.v1.types.flavor import Flavor
from tuskar.api.controllers.v1.types.link import Link from tuskar.api.controllers.v1.types.link import Link
from tuskar.api.controllers.v1.types.node import Node from tuskar.api.controllers.v1.types.node import Node
from tuskar.api.controllers.v1.types.overcloud import Overcloud
from tuskar.api.controllers.v1.types.rack import Rack from tuskar.api.controllers.v1.types.rack import Rack
from tuskar.api.controllers.v1.types.relation import Relation from tuskar.api.controllers.v1.types.relation import Relation
from tuskar.api.controllers.v1.types.resource_class import ResourceClass from tuskar.api.controllers.v1.types.resource_class import ResourceClass
__all__ = (Base, Capacity, Chassis, Error, Flavor, Link, Node, Rack, __all__ = (Base, Capacity, Chassis, Error, Flavor, Link, Node, Overcloud, Rack,
Relation, ResourceClass) Relation, ResourceClass)

View File

@ -0,0 +1,23 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from wsme import types as wtypes
from tuskar.api.controllers.v1.types.base import Base
from tuskar.api.controllers.v1.types.link import Link
class Overcloud(Base):
"""An Overcloud representation."""
stack_name = wtypes.text
links = [Link]

View File

@ -102,10 +102,12 @@ class HeatClient(object):
LOG.exception(e) LOG.exception(e)
return False return False
def get_stack(self): def get_stack(self, name=None):
"""Get overcloud Heat template.""" """Get overcloud Heat template."""
if name is None:
name = CONF.heat['stack_name']
if self.connection: if self.connection:
return self.connection.stacks.get(CONF.heat['stack_name']) return self.connection.stacks.get(name)
def get_template(self): def get_template(self):
"""Get JSON representation of the Heat overcloud template.""" """Get JSON representation of the Heat overcloud template."""
@ -135,9 +137,11 @@ class HeatClient(object):
LOG.exception(e) LOG.exception(e)
return False return False
def exists_stack(self): def exists_stack(self, name=None):
if name is None:
name = CONF.heat['stack_name']
try: try:
self.get_stack() self.get_stack(name)
return True return True
#return false if 404 #return false if 404
except HeatStackNotFound: except HeatStackNotFound:

View File

@ -0,0 +1,62 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mox
import heatclient.exc
import heatclient.v1.stacks
import tuskar.heat.client
from tuskar.tests.api import api
class TestOverclouds(api.FunctionalTest):
def test_it_returns_the_overcloud_endpoints(self):
heat_outputs = [
{'output_value': 'http://192.0.2.5:5000/v2.0/',
'description': 'URL for the Overcloud Keystone service',
'output_key': 'KeystoneURL'},
]
heat_stack = mox.MockAnything()
heat_stack.outputs = heat_outputs
self.mox.StubOutWithMock(tuskar.heat.client.HeatClient, 'get_stack')
tuskar.heat.client.HeatClient.get_stack('stack_name').AndReturn(
heat_stack)
self.mox.ReplayAll()
response = self.app.get('/v1/overclouds/stack_name')
self.assertEqual(response.status, '200 OK')
self.assertRegexpMatches(response.body, 'http://192.0.2.5:5000/v2.0/')
def test_it_returns_404_for_nonexisting_overcloud(self):
self.mox.StubOutWithMock(tuskar.heat.client.HeatClient, 'get_stack')
tuskar.heat.client.HeatClient.get_stack(
'stack_name').AndRaise(heatclient.exc.HTTPNotFound())
self.mox.ReplayAll()
response = self.app.get('/v1/overclouds/stack_name',
expect_errors=True)
self.assertEqual(response.status, '404 Not Found')
def test_it_returns_404_during_provisioning(self):
heat_stack = self.mox.CreateMock(heatclient.v1.stacks.Stack)
self.mox.StubOutWithMock(tuskar.heat.client.HeatClient, 'get_stack')
tuskar.heat.client.HeatClient.get_stack('stack_name').AndReturn(
heat_stack)
self.mox.ReplayAll()
response = self.app.get('/v1/overclouds/stack_name',
expect_errors=True)
self.assertEqual(response.status, '404 Not Found')