summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--horizon/middleware.py19
-rw-r--r--horizon/static/horizon/js/horizon.tables.js32
-rw-r--r--horizon/utils/urlresolvers.py30
-rw-r--r--openstack_dashboard/api/neutron.py80
-rw-r--r--openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html2
-rw-r--r--openstack_dashboard/dashboards/admin/instances/forms.py7
-rw-r--r--openstack_dashboard/dashboards/admin/instances/tests.py40
-rw-r--r--openstack_dashboard/dashboards/admin/instances/views.py4
-rw-r--r--openstack_dashboard/dashboards/admin/metering/templates/metering/daily.html1
-rw-r--r--openstack_dashboard/dashboards/admin/projects/tests.py14
-rw-r--r--openstack_dashboard/dashboards/admin/projects/workflows.py14
-rw-r--r--openstack_dashboard/dashboards/project/containers/tables.py31
-rw-r--r--openstack_dashboard/dashboards/project/containers/tests.py72
-rw-r--r--openstack_dashboard/dashboards/project/containers/views.py56
-rw-r--r--openstack_dashboard/dashboards/project/networks/workflows.py6
-rw-r--r--openstack_dashboard/dashboards/settings/user/tests.py1
-rw-r--r--openstack_dashboard/policy.py4
-rw-r--r--openstack_dashboard/test/api_tests/network_tests.py99
-rw-r--r--openstack_dashboard/test/api_tests/neutron_tests.py34
-rw-r--r--openstack_dashboard/test/test_data/exceptions.py2
-rw-r--r--openstack_dashboard/test/test_data/nova_data.py10
-rw-r--r--openstack_dashboard/usage/quotas.py6
-rw-r--r--openstack_dashboard/views.py2
-rw-r--r--requirements.txt36
-rw-r--r--setup.cfg2
-rw-r--r--test-requirements.txt8
26 files changed, 435 insertions, 177 deletions
diff --git a/horizon/middleware.py b/horizon/middleware.py
index e4b72b29f..3cdb36e6f 100644
--- a/horizon/middleware.py
+++ b/horizon/middleware.py
@@ -49,6 +49,17 @@ class HorizonMiddleware(object):
def process_request(self, request):
"""Adds data necessary for Horizon to function to the request."""
+
+ request.horizon = {'dashboard': None,
+ 'panel': None,
+ 'async_messages': []}
+ if not hasattr(request, "user") or not request.user.is_authenticated():
+ # proceed no further if the current request is already known
+ # not to be authenticated
+ # it is CRITICAL to perform this check as early as possible
+ # to avoid creating too many sessions
+ return None
+
# Activate timezone handling
tz = request.session.get('django_timezone')
if tz:
@@ -62,14 +73,6 @@ class HorizonMiddleware(object):
last_activity = request.session.get('last_activity', None)
timestamp = int(time.time())
- request.horizon = {'dashboard': None,
- 'panel': None,
- 'async_messages': []}
-
- if not hasattr(request, "user") or not request.user.is_authenticated():
- # proceed no further if the current request is already known
- # not to be authenticated
- return None
# If we use cookie-based sessions, check that the cookie size does not
# reach the max size accepted by common web browsers.
diff --git a/horizon/static/horizon/js/horizon.tables.js b/horizon/static/horizon/js/horizon.tables.js
index 2788119ca..f927d7c14 100644
--- a/horizon/static/horizon/js/horizon.tables.js
+++ b/horizon/static/horizon/js/horizon.tables.js
@@ -1,8 +1,9 @@
/* Namespace for core functionality related to DataTables. */
horizon.datatables = {
update: function () {
- var $rows_to_update = $('tr.status_unknown.ajax-update');
- if ($rows_to_update.length) {
+ var $rows_to_update = $('tr.status_unknown.ajax-update'),
+ rows_to_update = $rows_to_update.length;
+ if ( rows_to_update > 0 ) {
var interval = $rows_to_update.attr('data-update-interval'),
$table = $rows_to_update.closest('table'),
decay_constant = $table.attr('decay_constant');
@@ -93,19 +94,22 @@ horizon.datatables = {
complete: function (jqXHR, textStatus) {
// Revalidate the button check for the updated table
horizon.datatables.validate_button();
-
- // Set interval decay to this table, and increase if it already exist
- if(decay_constant === undefined) {
- decay_constant = 1;
- } else {
- decay_constant++;
+ rows_to_update--;
+ // Schedule next poll when all the rows are updated
+ if ( rows_to_update === 0 ) {
+ // Set interval decay to this table, and increase if it already exist
+ if(decay_constant === undefined) {
+ decay_constant = 1;
+ } else {
+ decay_constant++;
+ }
+ $table.attr('decay_constant', decay_constant);
+ // Poll until there are no rows in an "unknown" state on the page.
+ var next_poll = interval * decay_constant;
+ // Limit the interval to 30 secs
+ if(next_poll > 30 * 1000) { next_poll = 30 * 1000; }
+ setTimeout(horizon.datatables.update, next_poll);
}
- $table.attr('decay_constant', decay_constant);
- // Poll until there are no rows in an "unknown" state on the page.
- next_poll = interval * decay_constant;
- // Limit the interval to 30 secs
- if(next_poll > 30 * 1000) { next_poll = 30 * 1000; }
- setTimeout(horizon.datatables.update, next_poll);
}
});
});
diff --git a/horizon/utils/urlresolvers.py b/horizon/utils/urlresolvers.py
new file mode 100644
index 000000000..2f9d02a46
--- /dev/null
+++ b/horizon/utils/urlresolvers.py
@@ -0,0 +1,30 @@
+# Copyright 2012 Nebula, Inc.
+#
+# 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 six
+
+from django.core.urlresolvers import reverse as django_reverse
+from django.utils.http import urlquote # noqa
+from django import VERSION # noqa
+
+
+def reverse(viewname, urlconf=None, args=None, kwargs=None, prefix=None,
+ current_app=None):
+ if VERSION < (1, 6):
+ if args:
+ args = [urlquote(x) for x in args]
+ if kwargs:
+ kwargs = dict([(x, urlquote(y)) for x, y in six.iteritems(kwargs)])
+ return django_reverse(viewname, urlconf, args, kwargs, prefix,
+ current_app)
diff --git a/openstack_dashboard/api/neutron.py b/openstack_dashboard/api/neutron.py
index 9c7fecbdc..ddbd0af8d 100644
--- a/openstack_dashboard/api/neutron.py
+++ b/openstack_dashboard/api/neutron.py
@@ -29,6 +29,9 @@ from django.conf import settings
from django.utils.datastructures import SortedDict
from django.utils.translation import ugettext_lazy as _
+from neutronclient.common import exceptions as neutron_exc
+from neutronclient.v2_0 import client as neutron_client
+
from horizon import messages
from horizon.utils.memoized import memoized # noqa
@@ -36,7 +39,6 @@ from openstack_dashboard.api import base
from openstack_dashboard.api import network_base
from openstack_dashboard.api import nova
-from neutronclient.v2_0 import client as neutron_client
LOG = logging.getLogger(__name__)
@@ -442,6 +444,60 @@ def neutronclient(request):
return c
+def list_resources_with_long_filters(list_method,
+ filter_attr, filter_values, **params):
+ """List neutron resources with handling RequestURITooLong exception.
+
+ If filter parameters are long, list resources API request leads to
+ 414 error (URL is too long). For such case, this method split
+ list parameters specified by a list_field argument into chunks
+ and call the specified list_method repeatedly.
+
+ :param list_method: Method used to retrieve resource list.
+ :param filter_attr: attribute name to be filtered. The value corresponding
+ to this attribute is specified by "filter_values".
+ If you want to specify more attributes for a filter condition,
+ pass them as keyword arguments like "attr2=values2".
+ :param filter_values: values of "filter_attr" to be filtered.
+ If filter_values are too long and the total URI lenght exceed the
+ maximum lenght supported by the neutron server, filter_values will
+ be split into sub lists if filter_values is a list.
+ :param params: parameters to pass a specified listing API call
+ without any changes. You can specify more filter conditions
+ in addition to a pair of filter_attr and filter_values.
+ """
+ try:
+ params[filter_attr] = filter_values
+ return list_method(**params)
+ except neutron_exc.RequestURITooLong as uri_len_exc:
+ # The URI is too long because of too many filter values.
+ # Use the excess attribute of the exception to know how many
+ # filter values can be inserted into a single request.
+
+ # We consider only the filter condition from (filter_attr,
+ # filter_values) and do not consider other filter conditions
+ # which may be specified in **params.
+ if type(filter_values) != list:
+ filter_values = [filter_values]
+
+ # Length of each query filter is:
+ # <key>=<value>& (e.g., id=<uuid>)
+ # The length will be key_len + value_maxlen + 2
+ all_filter_len = sum(len(filter_attr) + len(val) + 2
+ for val in filter_values)
+ allowed_filter_len = all_filter_len - uri_len_exc.excess
+
+ val_maxlen = max(len(val) for val in filter_values)
+ filter_maxlen = len(filter_attr) + val_maxlen + 2
+ chunk_size = allowed_filter_len / filter_maxlen
+
+ resources = []
+ for i in range(0, len(filter_values), chunk_size):
+ params[filter_attr] = filter_values[i:i + chunk_size]
+ resources.extend(list_method(**params))
+ return resources
+
+
def network_list(request, **params):
LOG.debug("network_list(): params=%s" % (params))
networks = neutronclient(request).list_networks(**params).get('networks')
@@ -485,6 +541,7 @@ def network_get(request, network_id, expand_subnet=True, **params):
if expand_subnet:
network['subnets'] = [subnet_get(request, sid)
for sid in network['subnets']]
+
return Network(network)
@@ -745,7 +802,7 @@ def provider_list(request):
return providers['service_providers']
-def servers_update_addresses(request, servers):
+def servers_update_addresses(request, servers, all_tenants=False):
"""Retrieve servers networking information from Neutron if enabled.
Should be used when up to date networking information is required,
@@ -754,12 +811,19 @@ def servers_update_addresses(request, servers):
# Get all (filtered for relevant servers) information from Neutron
try:
- ports = port_list(request,
- device_id=[instance.id for instance in servers])
- floating_ips = FloatingIpManager(request).list(
- port_id=[port.id for port in ports])
- networks = network_list(request,
- id=[port.network_id for port in ports])
+ ports = list_resources_with_long_filters(
+ port_list, 'device_id', [instance.id for instance in servers],
+ request=request)
+ fips = FloatingIpManager(request)
+ if fips.is_supported():
+ floating_ips = list_resources_with_long_filters(
+ fips.list, 'port_id', [port.id for port in ports],
+ all_tenants=all_tenants)
+ else:
+ floating_ips = []
+ networks = list_resources_with_long_filters(
+ network_list, 'id', set([port.network_id for port in ports]),
+ request=request)
except Exception:
error_message = _('Unable to connect to Neutron.')
LOG.error(error_message)
diff --git a/openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html b/openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html
index e590462b8..e524dfa10 100644
--- a/openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html
+++ b/openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html
@@ -20,7 +20,7 @@
<div class="d3_quota_bar">
<div class="d3_pie_chart_usage" data-used="{% widthratio stats.memory_mb_used stats.memory_mb 100 %}"></div>
<strong>{% trans "Memory Usage" %} <br />
- {% blocktrans with used=stats.memory_mb_used|mbformat available=stats.memory_mb|mbformat %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
+ {% blocktrans with used=stats.memory_mb_used|mb_float_format available=stats.memory_mb|mb_float_format %}Used <span> {{ used }} </span> of <span> {{ available }} </span>{% endblocktrans %}
</strong>
</div>
diff --git a/openstack_dashboard/dashboards/admin/instances/forms.py b/openstack_dashboard/dashboards/admin/instances/forms.py
index b13dce6d7..86da60127 100644
--- a/openstack_dashboard/dashboards/admin/instances/forms.py
+++ b/openstack_dashboard/dashboards/admin/instances/forms.py
@@ -50,10 +50,11 @@ class LiveMigrateForm(forms.SelfHandlingForm):
def populate_host_choices(self, request, initial):
hosts = initial.get('hosts')
current_host = initial.get('current_host')
- host_list = [(host.hypervisor_hostname,
- host.hypervisor_hostname)
+ host_list = [(host.host_name,
+ host.host_name)
for host in hosts
- if host.service['host'] != current_host]
+ if host.service.startswith('compute') and
+ host.host_name != current_host]
if host_list:
host_list.insert(0, ("", _("Select a new host")))
else:
diff --git a/openstack_dashboard/dashboards/admin/instances/tests.py b/openstack_dashboard/dashboards/admin/instances/tests.py
index abb98af84..64276f8f6 100644
--- a/openstack_dashboard/dashboards/admin/instances/tests.py
+++ b/openstack_dashboard/dashboards/admin/instances/tests.py
@@ -221,14 +221,14 @@ class InstanceViewTest(test.BaseAdminViewTests):
self.assertContains(res, "instances__revert")
self.assertNotContains(res, "instances__migrate")
- @test.create_stubs({api.nova: ('hypervisor_list',
+ @test.create_stubs({api.nova: ('host_list',
'server_get',)})
def test_instance_live_migrate_get(self):
server = self.servers.first()
api.nova.server_get(IsA(http.HttpRequest), server.id) \
.AndReturn(server)
- api.nova.hypervisor_list(IsA(http.HttpRequest)) \
- .AndReturn(self.hypervisors.list())
+ api.nova.host_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.hosts.list())
self.mox.ReplayAll()
@@ -252,13 +252,13 @@ class InstanceViewTest(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
- @test.create_stubs({api.nova: ('hypervisor_list',
+ @test.create_stubs({api.nova: ('host_list',
'server_get',)})
def test_instance_live_migrate_list_hypervisor_get_exception(self):
server = self.servers.first()
api.nova.server_get(IsA(http.HttpRequest), server.id) \
.AndReturn(server)
- api.nova.hypervisor_list(IsA(http.HttpRequest)) \
+ api.nova.host_list(IsA(http.HttpRequest)) \
.AndRaise(self.exceptions.nova)
self.mox.ReplayAll()
@@ -268,14 +268,14 @@ class InstanceViewTest(test.BaseAdminViewTests):
self.assertRedirectsNoFollow(res, INDEX_URL)
- @test.create_stubs({api.nova: ('hypervisor_list',
+ @test.create_stubs({api.nova: ('host_list',
'server_get',)})
def test_instance_live_migrate_list_hypervisor_without_current(self):
server = self.servers.first()
api.nova.server_get(IsA(http.HttpRequest), server.id) \
.AndReturn(server)
- api.nova.hypervisor_list(IsA(http.HttpRequest)) \
- .AndReturn(self.hypervisors.list())
+ api.nova.host_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.hosts.list())
self.mox.ReplayAll()
@@ -283,24 +283,25 @@ class InstanceViewTest(test.BaseAdminViewTests):
args=[server.id])
res = self.client.get(url)
self.assertNotContains(
- res, "<option value=\"devstack003\">devstack003</option>")
+ res, "<option value=\"instance-host\">devstack004</option>")
self.assertContains(
res, "<option value=\"devstack001\">devstack001</option>")
- self.assertContains(
+ self.assertNotContains(
res, "<option value=\"devstack002\">devstack002</option>")
+ self.assertContains(
+ res, "<option value=\"devstack003\">devstack003</option>")
- @test.create_stubs({api.nova: ('hypervisor_list',
+ @test.create_stubs({api.nova: ('host_list',
'server_get',
'server_live_migrate',)})
def test_instance_live_migrate_post(self):
server = self.servers.first()
- hypervisor = self.hypervisors.first()
- host = hypervisor.hypervisor_hostname
+ host = self.hosts.first().host_name
api.nova.server_get(IsA(http.HttpRequest), server.id) \
.AndReturn(server)
- api.nova.hypervisor_list(IsA(http.HttpRequest)) \
- .AndReturn(self.hypervisors.list())
+ api.nova.host_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.hosts.list())
api.nova.server_live_migrate(IsA(http.HttpRequest), server.id, host,
block_migration=False,
disk_over_commit=False) \
@@ -314,18 +315,17 @@ class InstanceViewTest(test.BaseAdminViewTests):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
- @test.create_stubs({api.nova: ('hypervisor_list',
+ @test.create_stubs({api.nova: ('host_list',
'server_get',
'server_live_migrate',)})
def test_instance_live_migrate_post_api_exception(self):
server = self.servers.first()
- hypervisor = self.hypervisors.first()
- host = hypervisor.hypervisor_hostname
+ host = self.hosts.first().host_name
api.nova.server_get(IsA(http.HttpRequest), server.id) \
.AndReturn(server)
- api.nova.hypervisor_list(IsA(http.HttpRequest)) \
- .AndReturn(self.hypervisors.list())
+ api.nova.host_list(IsA(http.HttpRequest)) \
+ .AndReturn(self.hosts.list())
api.nova.server_live_migrate(IsA(http.HttpRequest), server.id, host,
block_migration=False,
disk_over_commit=False) \
diff --git a/openstack_dashboard/dashboards/admin/instances/views.py b/openstack_dashboard/dashboards/admin/instances/views.py
index 54f41ce1b..4d2358d7e 100644
--- a/openstack_dashboard/dashboards/admin/instances/views.py
+++ b/openstack_dashboard/dashboards/admin/instances/views.py
@@ -143,10 +143,10 @@ class LiveMigrateView(forms.ModalFormView):
@memoized.memoized_method
def get_hosts(self, *args, **kwargs):
try:
- return api.nova.hypervisor_list(self.request)
+ return api.nova.host_list(self.request)
except Exception:
redirect = reverse("horizon:admin:instances:index")
- msg = _('Unable to retrieve hypervisor information.')
+ msg = _('Unable to retrieve host information.')
exceptions.handle(self.request, msg, redirect=redirect)
@memoized.memoized_method
diff --git a/openstack_dashboard/dashboards/admin/metering/templates/metering/daily.html b/openstack_dashboard/dashboards/admin/metering/templates/metering/daily.html
index ea85ea64d..aaddef906 100644
--- a/openstack_dashboard/dashboards/admin/metering/templates/metering/daily.html
+++ b/openstack_dashboard/dashboards/admin/metering/templates/metering/daily.html
@@ -1,4 +1,5 @@
{% load i18n %}
+{% load url from future %}
<div id="ceilometer-report">
<form class="form-horizontal" action="{% url 'horizon:admin:metering:report' %}" method="POST">
{% csrf_token %}
diff --git a/openstack_dashboard/dashboards/admin/projects/tests.py b/openstack_dashboard/dashboards/admin/projects/tests.py
index 1cd563092..f7dbc57d5 100644
--- a/openstack_dashboard/dashboards/admin/projects/tests.py
+++ b/openstack_dashboard/dashboards/admin/projects/tests.py
@@ -272,7 +272,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
'get_disabled_quotas'),
api.cinder: ('tenant_quota_update',),
api.nova: ('tenant_quota_update',)})
- def test_add_project_post(self):
+ def test_add_project_post(self, neutron=False):
project = self.tenants.first()
quota = self.quotas.first()
default_role = self.roles.first()
@@ -285,6 +285,9 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
# init
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first())
+ if neutron:
+ quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
+ .AndReturn(self.disabled_quotas.first())
quotas.get_default_quota_data(IsA(http.HttpRequest)).AndReturn(quota)
api.keystone.get_default_role(IsA(http.HttpRequest)) \
@@ -363,7 +366,7 @@ class CreateProjectWorkflowTests(test.BaseAdminViewTests):
api.neutron.tenant_quota_update(IsA(http.HttpRequest),
self.tenant.id,
**neutron_updated_quota)
- self.test_add_project_post()
+ self.test_add_project_post(neutron=True)
@test.create_stubs({api.keystone: ('user_list',
'role_list',
@@ -817,7 +820,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.cinder: ('tenant_quota_update',),
quotas: ('get_tenant_quota_data',
'get_disabled_quotas')})
- def test_update_project_save(self):
+ def test_update_project_save(self, neutron=False):
project = self.tenants.first()
quota = self.quotas.first()
default_role = self.roles.first()
@@ -836,6 +839,9 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
.AndReturn(self.domain)
quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
.AndReturn(self.disabled_quotas.first())
+ if neutron:
+ quotas.get_disabled_quotas(IsA(http.HttpRequest)) \
+ .AndReturn(self.disabled_quotas.first())
quotas.get_tenant_quota_data(IsA(http.HttpRequest),
tenant_id=self.tenant.id) \
.AndReturn(quota)
@@ -1017,7 +1023,7 @@ class UpdateProjectWorkflowTests(test.BaseAdminViewTests):
api.neutron.tenant_quota_update(IsA(http.HttpRequest),
self.tenant.id,
**neutron_updated_quota)
- self.test_update_project_save()
+ self.test_update_project_save(neutron=True)
@test.create_stubs({api.keystone: ('tenant_get',)})
def test_update_project_get_error(self):
diff --git a/openstack_dashboard/dashboards/admin/projects/workflows.py b/openstack_dashboard/dashboards/admin/projects/workflows.py
index e0b712087..8e60a2053 100644
--- a/openstack_dashboard/dashboards/admin/projects/workflows.py
+++ b/openstack_dashboard/dashboards/admin/projects/workflows.py
@@ -461,8 +461,11 @@ class CreateProject(workflows.Workflow):
if api.base.is_service_enabled(request, 'network') and \
api.neutron.is_quotas_extension_supported(request):
- neutron_data = dict([(key, data[key]) for key in
- quotas.NEUTRON_QUOTA_FIELDS])
+ neutron_data = {}
+ disabled_quotas = quotas.get_disabled_quotas(request)
+ for key in quotas.NEUTRON_QUOTA_FIELDS:
+ if key not in disabled_quotas:
+ neutron_data[key] = data[key]
api.neutron.tenant_quota_update(request,
project_id,
**neutron_data)
@@ -723,8 +726,11 @@ class UpdateProject(workflows.Workflow):
if api.base.is_service_enabled(request, 'network') and \
api.neutron.is_quotas_extension_supported(request):
- neutron_data = dict([(key, data[key]) for key in
- quotas.NEUTRON_QUOTA_FIELDS])
+ neutron_data = {}
+ disabled_quotas = quotas.get_disabled_quotas(request)
+ for key in quotas.NEUTRON_QUOTA_FIELDS:
+ if key not in disabled_quotas:
+ neutron_data[key] = data[key]
api.neutron.tenant_quota_update(request,
project_id,
**neutron_data)
diff --git a/openstack_dashboard/dashboards/project/containers/tables.py b/openstack_dashboard/dashboards/project/containers/tables.py
index 62912e2b6..fb0262d36 100644
--- a/openstack_dashboard/dashboards/project/containers/tables.py
+++ b/openstack_dashboard/dashboards/project/containers/tables.py
@@ -15,7 +15,6 @@
# under the License.
import logging
-from django.core.urlresolvers import reverse
from django import shortcuts
from django import template
from django.template import defaultfilters as filters
@@ -26,6 +25,7 @@ from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import messages
from horizon import tables
+from horizon.utils.urlresolvers import reverse # noqa
from openstack_dashboard import api
from openstack_dashboard.api import swift
@@ -49,8 +49,7 @@ class ViewContainer(tables.LinkAction):
def get_link_url(self, datum=None):
obj_id = self.table.get_object_id(datum)
- args = (http.urlquote(obj_id),)
- return reverse(self.url, args=args)
+ return reverse(self.url, args=(obj_id,))
class MakePublicContainer(tables.Action):
@@ -157,8 +156,7 @@ class CreatePseudoFolder(tables.LinkAction):
else:
container_name = self.table.kwargs['container_name']
subfolders = self.table.kwargs.get('subfolder_path', '')
- args = (http.urlquote(bit) for bit in
- (container_name, subfolders) if bit)
+ args = (bit for bit in (container_name, subfolders) if bit)
return reverse(self.url, args=args)
def allowed(self, request, datum=None):
@@ -182,13 +180,12 @@ class UploadObject(tables.LinkAction):
# Usable for both the container and object tables
if getattr(datum, 'container', datum):
# This is a container
- container_name = http.urlquote(datum.name)
+ container_name = datum.name
else:
# This is a table action, and we already have the container name
container_name = self.table.kwargs['container_name']
subfolders = self.table.kwargs.get('subfolder_path', '')
- args = (http.urlquote(bit) for bit in
- (container_name, subfolders) if bit)
+ args = (bit for bit in (container_name, subfolders) if bit)
return reverse(self.url, args=args)
def allowed(self, request, datum=None):
@@ -208,7 +205,7 @@ def get_size_used(container):
def get_container_link(container):
return reverse("horizon:project:containers:index",
- args=(http.urlquote(wrap_delimiter(container.name)),))
+ args=(wrap_delimiter(container.name),))
class ContainerAjaxUpdateRow(tables.Row):
@@ -290,8 +287,7 @@ class ViewObject(tables.LinkAction):
def get_link_url(self, obj):
container_name = self.table.kwargs['container_name']
- return reverse(self.url, args=(http.urlquote(container_name),
- http.urlquote(obj.name)))
+ return reverse(self.url, args=(container_name, obj.name))
class UpdateObject(tables.LinkAction):
@@ -303,8 +299,7 @@ class UpdateObject(tables.LinkAction):
def get_link_url(self, obj):
container_name = self.table.kwargs['container_name']
- return reverse(self.url, args=(http.urlquote(container_name),
- http.urlquote(obj.name)))
+ return reverse(self.url, args=(container_name, obj.name))
class DeleteObject(tables.DeleteAction):
@@ -339,8 +334,7 @@ class CopyObject(tables.LinkAction):
def get_link_url(self, obj):
container_name = self.table.kwargs['container_name']
- return reverse(self.url, args=(http.urlquote(container_name),
- http.urlquote(obj.name)))
+ return reverse(self.url, args=(container_name, obj.name))
class DownloadObject(tables.LinkAction):
@@ -352,8 +346,7 @@ class DownloadObject(tables.LinkAction):
def get_link_url(self, obj):
container_name = self.table.kwargs['container_name']
- return reverse(self.url, args=(http.urlquote(container_name),
- http.urlquote(obj.name)))
+ return reverse(self.url, args=(container_name, obj.name))
def allowed(self, request, object):
return object.bytes and object.bytes > 0
@@ -400,8 +393,8 @@ def get_size(obj):
def get_link_subfolder(subfolder):
container_name = subfolder.container_name
return reverse("horizon:project:containers:index",
- args=(http.urlquote(wrap_delimiter(container_name)),
- http.urlquote(wrap_delimiter(subfolder.name))))
+ args=(wrap_delimiter(container_name),
+ wrap_delimiter(subfolder.name)))
class ObjectsTable(tables.DataTable):
diff --git a/openstack_dashboard/dashboards/project/containers/tests.py b/openstack_dashboard/dashboards/project/containers/tests.py
index 7063a1ec9..2822ab323 100644
--- a/openstack_dashboard/dashboards/project/containers/tests.py
+++ b/openstack_dashboard/dashboards/project/containers/tests.py
@@ -21,7 +21,6 @@
import tempfile
from django.core.files.uploadedfile import InMemoryUploadedFile # noqa
-from django.core.urlresolvers import reverse
from django import http
from django.utils import http as utils_http
@@ -30,18 +29,39 @@ from mox import IsA # noqa
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.containers import forms
from openstack_dashboard.dashboards.project.containers import tables
-from openstack_dashboard.dashboards.project.containers import views
from openstack_dashboard.test import helpers as test
+from horizon.utils.urlresolvers import reverse # noqa
+
CONTAINER_NAME_1 = u"container one%\u6346"
CONTAINER_NAME_2 = u"container_two\u6346"
CONTAINER_NAME_1_QUOTED = utils_http.urlquote(CONTAINER_NAME_1)
CONTAINER_NAME_2_QUOTED = utils_http.urlquote(CONTAINER_NAME_2)
+INVALID_CONTAINER_NAME_1 = utils_http.urlquote(CONTAINER_NAME_1_QUOTED)
+INVALID_CONTAINER_NAME_2 = utils_http.urlquote(CONTAINER_NAME_2_QUOTED)
CONTAINER_INDEX_URL = reverse('horizon:project:containers:index')
+INVALID_PATHS = []
+
+
+def invalid_paths():
+ if not INVALID_PATHS:
+ for x in (CONTAINER_NAME_1_QUOTED, CONTAINER_NAME_2_QUOTED):
+ y = reverse('horizon:project:containers:index',
+ args=(tables.wrap_delimiter(x), ))
+ INVALID_PATHS.append(y)
+ for x in (CONTAINER_NAME_1, CONTAINER_NAME_2):
+ INVALID_PATHS.append(CONTAINER_INDEX_URL + x)
+ return INVALID_PATHS
+
class SwiftTests(test.TestCase):
+
+ def _test_invalid_paths(self, response):
+ for x in invalid_paths():
+ self.assertNotContains(response, x)
+
@test.create_stubs({api.swift: ('swift_get_containers',)})
def test_index_no_container_selected(self):
containers = self.containers.list()
@@ -107,8 +127,7 @@ class SwiftTests(test.TestCase):
'method': forms.CreateContainer.__name__}
res = self.client.post(
reverse('horizon:project:containers:create'), formData)
- args = (utils_http.urlquote(tables.wrap_delimiter(
- container.name)),)
+ args = (tables.wrap_delimiter(container.name),)
url = reverse('horizon:project:containers:index', args=args)
self.assertRedirectsNoFollow(res, url)
@@ -167,6 +186,7 @@ class SwiftTests(test.TestCase):
form_action = ' action="%s%s/" ' % (CONTAINER_INDEX_URL,
CONTAINER_NAME_1_QUOTED)
self.assertContains(res, form_action, count=2)
+ self._test_invalid_paths(res)
@test.create_stubs({api.swift: ('swift_upload_object',)})
def test_upload(self):
@@ -190,9 +210,8 @@ class SwiftTests(test.TestCase):
res = self.client.get(upload_url)
self.assertTemplateUsed(res, 'project/containers/upload.html')
-
- res = self.client.get(upload_url)
self.assertContains(res, 'enctype="multipart/form-data"')
+ self._test_invalid_paths(res)
formData = {'method': forms.UploadObject.__name__,
'container_name': container.name,
@@ -200,7 +219,7 @@ class SwiftTests(test.TestCase):
'object_file': temp_file}
res = self.client.post(upload_url, formData)
- args = (utils_http.urlquote(tables.wrap_delimiter(container.name)),)
+ args = (tables.wrap_delimiter(container.name),)
index_url = reverse('horizon:project:containers:index', args=args)
self.assertRedirectsNoFollow(res, index_url)
@@ -223,6 +242,8 @@ class SwiftTests(test.TestCase):
res = self.client.get(upload_url)
self.assertContains(res, 'enctype="multipart/form-data"')
+ self.assertNotContains(res, INVALID_CONTAINER_NAME_1)
+ self.assertNotContains(res, INVALID_CONTAINER_NAME_2)
formData = {'method': forms.UploadObject.__name__,
'container_name': container.name,
@@ -230,7 +251,7 @@ class SwiftTests(test.TestCase):
'object_file': None}
res = self.client.post(upload_url, formData)
- args = (utils_http.urlquote(tables.wrap_delimiter(container.name)),)
+ args = (tables.wrap_delimiter(container.name),)
index_url = reverse('horizon:project:containers:index', args=args)
self.assertRedirectsNoFollow(res, index_url)
@@ -251,6 +272,7 @@ class SwiftTests(test.TestCase):
res = self.client.get(create_pseudo_folder_url)
self.assertTemplateUsed(res,
'project/containers/create_pseudo_folder.html')
+ self._test_invalid_paths(res)
formData = {'method': forms.CreatePseudoFolder.__name__,
'container_name': container.name,
@@ -266,7 +288,7 @@ class SwiftTests(test.TestCase):
def test_delete(self):
container = self.containers.first()
obj = self.objects.first()
- args = (utils_http.urlquote(tables.wrap_delimiter(container.name)),)
+ args = (tables.wrap_delimiter(container.name),)
index_url = reverse('horizon:project:containers:index', args=args)
api.swift.swift_delete_object(IsA(http.HttpRequest),
container.name,
@@ -285,7 +307,7 @@ class SwiftTests(test.TestCase):
def test_delete_pseudo_folder(self):
container = self.containers.first()
folder = self.folder.first()
- args = (utils_http.urlquote(tables.wrap_delimiter(container.name)),)
+ args = (tables.wrap_delimiter(container.name),)
index_url = reverse('horizon:project:containers:index', args=args)
api.swift.swift_delete_object(IsA(http.HttpRequest),
container.name,
@@ -317,6 +339,9 @@ class SwiftTests(test.TestCase):
res = self.client.get(download_url)
self.assertEqual(res.content, obj.data)
self.assertTrue(res.has_header('Content-Disposition'))
+ self.assertNotContains(res, INVALID_CONTAINER_NAME_1)
+ self.assertNotContains(res, INVALID_CONTAINER_NAME_2)
+
# Check that the returned Content-Disposition filename is well
# surrounded by double quotes and with commas removed
expected_name = '"%s"' % obj.name.replace(
@@ -336,6 +361,8 @@ class SwiftTests(test.TestCase):
args=[self.containers.first().name,
self.objects.first().name]))
self.assertTemplateUsed(res, 'project/containers/copy.html')
+ self.assertNotContains(res, INVALID_CONTAINER_NAME_1)
+ self.assertNotContains(res, INVALID_CONTAINER_NAME_2)
@test.create_stubs({api.swift: ('swift_get_containers',
'swift_copy_object')})
@@ -361,7 +388,7 @@ class SwiftTests(test.TestCase):
copy_url = reverse('horizon:project:containers:object_copy',
args=[container_1.name, obj.name])
res = self.client.post(copy_url, formData)
- args = (utils_http.urlquote(tables.wrap_delimiter(container_2.name)),)
+ args = (tables.wrap_delimiter(container_2.name),)
index_url = reverse('horizon:project:containers:index', args=args)
self.assertRedirectsNoFollow(res, index_url)
@@ -387,9 +414,8 @@ class SwiftTests(test.TestCase):
res = self.client.get(update_url)
self.assertTemplateUsed(res, 'project/containers/update.html')
-
- res = self.client.get(update_url)
self.assertContains(res, 'enctype="multipart/form-data"')
+ self._test_invalid_paths(res)
formData = {'method': forms.UpdateObject.__name__,
'container_name': container.name,
@@ -397,7 +423,7 @@ class SwiftTests(test.TestCase):
'object_file': temp_file}
res = self.client.post(update_url, formData)
- args = (utils_http.urlquote(tables.wrap_delimiter(container.name)),)
+ args = (tables.wrap_delimiter(container.name),)
index_url = reverse('horizon:project:containers:index', args=args)
self.assertRedirectsNoFollow(res, index_url)
@@ -413,16 +439,15 @@ class SwiftTests(test.TestCase):
res = self.client.get(update_url)
self.assertTemplateUsed(res, 'project/containers/update.html')
-
- res = self.client.get(update_url)
self.assertContains(res, 'enctype="multipart/form-data"')
+ self._test_invalid_paths(res)
formData = {'method': forms.UpdateObject.__name__,
'container_name': container.name,
'name': obj.name}
res = self.client.post(update_url, formData)
- args = (utils_http.urlquote(tables.wrap_delimiter(container.name)),)
+ args = (tables.wrap_delimiter(container.name),)
index_url = reverse('horizon:project:containers:index', args=args)
self.assertRedirectsNoFollow(res, index_url)
@@ -443,6 +468,8 @@ class SwiftTests(test.TestCase):
self.assertTemplateUsed(res,
'project/containers/container_detail.html')
self.assertContains(res, container.name, 1, 200)
+ self.assertNotContains(res, INVALID_CONTAINER_NAME_1)
+ self.assertNotContains(res, INVALID_CONTAINER_NAME_2)
@test.create_stubs({api.swift: ('swift_get_object', )})
def test_view_object(self):
@@ -462,6 +489,7 @@ class SwiftTests(test.TestCase):
self.assertTemplateUsed(
res, 'project/containers/object_detail.html')
self.assertContains(res, obj.name, 1, 200)
+ self._test_invalid_paths(res)
def test_wrap_delimiter(self):
expected = {
@@ -472,13 +500,3 @@ class SwiftTests(test.TestCase):
}
for name, expected_name in expected.items():
self.assertEqual(tables.wrap_delimiter(name), expected_name)
-
- def test_for_url(self):
- expected = {
- 'containerA': 'containerA/',
- 'containerB%': 'containerB%25/', # urlquote() must be called
- 'containerC%/': 'containerC%25/',
- 'containerD%/objectA%': 'containerD%25/objectA%25/'
- }
- for name, expected_name in expected.items():
- self.assertEqual(views.for_url(name), expected_name)
diff --git a/openstack_dashboard/dashboards/project/containers/views.py b/openstack_dashboard/dashboards/project/containers/views.py
index 217afe35c..1b21308e8 100644
--- a/openstack_dashboard/dashboards/project/containers/views.py
+++ b/openstack_dashboard/dashboards/project/containers/views.py
@@ -22,10 +22,10 @@
Views for managing Swift containers.
"""
-from django.core.urlresolvers import reverse
+import os
+
from django import http
from django.utils.functional import cached_property # noqa
-from django.utils import http as utils_http
from django.utils.translation import ugettext_lazy as _
from django.views import generic
@@ -33,6 +33,7 @@ from horizon import browsers
from horizon import exceptions
from horizon import forms
from horizon.utils import memoized
+from horizon.utils.urlresolvers import reverse # noqa
from openstack_dashboard import api
from openstack_dashboard.api import swift
@@ -42,18 +43,6 @@ from openstack_dashboard.dashboards.project.containers \
import forms as project_forms
from openstack_dashboard.dashboards.project.containers import tables
-import os
-
-
-def for_url(container_name):
- """Build a URL friendly container name.
-
- Add Swift delimiter if necessary.
- The name can contain '%' (bug 1231904).
- """
- container_name = tables.wrap_delimiter(container_name)
- return utils_http.urlquote(container_name)
-
class ContainerView(browsers.ResourceBrowserView):
browser_class = project_browsers.ContainerBrowser
@@ -145,10 +134,11 @@ class CreateView(forms.ModalFormView):
if parent:
container, slash, remainder = parent.partition(
swift.FOLDER_DELIMITER)
- args = (for_url(container), for_url(remainder))
+ args = (tables.wrap_delimiter(container),
+ tables.wrap_delimiter(remainder))
return reverse(self.success_url, args=args)
else:
- container = for_url(self.request.POST['name'])
+ container = tables.wrap_delimiter(self.request.POST['name'])
return reverse(self.success_url, args=[container])
def get_initial(self):
@@ -185,9 +175,9 @@ class UploadView(forms.ModalFormView):
success_url = "horizon:project:containers:index"
def get_success_url(self):
- container_name = for_url(self.request.POST['container_name'])
- path = for_url(self.request.POST.get('path', ''))
- args = (container_name, path)
+ container = tables.wrap_delimiter(self.request.POST['container_name'])
+ path = tables.wrap_delimiter(self.request.POST.get('path', ''))
+ args = (container, path)
return reverse(self.success_url, args=args)
def get_initial(self):
@@ -196,8 +186,7 @@ class UploadView(forms.ModalFormView):
def get_context_data(self, **kwargs):
context = super(UploadView, self).get_context_data(**kwargs)
- container_name = utils_http.urlquote(self.kwargs["container_name"])
- context['container_name'] = container_name
+ context['container_name'] = self.kwargs["container_name"]
return context
@@ -229,9 +218,10 @@ class CopyView(forms.ModalFormView):
success_url = "horizon:project:containers:index"
def get_success_url(self):
- new_container_name = for_url(self.request.POST['new_container_name'])
- path = for_url(self.request.POST.get('path', ''))
- args = (new_container_name, path)
+ container = tables.wrap_delimiter(
+ self.request.POST['new_container_name'])
+ path = tables.wrap_delimiter(self.request.POST.get('path', ''))
+ args = (container, path)
return reverse(self.success_url, args=args)
def get_form_kwargs(self):
@@ -257,8 +247,7 @@ class CopyView(forms.ModalFormView):
def get_context_data(self, **kwargs):
context = super(CopyView, self).get_context_data(**kwargs)
- container_name = utils_http.urlquote(self.kwargs["container_name"])
- context['container_name'] = container_name
+ context['container_name'] = self.kwargs["container_name"]
context['object_name'] = self.kwargs["object_name"]
return context
@@ -314,9 +303,9 @@ class UpdateObjectView(forms.ModalFormView):
success_url = "horizon:project:containers:index"
def get_success_url(self):
- container_name = for_url(self.request.POST['container_name'])
- path = for_url(self.request.POST.get('path', ''))
- args = (container_name, path)
+ container = tables.wrap_delimiter(self.request.POST['container_name'])
+ path = tables.wrap_delimiter(self.request.POST.get('path', ''))
+ args = (container, path)
return reverse(self.success_url, args=args)
def get_initial(self):
@@ -326,10 +315,7 @@ class UpdateObjectView(forms.ModalFormView):
def get_context_data(self, **kwargs):
context = super(UpdateObjectView, self).get_context_data(**kwargs)
- context['container_name'] = utils_http.urlquote(
- self.kwargs["container_name"])
- context['subfolder_path'] = utils_http.urlquote(
- self.kwargs["subfolder_path"])
- context['object_name'] = utils_http.urlquote(
- self.kwargs["object_name"])
+ context['container_name'] = self.kwargs["container_name"]
+ context['subfolder_path'] = self.kwargs["subfolder_path"]
+ context['object_name'] = self.kwargs["object_name"]
return context
diff --git a/openstack_dashboard/dashboards/project/networks/workflows.py b/openstack_dashboard/dashboards/project/networks/workflows.py
index c5420995d..e0b053f52 100644
--- a/openstack_dashboard/dashboards/project/networks/workflows.py
+++ b/openstack_dashboard/dashboards/project/networks/workflows.py
@@ -170,8 +170,8 @@ class CreateSubnetDetailAction(workflows.Action):
allocation_pools = forms.CharField(
widget=forms.Textarea(),
label=_("Allocation Pools"),
- help_text=_("IP address allocation pools. Each entry is "
- "&lt;start_ip_address&gt;,&lt;end_ip_address&gt; "
+ help_text=_("IP address allocation pools. Each entry is: "
+ "start_ip_address,end_ip_address "
"(e.g., 192.168.1.100,192.168.1.120) "
"and one entry per line."),
required=False)
@@ -185,7 +185,7 @@ class CreateSubnetDetailAction(workflows.Action):
widget=forms.widgets.Textarea(),
label=_("Host Routes"),
help_text=_("Additional routes announced to the hosts. "
- "Each entry is &lt;destination_cidr&gt;,&lt;nexthop&gt; "
+ "Each entry is: destination_cidr,nexthop "
"(e.g., 192.168.200.0/24,10.56.1.254) "
"and one entry per line."),
required=False)
diff --git a/openstack_dashboard/dashboards/settings/user/tests.py b/openstack_dashboard/dashboards/settings/user/tests.py
index 2003f1ba4..7124b119c 100644
--- a/openstack_dashboard/dashboards/settings/user/tests.py
+++ b/openstack_dashboard/dashboards/settings/user/tests.py
@@ -29,7 +29,6 @@ class UserSettingsTest(test.TestCase):
res = self.client.get(INDEX_URL)
self.assertContains(res, "Australia/Melbourne (UTC +11:00)")
- self.assertContains(res, "Europe/Moscow (UTC +04:00)")
self.assertContains(res, "Atlantic/Stanley (UTC -03:00)")
self.assertContains(res, "Pacific/Honolulu (UTC -10:00)")
diff --git a/openstack_dashboard/policy.py b/openstack_dashboard/policy.py
index 18361ad62..0942e8d38 100644
--- a/openstack_dashboard/policy.py
+++ b/openstack_dashboard/policy.py
@@ -59,7 +59,7 @@ def reset():
_ENFORCER = None
-def check(actions, request, target={}):
+def check(actions, request, target=None):
"""Check user permission.
Check if the user has permission to the action according
@@ -96,6 +96,8 @@ def check(actions, request, target={}):
{'tenant_id': object.tenant_id}
:returns: boolean if the user has permission or not for the actions.
"""
+ if target is None:
+ target = {}
user = auth_utils.get_user(request)
# Several service policy engines default to a project id check for
diff --git a/openstack_dashboard/test/api_tests/network_tests.py b/openstack_dashboard/test/api_tests/network_tests.py
index febbd59c9..7ad3e9afc 100644
--- a/openstack_dashboard/test/api_tests/network_tests.py
+++ b/openstack_dashboard/test/api_tests/network_tests.py
@@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
+import collections
import copy
import itertools
import uuid
@@ -224,6 +225,104 @@ class NetworkApiNeutronTestBase(test.APITestCase):
.AndReturn({'extensions': self.api_extensions.list()})
+class NetworkApiNeutronTests(NetworkApiNeutronTestBase):
+
+ def _get_expected_addresses(self, server, no_fip_expected=True):
+ server_ports = self.ports.filter(device_id=server.id)
+ addresses = collections.defaultdict(list)
+ for p in server_ports:
+ net_name = self.networks.get(id=p['network_id']).name
+ for ip in p.fixed_ips:
+ addresses[net_name].append(
+ {'version': 4,
+ 'addr': ip['ip_address'],
+ 'OS-EXT-IPS-MAC:mac_addr': p.mac_address,
+ 'OS-EXT-IPS:type': 'fixed'})
+ if no_fip_expected:
+ continue
+ fips = self.q_floating_ips.filter(port_id=p['id'])
+ if not fips:
+ continue
+ # Only one FIP should match.
+ fip = fips[0]
+ addresses[net_name].append(
+ {'version': 4,
+ 'addr': fip.floating_ip_address,
+ 'OS-EXT-IPS-MAC:mac_addr': p.mac_address,
+ 'OS-EXT-IPS:type': 'floating'})
+ return addresses
+
+ def _check_server_address(self, res_server_data, no_fip_expected=False):
+ expected_addresses = self._get_expected_addresses(res_server_data,
+ no_fip_expected)
+ self.assertEqual(len(expected_addresses),
+ len(res_server_data.addresses))
+ for net, addresses in expected_addresses.items():
+ self.assertIn(net, res_server_data.addresses)
+ self.assertEqual(addresses, res_server_data.addresses[net])
+
+ def _test_servers_update_addresses(self, router_enabled=True):
+ tenant_id = self.request.user.tenant_id
+
+ servers = copy.deepcopy(self.servers.list())
+ server_ids = [server.id for server in servers]
+ server_ports = [p for p in self.api_ports.list()
+ if p['device_id'] in server_ids]
+ server_port_ids = [p['id'] for p in server_ports]
+ if router_enabled:
+ assoc_fips = [fip for fip in self.api_q_floating_ips.list()
+ if fip['port_id'] in server_port_ids]
+ server_network_ids = [p['network_id'] for p in server_ports]
+ server_networks = [net for net in self.api_networks.list()
+ if net['id'] in server_network_ids]
+
+ self.qclient.list_ports(device_id=server_ids) \
+ .AndReturn({'ports': server_ports})
+ if router_enabled:
+ self.qclient.list_floatingips(tenant_id=tenant_id,
+ port_id=server_port_ids) \
+ .AndReturn({'floatingips': assoc_fips})
+ self.qclient.list_ports(tenant_id=tenant_id) \
+ .AndReturn({'ports': self.api_ports.list()})
+ self.qclient.list_networks(id=set(server_network_ids)) \
+ .AndReturn({'networks': server_networks})
+ self.qclient.list_subnets() \
+ .AndReturn({'subnets': self.api_subnets.list()})
+ self.mox.ReplayAll()
+
+ api.network.servers_update_addresses(self.request, servers)
+
+ self.assertEqual(self.servers.count(), len(servers))
+ self.assertEqual([server.id for server in self.servers.list()],
+ [server.id for server in servers])
+
+ no_fip_expected = not router_enabled
+
+ # server[0] has one fixed IP and one floating IP
+ # if router ext isenabled.
+ self._check_server_address(servers[0], no_fip_expected)
+ # The expected is also calculated, we examine the result manually once.
+ addrs = servers[0].addresses['net1']
+ if router_enabled:
+ self.assertEqual(2, len(addrs))
+ self.assertEqual('fixed', addrs[0]['OS-EXT-IPS:type'])
+ self.assertEqual('floating', addrs[1]['OS-EXT-IPS:type'])
+ else:
+ self.assertEqual(1, len(addrs))
+ self.assertEqual('fixed', addrs[0]['OS-EXT-IPS:type'])
+
+ # server[1] has one fixed IP.
+ self._check_server_address(servers[1], no_fip_expected)
+ # manual check.
+ addrs = servers[1].addresses['net2']
+ self.assertEqual(1, len(addrs))
+ self.assertEqual('fixed', addrs[0]['OS-EXT-IPS:type'])
+
+ # server[2] has no corresponding ports in neutron_data,
+ # so it should be an empty dict.
+ self.assertFalse(servers[2].addresses)
+
+
class NetworkApiNeutronSecurityGroupTests(NetworkApiNeutronTestBase):
def setUp(self):
diff --git a/openstack_dashboard/test/api_tests/neutron_tests.py b/openstack_dashboard/test/api_tests/neutron_tests.py
index 535af52e3..32ced2e89 100644
--- a/openstack_dashboard/test/api_tests/neutron_tests.py
+++ b/openstack_dashboard/test/api_tests/neutron_tests.py
@@ -14,6 +14,10 @@
# License for the specific language governing permissions and limitations
# under the License.
+import uuid
+
+from neutronclient.common import exceptions as neutron_exc
+
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
@@ -281,3 +285,33 @@ class NeutronApiTests(test.APITestCase):
api.neutron.is_extension_supported(self.request, 'quotas'))
self.assertFalse(
api.neutron.is_extension_supported(self.request, 'doesntexist'))
+
+ def test_list_resources_with_long_filters(self):
+ # In this tests, port_list is called with id=[10 port ID]
+ # filter. It generates about 40*10 char length URI.
+ # Each port ID is converted to "id=<UUID>&" in URI and
+ # it means 40 chars (len(UUID)=36).
+ # If excess lenght is 220, it means 400-220=180 chars
+ # can be sent in the first request.
+ # As a result three API calls with 4, 4, 2 port ID
+ # are expected.
+
+ ports = [{'id': str(uuid.uuid4()),
+ 'name': 'port%s' % i,
+ 'admin_state_up': True}
+ for i in range(10)]
+ port_ids = [port['id'] for port in ports]
+
+ neutronclient = self.stub_neutronclient()
+ uri_len_exc = neutron_exc.RequestURITooLong(excess=220)
+ neutronclient.list_ports(id=port_ids).AndRaise(uri_len_exc)
+ for i in range(0, 10, 4):
+ neutronclient.list_ports(id=port_ids[i:i + 4]) \
+ .AndReturn({'ports': ports[i:i + 4]})
+ self.mox.ReplayAll()
+
+ ret_val = api.neutron.list_resources_with_long_filters(
+ api.neutron.port_list, 'id', port_ids,
+ request=self.request)
+ self.assertEqual(10, len(ret_val))
+ self.assertEqual(port_ids, [p.id for p in ret_val])
diff --git a/openstack_dashboard/test/test_data/exceptions.py b/openstack_dashboard/test/test_data/exceptions.py
index f89b9ef15..2b734edc4 100644
--- a/openstack_dashboard/test/test_data/exceptions.py
+++ b/openstack_dashboard/test/test_data/exceptions.py
@@ -27,7 +27,7 @@ from openstack_dashboard.test.test_data import utils
def create_stubbed_exception(cls, status_code=500):
msg = "Expected failure."
- def fake_init_exception(self, code, message, **kwargs):
+ def fake_init_exception(self, code=None, message=None, **kwargs):
if code is not None:
if hasattr(self, 'http_status'):
self.http_status = code
diff --git a/openstack_dashboard/test/test_data/nova_data.py b/openstack_dashboard/test/test_data/nova_data.py
index 56cd6987b..26206e154 100644
--- a/openstack_dashboard/test/test_data/nova_data.py
+++ b/openstack_dashboard/test/test_data/nova_data.py
@@ -730,6 +730,16 @@ def data(TEST):
"zone": "testing"
}
)
+
+ host4 = hosts.Host(hosts.HostManager(None),
+ {
+ "host_name": "devstack004",
+ "service": "compute",
+ "zone": "testing",
+ }
+ )
+
TEST.hosts.add(host1)
TEST.hosts.add(host2)
TEST.hosts.add(host3)
+ TEST.hosts.add(host4)
diff --git a/openstack_dashboard/usage/quotas.py b/openstack_dashboard/usage/quotas.py
index be2dc9705..0210ff8d7 100644
--- a/openstack_dashboard/usage/quotas.py
+++ b/openstack_dashboard/usage/quotas.py
@@ -24,6 +24,7 @@ from openstack_dashboard.api import cinder
from openstack_dashboard.api import network
from openstack_dashboard.api import neutron
from openstack_dashboard.api import nova
+from openstack_dashboard.exceptions import neutronclient # noqa
LOG = logging.getLogger(__name__)
@@ -200,7 +201,10 @@ def tenant_quota_usages(request):
usages.add_quota(quota)
# Get our usages.
- floating_ips = network.tenant_floating_ip_list(request)
+ try:
+ floating_ips = network.tenant_floating_ip_list(request)
+ except neutronclient.NeutronClientException:
+ floating_ips = []
flavors = dict([(f.id, f) for f in nova.flavor_list(request)])
instances, has_more = nova.server_list(request)
# Fetch deleted flavors if necessary.
diff --git a/openstack_dashboard/views.py b/openstack_dashboard/views.py
index 8a630e933..5ff1fd541 100644
--- a/openstack_dashboard/views.py
+++ b/openstack_dashboard/views.py
@@ -33,6 +33,4 @@ def splash(request):
if request.user.is_authenticated():
return shortcuts.redirect(horizon.get_user_home(request.user))
form = forms.Login(request)
- request.session.clear()
- request.session.set_test_cookie()
return shortcuts.render(request, 'splash.html', {'form': form})
diff --git a/requirements.txt b/requirements.txt
index 3af5e74e7..5ec059595 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,25 +1,25 @@
# Horizon Core Requirements
Django>=1.4,<1.7
django_compressor>=1.3
-django_openstack_auth>=1.1.4,!=1.1.6
-eventlet>=0.13.0
-httplib2>=0.7.5
-iso8601>=0.1.9
-kombu>=2.4.8
+django_openstack_auth>=1.1.4,!=1.1.6,<1.1.8
+eventlet>=0.13.0,<0.16.0
+httplib2>=0.7.5,<=0.9
+iso8601>=0.1.9,<=0.1.10
+kombu>=2.5.0,<=3.0.7
lesscpy>=0.9j
# Horizon Utility Requirements
# for SECURE_KEY generation
-lockfile>=0.8
-netaddr>=0.7.6
+lockfile>=0.8,<=0.10.2
+netaddr>=0.7.6,<=0.7.14
pbr>=0.6,<1.0
-python-ceilometerclient>=1.0.6
-python-cinderclient>=1.0.6
-python-glanceclient>=0.9.0
-python-heatclient>=0.2.3
-python-keystoneclient>=0.7.0
-python-neutronclient>=2.3.4,<3
-python-novaclient>=2.17.0
-python-swiftclient>=1.6
-python-troveclient>=1.0.3
-pytz>=2010h
-six>=1.6.0
+python-ceilometerclient>=1.0.6,<=1.0.12
+python-cinderclient>=1.0.6,<=1.1.1
+python-glanceclient>=0.9.0,!=0.14.0,<=0.14.2
+python-heatclient>=0.2.3,<=0.2.12
+python-keystoneclient>=0.7.0,<0.12.0
+python-neutronclient>=2.3.4,<2.3.11
+python-novaclient>=2.17.0,<2.21
+python-swiftclient>=1.6,<=2.3.1
+python-troveclient>=1.0.3,<=1.0.8
+pytz>=2010h,<=2015.2
+six>=1.6.0,<=1.9.0
diff --git a/setup.cfg b/setup.cfg
index c57c70b08..27453003d 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = horizon
-version = 2014.1.4
+version = 2014.1.5
summary = OpenStack Dashboard
description-file =
README.rst
diff --git a/test-requirements.txt b/test-requirements.txt
index 67ba61673..48f1d908c 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,14 +1,14 @@
-coverage>=3.6
+coverage>=3.6,<=3.7.1
django-nose
docutils==0.9.1
hacking>=0.8.0,<0.9
-mox>=0.5.3
+mox>=0.5.3,<=0.5.3
nose
nose-exclude
nosehtmloutput>=0.0.3
nosexcover
openstack.nose_plugin>=0.7
-oslosphinx
+oslosphinx<=2.5.0
selenium
sphinx>=1.1.2,<1.1.999
-testtools>=0.9.34
+testtools>=0.9.34,!=1.2.0,!=1.4.0,<=1.7.1