diff options
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 " - "<start_ip_address>,<end_ip_address> " + 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 <destination_cidr>,<nexthop> " + "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 @@ -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 |