Orchestration Resource types panel

This patch set adds 'Resources' panel to 'Orchestration' to
provide user with a list of all available resource types.

Partially implements blueprint: heat-ui-improvement

Change-Id: I0e26a1009500cc4fa78ef27ca06553cdc987fd48
This commit is contained in:
Tatiana Ovchinnikova 2014-11-13 19:12:30 +03:00
parent 6ff1234187
commit 36273e917b
13 changed files with 373 additions and 1 deletions

View File

@ -124,3 +124,11 @@ def resource_metadata_get(request, stack_id, resource_name):
def template_validate(request, **kwargs):
return heatclient(request).stacks.validate(**kwargs)
def resource_types_list(request):
return heatclient(request).resource_types.list()
def resource_type_get(request, resource_type):
return heatclient(request).resource_types.get(resource_type)

View File

@ -47,7 +47,8 @@ class ObjectStorePanels(horizon.PanelGroup):
class OrchestrationPanels(horizon.PanelGroup):
name = _("Orchestration")
slug = "orchestration"
panels = ('stacks',)
panels = ('stacks',
'stacks.resource_types',)
class DatabasePanels(horizon.PanelGroup):

View File

@ -0,0 +1,26 @@
# 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 django.utils.translation import ugettext_lazy as _
import horizon
from openstack_dashboard.dashboards.project import dashboard
class ResourceTypes(horizon.Panel):
name = _("Resource Types")
slug = "stacks.resource_types"
permissions = ('openstack.services.orchestration',)
dashboard.Project.register(ResourceTypes)

View File

@ -0,0 +1,44 @@
# 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 django.utils.translation import ugettext_lazy as _
from horizon import tables
class ResourceTypesTable(tables.DataTable):
class ResourceColumn(tables.Column):
def get_raw_data(self, datum):
attr_list = ['implementation', 'component', 'resource']
info_list = datum.resource_type.split('::')
info_list[0] = info_list[0].replace("AWS", "Amazon Web Services").\
replace("OS", "OpenStack")
info_dict = dict(zip(attr_list, info_list))
return info_dict[self.transform]
name = tables.Column("resource_type",
verbose_name=_("Type"),
link="horizon:project:stacks.resource_types:details",)
implementation = ResourceColumn("implementation",
verbose_name=_("Implementation"),)
component = ResourceColumn("component",
verbose_name=_("Component"),)
resource = ResourceColumn("resource",
verbose_name=_("Resource"),)
def get_object_id(self, resource):
return resource.resource_type
class Meta:
name = "resource_types"
verbose_name = _("Resource Types")

View File

@ -0,0 +1,38 @@
# 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 django.utils.translation import ugettext_lazy as _
from horizon import tabs
from openstack_dashboard import policy
class ResourceTypeOverviewTab(tabs.Tab):
name = _("Overview")
slug = "resource_type_overview"
template_name = "project/stacks.resource_types/_details.html"
def allowed(self, request):
return policy.check(
(("orchestration", "cloudformation:DescribeStacks"),),
request)
def get_context_data(self, request):
return {"r_type": self.tab_group.kwargs['rt'],
"r_type_attributes": self.tab_group.kwargs['rt_attributes'],
"r_type_properties": self.tab_group.kwargs['rt_properties']}
class ResourceTypeDetailsTabs(tabs.TabGroup):
slug = "resource_type_details"
tabs = (ResourceTypeOverviewTab,)

View File

@ -0,0 +1,21 @@
{% load i18n %}
<div class="resource_type row detail">
<h4>{% trans "Resource Type" %}</h4>
<hr class="header_rule">
<dl>
<dd>{{ r_type }}</dd>
</dl>
</div>
<div class="attributes row detail">
<h4>{% trans "Attributes" %}</h4>
<pre>{{ r_type_attributes }}
</pre>
</div>
<div class="properties row detail">
<h4>{% trans "Properties" %}</h4>
<pre>{{ r_type_properties }}
</pre>
</div>

View File

@ -0,0 +1,15 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Resource Type Details" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Resource Type Details") %}
{% endblock page_header %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Resource Types" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Resource Types") %}
{% endblock page_header %}
{% block main %}
{{ table.render }}
{% endblock %}

View File

@ -0,0 +1,52 @@
# 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 django.core.urlresolvers import reverse
from django import http
from mox import IsA # noqa
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
class ResourceTypesTests(test.TestCase):
INDEX_URL = reverse('horizon:project:stacks.resource_types:index')
@test.create_stubs({api.heat: ('resource_types_list',)})
def test_index(self):
api.heat.resource_types_list(
IsA(http.HttpRequest)).AndReturn(self.resource_types.list())
self.mox.ReplayAll()
res = self.client.get(self.INDEX_URL)
self.assertTemplateUsed(
res, 'project/stacks.resource_types/index.html')
self.assertContains(res, 'AWS::CloudFormation::Stack')
@test.create_stubs({api.heat: ('resource_type_get',)})
def test_detail_view(self):
rt = self.api_resource_types.first()
api.heat.resource_type_get(
IsA(http.HttpRequest), rt['resource_type']).AndReturn(rt)
self.mox.ReplayAll()
url = reverse('horizon:project:stacks.resource_types:details',
args=[rt['resource_type']])
res = self.client.get(url)
self.assertTemplateUsed(
res, 'project/stacks.resource_types/details.html')
self.assertContains(res, "<h1>Resource Type Details</h1>", 1, 200)
self.assertNoMessages()

View File

@ -0,0 +1,24 @@
# 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 django.conf.urls import patterns
from django.conf.urls import url
from openstack_dashboard.dashboards.project.stacks.resource_types import views
urlpatterns = patterns(
'',
url(r'^$', views.ResourceTypesView.as_view(), name='index'),
url(r'^(?P<resource_type>[^/]+)/$',
views.DetailView.as_view(), name='details'),
)

View File

@ -0,0 +1,73 @@
# 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 yaml
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tables
from horizon import tabs
from openstack_dashboard import api
import openstack_dashboard.dashboards.project.stacks.resource_types.tables \
as project_tables
import openstack_dashboard.dashboards.project.stacks.resource_types.tabs \
as project_tabs
class ResourceTypesView(tables.DataTableView):
table_class = project_tables.ResourceTypesTable
template_name = 'project/stacks.resource_types/index.html'
def get_data(self):
try:
r_types = sorted(api.heat.resource_types_list(self.request),
key=lambda resource: resource.resource_type)
except Exception:
r_types = []
msg = _('Unable to retrieve stack resource types.')
exceptions.handle(self.request, msg)
return r_types
class DetailView(tabs.TabView):
tab_group_class = project_tabs.ResourceTypeDetailsTabs
template_name = 'project/stacks.resource_types/details.html'
def get_resource_type(self, request, **kwargs):
try:
resource_type_overview = api.heat.resource_type_get(
request,
kwargs['resource_type'])
return resource_type_overview
except Exception:
msg = _('Unable to retrieve resource type details.')
exceptions.handle(request, msg, redirect=self.get_redirect_url())
def get_tabs(self, request, **kwargs):
resource_type_overview = self.get_resource_type(request, **kwargs)
r_type = resource_type_overview['resource_type']
r_type_attributes = resource_type_overview['attributes']
r_type_properties = resource_type_overview['properties']
return self.tab_group_class(
request,
rt=r_type,
rt_attributes=yaml.safe_dump(r_type_attributes, indent=2),
rt_properties=yaml.safe_dump(r_type_properties, indent=2),
**kwargs)
@staticmethod
def get_redirect_url():
return reverse('horizon:project:stacks.resources:index')

View File

@ -10,6 +10,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from heatclient.v1 import resource_types
from heatclient.v1 import stacks
from openstack_dashboard.test.test_data import utils
@ -326,6 +327,10 @@ def data(TEST):
TEST.stacks = utils.TestDataContainer()
TEST.stack_templates = utils.TestDataContainer()
TEST.stack_environments = utils.TestDataContainer()
TEST.resource_types = utils.TestDataContainer()
# Data return by heatclient.
TEST.api_resource_types = utils.TestDataContainer()
for i in range(10):
stack_data = {
@ -360,3 +365,57 @@ def data(TEST):
TEST.stack_templates.add(Template(TEMPLATE, VALIDATE))
TEST.stack_environments.add(Environment(ENVIRONMENT))
# Resource types list
r_type_1 = {
"resource_type": "AWS::CloudFormation::Stack",
"attributes": {},
"properties": {
"Parameters": {
"description":
"The set of parameters passed to this nested stack.",
"immutable": False,
"required": False,
"type": "map",
"update_allowed": True},
"TemplateURL": {
"description": "The URL of a template that specifies"
" the stack to be created as a resource.",
"immutable": False,
"required": True,
"type": "string",
"update_allowed": True},
"TimeoutInMinutes": {
"description": "The length of time, in minutes,"
" to wait for the nested stack creation.",
"immutable": False,
"required": False,
"type": "number",
"update_allowed": True}
}
}
r_type_2 = {
"resource_type": "OS::Heat::CloudConfig",
"attributes": {
"config": {
"description": "The config value of the software config."}
},
"properties": {
"cloud_config": {
"description": "Map representing the cloud-config data"
" structure which will be formatted as YAML.",
"immutable": False,
"required": False,
"type": "map",
"update_allowed": False}
}
}
r_types_list = [r_type_1, r_type_2]
for rt in r_types_list:
r_type = resource_types.ResourceType(
resource_types.ResourceTypeManager(None), rt['resource_type'])
TEST.resource_types.add(r_type)
TEST.api_resource_types.add(rt)