Adding tox file for gate testing and some other fixes

- Renaming some labels
- Adding title to the cluster page
- Also removing some debuging logs
- Fix code to pass pep8
- Remove volume size
- Display flavor name instead of ID in detail page

Change-Id: Ia3351eace341797f48275c94b8bc352fd83791c4
This commit is contained in:
Steve Leon 2015-03-12 22:47:05 -07:00
parent 76225d2aae
commit ab15cbca8d
19 changed files with 183 additions and 55 deletions

2
.gitignore vendored
View File

@ -6,4 +6,6 @@ ChangeLog
.DS_Store .DS_Store
cue_dashboard.egg* cue_dashboard.egg*
build/ build/
.tox/
.egg/
.idea/ .idea/

View File

@ -1,5 +1,5 @@
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
PANEL_GROUP = 'queuesgroup' PANEL_GROUP = 'queuesgroup'
PANEL_GROUP_NAME = _('Message Queues') PANEL_GROUP_NAME = _('Messaging')
PANEL_GROUP_DASHBOARD = 'project' PANEL_GROUP_DASHBOARD = 'project'

View File

@ -21,14 +21,14 @@ from cueclient.v1 import client
from keystoneclient import session as ksc_session from keystoneclient import session as ksc_session
from keystoneclient.auth.identity import v2 from keystoneclient.auth.identity import v2
from collections import namedtuple from collections import namedtuple
from openstack_dashboard.api import base from openstack_dashboard import api
from horizon.utils.memoized import memoized # noqa from horizon.utils.memoized import memoized # noqa
@memoized @memoized
def cueclient(request): def cueclient(request):
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None) cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
auth_url = base.url_for(request, 'identity') auth_url = api.base.url_for(request, 'identity')
auth = v2.Token(auth_url, request.user.token.id, auth = v2.Token(auth_url, request.user.token.id,
tenant_id=request.user.tenant_id, tenant_id=request.user.tenant_id,
tenant_name=request.user.tenant_name) tenant_name=request.user.tenant_name)
@ -50,17 +50,27 @@ def cluster_get(request, cluster_id):
return cluster return cluster
def cluster_create(request, name, nic, flavor, size, volume_size): def cluster_create(request, name, nic, flavor, size):
return cueclient(request).clusters.create(name, nic,flavor, return cueclient(request).clusters.create(name, nic[0],
size, volume_size) flavor, size, 0)
def delete_cluster(request, cluster_id): def delete_cluster(request, cluster_id):
return cueclient(request).clusters.delete(cluster_id) return cueclient(request).clusters.delete(cluster_id)
#todo def flavor(request, flavor_id):
#This is needed because the cue client returns a dict return api.nova.flavor_get(request, flavor_id)
#instead of a cluster object.
# todo
# This is needed because the cue client returns a dict
# instead of a cluster object.
def _to_cluster_object(cluster_dict): def _to_cluster_object(cluster_dict):
return namedtuple('Cluster', cluster_dict)(**cluster_dict) endpoint = (cluster_dict['end_points'][0] if cluster_dict['end_points']
else None)
if endpoint:
cluster_dict['url'] = "".join((endpoint['type'],
'://',
endpoint['uri']))
return namedtuple('Cluster', cluster_dict)(**cluster_dict)

View File

@ -24,4 +24,3 @@ class CuePanel(horizon.Panel):
name = _("Clusters") name = _("Clusters")
slug = 'queues' slug = 'queues'
permissions = ('openstack.services.message_queue',) permissions = ('openstack.services.message_queue',)

View File

@ -20,6 +20,10 @@ from django.utils.translation import ungettext_lazy
from django.utils.translation import ugettext as _ from django.utils.translation import ugettext as _
from horizon import tables from horizon import tables
from cuedashboard import api from cuedashboard import api
import logging
LOG = logging.getLogger(__name__)
class CreateCluster(tables.LinkAction): class CreateCluster(tables.LinkAction):
@ -55,16 +59,23 @@ class DeleteCluster(tables.BatchAction):
api.delete_cluster(request, obj_id) api.delete_cluster(request, obj_id)
class UpdateRow(tables.Row):
ajax = True
def get_data(self, request, cluster_id):
return api.cluster_get(request, cluster_id)
class ClusterTable(tables.DataTable): class ClusterTable(tables.DataTable):
name = tables.Column("name", name = tables.Column("name",
verbose_name=_("Name"), verbose_name=_("Name"),
link='horizon:project:queues:detail') link='horizon:project:queues:detail')
size = tables.Column("size", verbose_name=_("Size"),) size = tables.Column("size", verbose_name=_("Cluster Size"),)
flavor = tables.Column("flavor", verbose_name=_("Flavor"),)
status = tables.Column("status", verbose_name=_("Status")) status = tables.Column("status", verbose_name=_("Status"))
class Meta: class Meta:
name = "clusters" name = "clusters"
verbose_name = _("Clusters") verbose_name = _("Clusters")
row_class = UpdateRow
table_actions = (CreateCluster, DeleteCluster,) table_actions = (CreateCluster, DeleteCluster,)
row_actions = (DeleteCluster,) row_actions = (DeleteCluster,)

View File

@ -14,4 +14,4 @@ class OverviewTab(tabs.Tab):
class ClusterDetailTabs(tabs.TabGroup): class ClusterDetailTabs(tabs.TabGroup):
slug = "cluster_details" slug = "cluster_details"
tabs = (OverviewTab,) tabs = (OverviewTab,)
sticky = True sticky = True

View File

@ -26,6 +26,8 @@ CLUSTERS = r'^(?P<cluster_id>[^/]+)/%s$'
urlpatterns = patterns('', urlpatterns = patterns('',
url(r'^$', views.IndexView.as_view(), url(r'^$', views.IndexView.as_view(),
name='index'), name='index'),
url(CLUSTERS % '', views.DetailView.as_view(), name='detail'), url(CLUSTERS % '', views.DetailView.as_view(),
url(r'^create$', views.CreateClusterView.as_view(), name='create'), name='detail'),
) url(r'^create$', views.CreateClusterView.as_view(),
name='create'),
)

View File

@ -36,6 +36,7 @@ LOG = logging.getLogger(__name__)
class IndexView(tables.DataTableView): class IndexView(tables.DataTableView):
table_class = ClusterTable table_class = ClusterTable
template_name = 'queues/index.html' template_name = 'queues/index.html'
page_title = _("Clusters")
def get_data(self): def get_data(self):
return api.clusters_list(self.request) return api.clusters_list(self.request)
@ -57,6 +58,8 @@ class DetailView(horizon_tabs.TabbedTableView):
cluster = self.get_data() cluster = self.get_data()
table = ClusterTable(self.request) table = ClusterTable(self.request)
context["cluster"] = cluster context["cluster"] = cluster
flavor = api.flavor(self.request, cluster.flavor)
context["flavor"] = flavor.name
context["url"] = self.get_redirect_url() context["url"] = self.get_redirect_url()
context["actions"] = table.render_row_actions(cluster) context["actions"] = table.render_row_actions(cluster)
return context return context
@ -66,16 +69,17 @@ class DetailView(horizon_tabs.TabbedTableView):
cluster_id = self.kwargs['cluster_id'] cluster_id = self.kwargs['cluster_id']
cluster = api.cluster_get(self.request, cluster_id) cluster = api.cluster_get(self.request, cluster_id)
LOG.info('hlahaha')
LOG.info(cluster)
LOG.info(type(cluster))
return cluster return cluster
def get_tabs(self, request, *args, **kwargs): def get_tabs(self, request, *args, **kwargs):
cluster = self.get_data() cluster = self.get_data()
return self.tab_group_class(request, cluster=cluster, **kwargs) # Get flavor name
flavor = api.flavor(self.request, cluster.flavor)
return self.tab_group_class(request,
cluster=flavor,
flavor=flavor.name,
**kwargs)
@staticmethod @staticmethod
def get_redirect_url(): def get_redirect_url():
return reverse('horizon:project:queues:index') return reverse('horizon:project:queues:index')

View File

@ -36,20 +36,15 @@ class SetInstanceDetailsAction(workflows.Action):
name = forms.CharField(max_length=80, label=_("Cluster Name")) name = forms.CharField(max_length=80, label=_("Cluster Name"))
flavor = forms.ChoiceField(label=_("Flavor"), flavor = forms.ChoiceField(label=_("Flavor"),
help_text=_("Size of image to launch.")) help_text=_("Size of image to launch."))
size = forms.IntegerField(label=_("Size"), size = forms.IntegerField(label=_("Cluster Size"),
min_value=0, min_value=1,
initial=1, initial=1,
help_text=_("Size of cluster.")) help_text=_("Size of cluster."))
volume = forms.IntegerField(label=_("Volume Size"),
min_value=0,
initial=1,
help_text=_("Size of the volume in GB."))
class Meta(object): class Meta(object):
name = _("Details") name = _("Details")
help_text_template = "queues/_launch_details_help.html" help_text_template = "queues/_launch_details_help.html"
@memoized.memoized_method @memoized.memoized_method
def flavors(self, request): def flavors(self, request):
try: try:
@ -70,7 +65,7 @@ class SetInstanceDetailsAction(workflows.Action):
class SetClusterDetails(workflows.Step): class SetClusterDetails(workflows.Step):
action_class = SetInstanceDetailsAction action_class = SetInstanceDetailsAction
contributes = ("name", "volume", "flavor", "size") contributes = ("name", "flavor", "size")
class SetNetworkAction(workflows.Action): class SetNetworkAction(workflows.Action):
@ -94,6 +89,13 @@ class SetNetworkAction(workflows.Action):
permissions = ('openstack.services.network',) permissions = ('openstack.services.network',)
help_text = _("Select networks for your cluster.") help_text = _("Select networks for your cluster.")
def clean(self):
# Cue does not currently support attaching multiple networks.
if len(self.data.getlist("network", None)) > 1:
msg = _("You must select only one network.")
self._errors["network"] = self.error_class([msg])
return self.cleaned_data
def populate_network_choices(self, request, context): def populate_network_choices(self, request, context):
try: try:
tenant_id = self.request.user.tenant_id tenant_id = self.request.user.tenant_id
@ -119,9 +121,9 @@ class SetNetwork(workflows.Step):
# contains an empty string, so remove it. # contains an empty string, so remove it.
networks = [n for n in networks if n != ''] networks = [n for n in networks if n != '']
if networks: if networks:
#TODO # TODO
#Choosing the first networks until Cue # Choosing the first networks until Cue
#supports more than one networks. # supports more than one networks.
context['network_id'] = networks[0] context['network_id'] = networks[0]
return context return context
@ -140,7 +142,7 @@ class CreateCluster(workflows.Workflow):
def __init__(self, request=None, context_seed=None, entry_point=None, def __init__(self, request=None, context_seed=None, entry_point=None,
*args, **kwargs): *args, **kwargs):
super(CreateCluster, self).__init__(request, context_seed, super(CreateCluster, self).__init__(request, context_seed,
entry_point, *args, **kwargs) entry_point, *args, **kwargs)
self.attrs['autocomplete'] = ( self.attrs['autocomplete'] = (
settings.HORIZON_CONFIG.get('password_autocomplete')) settings.HORIZON_CONFIG.get('password_autocomplete'))
@ -151,13 +153,13 @@ class CreateCluster(workflows.Workflow):
def handle(self, request, context): def handle(self, request, context):
try: try:
LOG.info("Launching message queue cluster with parameters " LOG.info("Launching message queue cluster with parameters "
"{name=%s, volume=%s, flavor=%s, size=%s, nics=%s}", "{name=%s, flavor=%s, size=%s, nics=%s}",
context['name'], context['volume'], context['flavor'], context['name'], context['flavor'],
context['size'], context['network_id']) context['size'], context['network_id'])
cluster_create(request, context['name'], context['network_id'], cluster_create(request, context['name'], context['network_id'],
context['flavor'], context['size'], context['volume']) context['flavor'], context['size'])
return True return True
except Exception: except Exception:
exceptions.handle(request) exceptions.handle(request)
return False return False

View File

@ -13,12 +13,10 @@
<dt>{% trans "Network" %}</dt> <dt>{% trans "Network" %}</dt>
<dd>{{ cluster.network_id|default:_("-") }}</dd> <dd>{{ cluster.network_id|default:_("-") }}</dd>
<dt>{% trans "Flavor" %}</dt> <dt>{% trans "Flavor" %}</dt>
<dd>{{ cluster.flavor|default:_("-") }}</dd> <dd>{{ flavor|default:_("-") }}</dd>
<dt>{% trans "Size" %}</dt> <dt>{% trans "Cluster Size" %}</dt>
<dd>{{ cluster.size|default:_("-") }}</dd> <dd>{{ cluster.size|default:_("-") }}</dd>
<dt>{% trans "Volume Size" %}</dt> <dt>{% trans "Endpoint" %}</dt>
<dd>{{ cluster.volume_size|default:_("-") }}</dd> <dd>{{ cluster.url|default:_("-") }}</dd>
<dt>{% trans "Endpoints" %}</dt>
<dd>{{ cluster.end_points|default:_("-") }}</dd>
</dl> </dl>
</div> </div>

View File

@ -1,4 +1,3 @@
{% load i18n %} {% load i18n %}
<p>{% blocktrans %}Specify the details for creating a message queue cluster.{% endblocktrans %}</p> <p>{% blocktrans %}Specify the details for creating a message queue cluster.{% endblocktrans %}</p>
<p>{% blocktrans %}<strong>Please note:</strong> The cluster size must be at least 3{% endblocktrans %}</p>

View File

@ -6,4 +6,5 @@
clicking the button, or dragging and dropping. You can change the clicking the button, or dragging and dropping. You can change the
NIC order by dragging and dropping as well. NIC order by dragging and dropping as well.
{% endblocktrans %} {% endblocktrans %}
</p> </p>
<p>{% blocktrans %}<strong>Please note:</strong> You can only choose one network per cluster.{% endblocktrans %}</p>

View File

@ -4,7 +4,7 @@
<table class="table-fixed" id="networkListSortContainer"> <table class="table-fixed" id="networkListSortContainer">
<tbody> <tbody>
<tr> <tr>
<td class="actions"> <td class="col-sm-6">
<label id="selected_network_label">{% trans "Selected networks" %}</label> <label id="selected_network_label">{% trans "Selected networks" %}</label>
<ul id="selected_network" class="networklist"> <ul id="selected_network" class="networklist">
</ul> </ul>
@ -12,7 +12,7 @@
<ul id="available_network" class="networklist"> <ul id="available_network" class="networklist">
</ul> </ul>
</td> </td>
<td class="help_text"> <td class="col-sm-6">
{% include "queues/_launch_network_help.html" %} {% include "queues/_launch_network_help.html" %}
</td> </td>
</tr> </tr>

View File

@ -1,6 +1,6 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Queues" %}{% endblock %} {% block title %}{% trans "Clusters" %}{% endblock %}
{% block main %} {% block main %}
{{ table.render }} {{ table.render }}

70
doc/source/conf.py Normal file
View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
# 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 os
import sys
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
'oslosphinx',
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'cue-dashboard'
copyright = u'2015, OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
# html_theme_path = ["."]
# html_theme = '_theme'
# html_static_path = []
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# -- Options for manual page output -------------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = []
# If true, show URL addresses after external links.
man_show_urls = True

1
doc/source/index.rst Normal file
View File

@ -0,0 +1 @@
.. include:: ../../README.rst

View File

@ -1,2 +1,2 @@
Django>=1.4,<1.7 Django>=1.4,<1.7
python-cueclient -e git+https://github.com/stackforge/python-cueclient.git#egg=python-cueclient

4
test-requirements.txt Normal file
View File

@ -0,0 +1,4 @@
pep8==1.5.7
flake8==2.2.3
sphinx>=1.1.2,!=1.2.0,<1.3
oslosphinx>=2.2.0 # Apache-2.0

25
tox.ini Normal file
View File

@ -0,0 +1,25 @@
[tox]
envlist = pep8
minversion = 1.6
skipsdist = True
[testenv]
usedevelop = True
setenv = VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt
[testenv:pep8]
commands = flake8 {posargs}
[testenv:venv]
commands = {posargs}
[testenv:docs]
commands = python setup.py build_sphinx
[flake8]
show-source = true
ignore = H101,H302,H803
builtins = _
exclude = .venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,tools,local_settings.py