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:
parent
38e39fba8a
commit
8de738f3d4
@ -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>`_
|
||||||
|
@ -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)
|
||||||
|
@ -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')
|
||||||
|
66
tuskar/api/controllers/v1/overcloud.py
Normal file
66
tuskar/api/controllers/v1/overcloud.py
Normal 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
|
@ -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)
|
||||||
|
23
tuskar/api/controllers/v1/types/overcloud.py
Normal file
23
tuskar/api/controllers/v1/types/overcloud.py
Normal 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]
|
@ -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:
|
||||||
|
62
tuskar/tests/api/controllers/v1/test_overclouds.py
Normal file
62
tuskar/tests/api/controllers/v1/test_overclouds.py
Normal 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')
|
Loading…
x
Reference in New Issue
Block a user