diff --git a/doc/source/topics/settings.rst b/doc/source/topics/settings.rst index c8ba487224..f2dfa04e5d 100644 --- a/doc/source/topics/settings.rst +++ b/doc/source/topics/settings.rst @@ -263,26 +263,6 @@ This example sorts flavors by vcpus in descending order:: 'reverse': True, } -``FLAVOR_EXTRA_KEYS`` ---------------------- - -.. versionadded:: 2014.1(Icehouse) - -Default:: - - { - 'flavor_keys': [ - ('quota:disk_read_bytes_sec', _('Quota: Read bytes')), - ('quota:disk_write_bytes_sec', _('Quota: Write bytes')), - ('quota:cpu_quota', _('Quota: CPU')), - ('quota:cpu_period', _('Quota: CPU period')), - ('quota:vif_inbound_average', _('Quota: Inbound average')), - ('quota:vif_outbound_average', _('Quota: Outbound average')) - ] - } - -Used to customize flavor extra specs keys - ``IMAGES_LIST_FILTER_TENANTS`` ------------------------------ diff --git a/openstack_dashboard/dashboards/admin/flavors/constants.py b/openstack_dashboard/dashboards/admin/flavors/constants.py new file mode 100644 index 0000000000..8d304d06e9 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/flavors/constants.py @@ -0,0 +1,21 @@ +# 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. + +FLAVORS_TEMPLATE_NAME = 'admin/flavors/index.html' +FLAVORS_INDEX_URL = 'horizon:admin:flavors:index' +FLAVORS_CREATE_URL = 'horizon:admin:flavors:create' +FLAVORS_CREATE_VIEW_TEMPLATE = 'admin/flavors/create.html' +FLAVORS_UPDATE_URL = 'horizon:admin:flavors:update' +FLAVORS_UPDATE_VIEW_TEMPLATE = 'admin/flavors/update.html' +FLAVORS_UPDATE_METADATA_URL = 'horizon:admin:flavors:update_metadata' +FLAVORS_UPDATE_METADATA_TEMPLATE = 'admin/flavors/update_metadata.html' +FLAVORS_UPDATE_METADATA_SUBTEMPLATE = 'admin/flavors/_update_metadata.html' diff --git a/openstack_dashboard/dashboards/admin/flavors/extras/__init__.py b/openstack_dashboard/dashboards/admin/flavors/extras/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openstack_dashboard/dashboards/admin/flavors/extras/forms.py b/openstack_dashboard/dashboards/admin/flavors/extras/forms.py deleted file mode 100644 index e2cff0b53f..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/extras/forms.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright (c) 2012 Intel, 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. - -from django.conf import settings -from django.forms import ValidationError # noqa -from django.utils.translation import ugettext_lazy as _ - -from openstack_dashboard import api - -from horizon import exceptions -from horizon import forms -from horizon import messages - -import re - - -class CreateExtraSpec(forms.SelfHandlingForm): - _extraspec_name_regex = re.compile(r"^[\w\.\-: ]+$", re.UNICODE) - keys = forms.ChoiceField(label=_("Keys"), - widget=forms.Select(attrs={ - 'class': 'switchable', - 'data-slug': 'keys'})) - key = forms.RegexField( - max_length=255, - label=_("Key"), - required=False, - regex=_extraspec_name_regex, - error_messages={'invalid': _('Key Name may only contain letters, ' - 'numbers, underscores, periods, colons, ' - 'spaces and hyphens.')}, - widget=forms.TextInput(attrs={ - 'class': 'switched', - 'data-switch-on': 'keys', - 'data-keys-custom': _('Key')})) - value = forms.CharField(max_length=255, label=_("Value")) - flavor_id = forms.CharField(widget=forms.widgets.HiddenInput) - - def __init__(self, *args, **kwargs): - super(CreateExtraSpec, self).__init__(*args, **kwargs) - key_settings = getattr(settings, 'FLAVOR_EXTRA_KEYS', {}) - key_list = key_settings.get('flavor_keys', []) - self.fields['keys'].choices = key_list + [('custom', _('Other Key'))] - - def clean(self): - cleaned_data = super(CreateExtraSpec, self).clean() - keys = cleaned_data.get('keys', None) - key = cleaned_data.get('key', None) - if keys == 'custom' and key == "": - msg = _('This field is required.') - self._errors["key"] = self.error_class([msg]) - return cleaned_data - - def handle(self, request, data): - if data["keys"] != 'custom': - data['key'] = data['keys'] - try: - api.nova.flavor_extra_set(request, - data['flavor_id'], - {data['key']: data['value']}) - msg = _('Created extra spec "%s".') % data['key'] - messages.success(request, msg) - return True - except Exception: - exceptions.handle(request, - _("Unable to create flavor extra spec.")) - - -class EditExtraSpec(forms.SelfHandlingForm): - key = forms.CharField(widget=forms.widgets.HiddenInput) - value = forms.CharField(max_length=255, label=_("Value")) - flavor_id = forms.CharField(widget=forms.widgets.HiddenInput) - - def handle(self, request, data): - flavor_id = data['flavor_id'] - try: - api.nova.flavor_extra_set(request, - flavor_id, - {data['key']: data['value']}) - msg = _('Saved extra spec "%s".') % data['key'] - messages.success(request, msg) - return True - except Exception: - exceptions.handle(request, _("Unable to edit extra spec.")) diff --git a/openstack_dashboard/dashboards/admin/flavors/extras/tables.py b/openstack_dashboard/dashboards/admin/flavors/extras/tables.py deleted file mode 100644 index 43d8a568dc..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/extras/tables.py +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright (c) 2012 Intel, 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. - -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext_lazy as _ - -from horizon import tables - -from openstack_dashboard import api - - -class ExtraSpecDelete(tables.DeleteAction): - data_type_singular = _("ExtraSpec") - data_type_plural = _("ExtraSpecs") - - def delete(self, request, obj_ids): - flavor = api.nova.flavor_get(request, self.table.kwargs['id']) - flavor.unset_keys([obj_ids]) - - -class ExtraSpecCreate(tables.LinkAction): - name = "create" - verbose_name = _("Create") - url = "horizon:admin:flavors:extras:create" - classes = ("ajax-modal",) - icon = "plus" - - def get_link_url(self, extra_spec=None): - return reverse(self.url, args=[self.table.kwargs['id']]) - - -class ExtraSpecEdit(tables.LinkAction): - name = "edit" - verbose_name = _("Edit") - url = "horizon:admin:flavors:extras:edit" - classes = ("ajax-modal",) - icon = "pencil" - - def get_link_url(self, extra_spec): - return reverse(self.url, args=[self.table.kwargs['id'], - extra_spec.key]) - - -class ExtraSpecsTable(tables.DataTable): - key = tables.Column('key', verbose_name=_('Key')) - value = tables.Column('value', verbose_name=_('Value')) - - class Meta: - name = "extras" - verbose_name = _("Extra Specs") - table_actions = (ExtraSpecCreate, ExtraSpecDelete) - row_actions = (ExtraSpecEdit, ExtraSpecDelete) - - def get_object_id(self, datum): - return datum.key - - def get_object_display(self, datum): - return datum.key diff --git a/openstack_dashboard/dashboards/admin/flavors/extras/tests.py b/openstack_dashboard/dashboards/admin/flavors/extras/tests.py deleted file mode 100644 index f30ecbe4a3..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/extras/tests.py +++ /dev/null @@ -1,127 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -from django.core.urlresolvers import reverse -from django import http - -from mox import IsA # noqa - -from openstack_dashboard import api -from openstack_dashboard.test import helpers as test - - -class FlavorExtrasTests(test.BaseAdminViewTests): - - @test.create_stubs({api.nova: ('flavor_get_extras', - 'flavor_get'), }) - def test_list_extras_when_none_exists(self): - flavor = self.flavors.first() - extras = [api.nova.FlavorExtraSpec(flavor.id, 'k1', 'v1')] - - # GET -- to determine correctness of output - api.nova.flavor_get(IsA(http.HttpRequest), flavor.id).AndReturn(flavor) - api.nova.flavor_get_extras(IsA(http.HttpRequest), - flavor.id).AndReturn(extras) - self.mox.ReplayAll() - url = reverse('horizon:admin:flavors:extras:index', args=[flavor.id]) - resp = self.client.get(url) - self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, "admin/flavors/extras/index.html") - - @test.create_stubs({api.nova: ('flavor_extra_set', ), }) - def _generic_extra_create_post(self, key_name): - flavor = self.flavors.first() - create_url = reverse('horizon:admin:flavors:extras:create', - args=[flavor.id]) - index_url = reverse('horizon:admin:flavors:extras:index', - args=[flavor.id]) - - # GET to display the flavor_name - api.nova.flavor_extra_set(IsA(http.HttpRequest), - flavor.id, - {key_name: 'v1'}) - self.mox.ReplayAll() - - data = {'flavor_id': flavor.id, - 'keys': 'custom', - 'key': key_name, - 'value': 'v1'} - resp = self.client.post(create_url, data) - self.assertNoFormErrors(resp) - self.assertRedirectsNoFollow(resp, index_url) - self.mox.UnsetStubs() - - @test.create_stubs({api.nova: ('flavor_extra_set', ), }) - def test_extra_create_with_template(self): - flavor = self.flavors.first() - create_url = reverse('horizon:admin:flavors:extras:create', - args=[flavor.id]) - index_url = reverse('horizon:admin:flavors:extras:index', - args=[flavor.id]) - - # GET to display the flavor_name - api.nova.flavor_extra_set(IsA(http.HttpRequest), - flavor.id, - {'quota:disk_read_bytes_sec': '1000'}) - self.mox.ReplayAll() - - data = {'flavor_id': flavor.id, - 'keys': 'quota:disk_read_bytes_sec', - 'value': '1000'} - resp = self.client.post(create_url, data) - self.assertNoFormErrors(resp) - self.assertRedirectsNoFollow(resp, index_url) - - @test.create_stubs({api.nova: ('flavor_get', ), }) - def test_extra_create_get(self): - flavor = self.flavors.first() - create_url = reverse('horizon:admin:flavors:extras:create', - args=[flavor.id]) - - api.nova.flavor_get(IsA(http.HttpRequest), flavor.id).AndReturn(flavor) - self.mox.ReplayAll() - - resp = self.client.get(create_url) - self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, - 'admin/flavors/extras/create.html') - - @test.create_stubs({api.nova: ('flavor_get', ), }) - def _generic_extra_create_names_format_fail(self, key_name): - flavor = self.flavors.first() - create_url = reverse('horizon:admin:flavors:extras:create', - args=[flavor.id]) - api.nova.flavor_get(IsA(http.HttpRequest), flavor.id).AndReturn(flavor) - - self.mox.ReplayAll() - - data = {'flavor_id': flavor.id, - 'keys': 'custom', - 'key': key_name, - 'value': 'v1'} - - resp = self.client.post(create_url, data) - msg = ('Name may only contain letters, numbers, underscores, periods, ' - 'colons, spaces and hyphens.') - - self.assertFormErrors(resp, 1, msg) - self.mox.UnsetStubs() - - def test_create_extra_key_names_valid_formats(self): - valid_keys = ("key1", "month.price", "I-Am:AK-ey. 22-") - for x in valid_keys: - self._generic_extra_create_post(key_name=x) - - def test_create_extra_key_names_invalid_formats(self): - invalid_keys = ("key1/", "", "$$akey$", "!akey") - for x in invalid_keys: - self._generic_extra_create_names_format_fail(key_name=x) diff --git a/openstack_dashboard/dashboards/admin/flavors/extras/urls.py b/openstack_dashboard/dashboards/admin/flavors/extras/urls.py deleted file mode 100644 index 812dcb1831..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/extras/urls.py +++ /dev/null @@ -1,28 +0,0 @@ -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# 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. - -from django.conf.urls import patterns # noqa -from django.conf.urls import url # noqa - -from openstack_dashboard.dashboards.admin.flavors.extras import views - -urlpatterns = patterns('', - url(r'^$', views.IndexView.as_view(), name='index'), - url(r'^create/$', views.CreateView.as_view(), name='create'), - url(r'^(?P[^/]+)/edit/$', views.EditView.as_view(), name='edit') -) diff --git a/openstack_dashboard/dashboards/admin/flavors/extras/views.py b/openstack_dashboard/dashboards/admin/flavors/extras/views.py deleted file mode 100644 index 0d78bd6c1a..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/extras/views.py +++ /dev/null @@ -1,100 +0,0 @@ -# Copyright 2012 United States Government as represented by the -# Administrator of the National Aeronautics and Space Administration. -# All Rights Reserved. -# -# Copyright (c) 2012 Intel, 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. - - -from django.core.urlresolvers import reverse -from django.utils.translation import ugettext_lazy as _ - -from horizon import exceptions -from horizon import forms -from horizon import tables - -from openstack_dashboard import api - -from openstack_dashboard.dashboards.admin.flavors.extras \ - import forms as project_forms -from openstack_dashboard.dashboards.admin.flavors.extras \ - import tables as project_tables - - -class ExtraSpecMixin(object): - def get_context_data(self, **kwargs): - context = super(ExtraSpecMixin, self).get_context_data(**kwargs) - try: - context['flavor'] = api.nova.flavor_get(self.request, - self.kwargs['id']) - except Exception: - exceptions.handle(self.request, - _("Unable to retrieve flavor details.")) - if 'key' in self.kwargs: - context['key'] = self.kwargs['key'] - return context - - -class IndexView(ExtraSpecMixin, forms.ModalFormMixin, tables.DataTableView): - table_class = project_tables.ExtraSpecsTable - template_name = 'admin/flavors/extras/index.html' - - def get_data(self): - try: - flavor_id = self.kwargs['id'] - extras_list = api.nova.flavor_get_extras(self.request, flavor_id) - extras_list.sort(key=lambda es: (es.key,)) - except Exception: - extras_list = [] - exceptions.handle(self.request, - _('Unable to retrieve extra spec list.')) - return extras_list - - -class CreateView(ExtraSpecMixin, forms.ModalFormView): - form_class = project_forms.CreateExtraSpec - template_name = 'admin/flavors/extras/create.html' - - def get_initial(self): - return {'flavor_id': self.kwargs['id']} - - def get_success_url(self): - return reverse("horizon:admin:flavors:extras:index", - args=(self.kwargs["id"],)) - - -class EditView(ExtraSpecMixin, forms.ModalFormView): - form_class = project_forms.EditExtraSpec - template_name = 'admin/flavors/extras/edit.html' - success_url = 'horizon:admin:flavors:extras:index' - - def get_success_url(self): - return reverse(self.success_url, - args=(self.kwargs['id'],)) - - def get_initial(self): - flavor_id = self.kwargs['id'] - key = self.kwargs['key'] - try: - extra_specs = api.nova.flavor_get_extras(self.request, - flavor_id, - raw=True) - except Exception: - extra_specs = {} - exceptions.handle(self.request, - _('Unable to retrieve flavor extra spec ' - 'details.')) - return {'flavor_id': flavor_id, - 'key': key, - 'value': extra_specs.get(key, '')} diff --git a/openstack_dashboard/dashboards/admin/flavors/forms.py b/openstack_dashboard/dashboards/admin/flavors/forms.py new file mode 100644 index 0000000000..81057ea386 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/flavors/forms.py @@ -0,0 +1,50 @@ +# 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 json + +from django.utils.translation import ugettext_lazy as _ + +from horizon import exceptions +from horizon import forms +from horizon import messages + +from openstack_dashboard import api + + +class UpdateMetadataForm(forms.SelfHandlingForm): + + def handle(self, request, data): + id = self.initial['id'] + old_metadata = self.initial['metadata'] + + try: + new_metadata = json.loads(self.data['metadata']) + + metadata = dict( + (item['key'], str(item['value'])) + for item in new_metadata + ) + api.nova.flavor_extra_set(request, id, metadata) + + remove_keys = [key for key in old_metadata if key not in metadata] + + api.nova.flavor_extra_delete(request, id, remove_keys) + + message = _('Metadata successfully updated.') + messages.success(request, message) + except Exception: + exceptions.handle(request, + _('Unable to update the flavor metadata.')) + return False + return True diff --git a/openstack_dashboard/dashboards/admin/flavors/tables.py b/openstack_dashboard/dashboards/admin/flavors/tables.py index 8135bf4e62..081f2c74c7 100644 --- a/openstack_dashboard/dashboards/admin/flavors/tables.py +++ b/openstack_dashboard/dashboards/admin/flavors/tables.py @@ -50,10 +50,11 @@ class UpdateFlavor(tables.LinkAction): icon = "pencil" -class ViewFlavorExtras(tables.LinkAction): - name = "extras" - verbose_name = _("View Extra Specs") - url = "horizon:admin:flavors:extras:index" +class UpdateMetadata(tables.LinkAction): + url = "horizon:admin:flavors:update_metadata" + name = "update_metadata" + verbose_name = _("Update Metadata") + classes = ("ajax-modal",) icon = "pencil" @@ -124,7 +125,8 @@ class FlavorsTable(tables.DataTable): filters=(filters.yesno, filters.capfirst)) extra_specs = tables.Column(get_extra_specs, verbose_name=_("Extra Specs"), - link=("horizon:admin:flavors:extras:index"), + link="horizon:admin:flavors:update_metadata", + link_classes=("ajax-modal",), empty_value=False, filters=(filters.yesno, filters.capfirst)) @@ -134,5 +136,5 @@ class FlavorsTable(tables.DataTable): table_actions = (FlavorFilterAction, CreateFlavor, DeleteFlavor) row_actions = (UpdateFlavor, ModifyAccess, - ViewFlavorExtras, + UpdateMetadata, DeleteFlavor) diff --git a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/_update_metadata.html b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/_update_metadata.html new file mode 100755 index 0000000000..f11fda2c53 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/_update_metadata.html @@ -0,0 +1,11 @@ +{% extends 'horizon/common/_modal_form_update_metadata.html' %} +{% load i18n %} +{% load url from future %} +{% block title %}{% trans "Update Flavor Metadata" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Update Flavor Metadata") %} +{% endblock page_header %} + +{% block form_action %}{% url 'horizon:admin:flavors:update_metadata' id %}{% endblock %} +{% block modal-header %}{% trans "Update Metadata" %}{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_create.html b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_create.html deleted file mode 100644 index 287d866a92..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_create.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}extra_spec_create_form{% endblock %} -{% block form_action %}{% url 'horizon:admin:flavors:extras:create' flavor.id %}{% endblock %} - - -{% block modal_id %}extra_spec_create_modal{% endblock %} -{% block modal-header %}{% trans "Create Flavor Extra Spec" %}{% endblock %} - -{% block modal-body %} -
-
- {% include "horizon/common/_form_fields.html" %} -
-
-
-

{% trans "Description" %}:

-

{% trans 'Create a new "extra spec" key-value pair for a flavor.' %}

-
-{% endblock %} - -{% block modal-footer %} - - {% trans "Cancel" %} -{% endblock %} - diff --git a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_edit.html b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_edit.html deleted file mode 100644 index 65e13474cf..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_edit.html +++ /dev/null @@ -1,28 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% load url from future %} - -{% block form_id %}extra_spec_edit_form{% endblock %} -{% block form_action %}{% url 'horizon:admin:flavors:extras:edit' flavor.id key %}{% endblock %} - - -{% block modal_id %}extra_spec_edit_modal{% endblock %} -{% block modal-header %}{% trans "Edit Extra Spec Value" %}: {{ key }}{% endblock %} - -{% block modal-body %} -
-
- {% include "horizon/common/_form_fields.html" %} -
-
-
-

{% trans "Description" %}:

-

{% blocktrans %}Update the "extra spec" value for "{{ key }}"{% endblocktrans %}

-
-{% endblock %} - -{% block modal-footer %} - - {% trans "Cancel" %} -{% endblock %} - diff --git a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_index.html b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_index.html deleted file mode 100644 index c7054e7052..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/_index.html +++ /dev/null @@ -1,15 +0,0 @@ -{% extends "horizon/common/_modal.html" %} -{% load i18n %} -{% load url from future %} - -{% block modal_id %}extra_specs_modal{% endblock %} -{% block modal-header %}{% trans "Flavor Extra Specs" %}{% endblock %} - -{% block modal-body %} - {{ table.render }} -{% endblock %} - -{% block modal-footer %} - {% trans "Close" %} -{% endblock %} - diff --git a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/create.html b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/create.html deleted file mode 100644 index 20461837eb..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/create.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} - -{% block title %}{% trans "Create Flavor Extra Spec" %}{% endblock %} - -{% block page_header %} -

{% trans "Flavor" %}: {{flavor.name}}

-{% endblock page_header %} - -{% block main %} - {% include "admin/flavors/extras/_create.html" %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/edit.html b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/edit.html deleted file mode 100644 index c471844ef3..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/edit.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} - -{% block title %}{% trans "Edit Flavor Extra Spec" %}{% endblock %} - -{% block page_header %} -

{% trans "Flavor" %}: {{flavor.name}}

-{% endblock page_header %} - -{% block main %} - {% include "admin/flavors/extras/_edit.html" %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/index.html b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/index.html deleted file mode 100644 index fbe91effca..0000000000 --- a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/extras/index.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} - -{% block title %}{% trans "Flavor Extra Specs" %}{% endblock %} - -{% block page_header %} -

{% trans "Flavor" %}: {{flavor.name}}

-{% endblock page_header %} - -{% block main %} - {% include "admin/flavors/extras/_index.html" %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/flavors/templates/flavors/update_metadata.html b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/update_metadata.html new file mode 100755 index 0000000000..5e63ad99a5 --- /dev/null +++ b/openstack_dashboard/dashboards/admin/flavors/templates/flavors/update_metadata.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} +{% load i18n %} +{% block title %}{% trans "Update Flavor Metadata" %}{% endblock %} + +{% block page_header %} + {% include "horizon/common/_page_header.html" with title=_("Update Flavor Metadata") %} +{% endblock page_header %} + +{% block main %} + {% include 'admin/flavors/_update_metadata.html' %} +{% endblock %} diff --git a/openstack_dashboard/dashboards/admin/flavors/tests.py b/openstack_dashboard/dashboards/admin/flavors/tests.py index 8d19e8216d..2d4b378579 100644 --- a/openstack_dashboard/dashboards/admin/flavors/tests.py +++ b/openstack_dashboard/dashboards/admin/flavors/tests.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from django.core.urlresolvers import reverse from django import http from mox import IsA # noqa @@ -19,10 +21,9 @@ from openstack_dashboard.test import helpers as test from novaclient.v1_1 import flavors +from openstack_dashboard.dashboards.admin.flavors import constants from openstack_dashboard.dashboards.admin.flavors import workflows -INDEX_URL = reverse('horizon:admin:flavors:index') - class FlavorsViewTests(test.BaseAdminViewTests): @test.create_stubs({api.nova: ('flavor_list',), @@ -33,8 +34,8 @@ class FlavorsViewTests(test.BaseAdminViewTests): flavors.Flavor.get_keys().MultipleTimes().AndReturn({}) self.mox.ReplayAll() - res = self.client.get(INDEX_URL) - self.assertTemplateUsed(res, 'admin/flavors/index.html') + res = self.client.get(reverse(constants.FLAVORS_INDEX_URL)) + self.assertTemplateUsed(res, constants.FLAVORS_TEMPLATE_NAME) self.assertItemsEqual(res.context['table'].data, self.flavors.list()) @@ -82,9 +83,9 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests): False]) self.mox.ReplayAll() - url = reverse('horizon:admin:flavors:create') + url = reverse(constants.FLAVORS_CREATE_URL) res = self.client.get(url) - self.assertTemplateUsed(res, 'admin/flavors/create.html') + self.assertTemplateUsed(res, constants.FLAVORS_CREATE_VIEW_TEMPLATE) workflow = res.context['workflow'] expected_name = workflows.CreateFlavor.name self.assertEqual(res.context['workflow'].name, expected_name) @@ -114,11 +115,11 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests): workflow_data = self._get_workflow_data(flavor) - url = reverse('horizon:admin:flavors:create') + url = reverse(constants.FLAVORS_CREATE_URL) res = self.client.post(url, workflow_data) self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) + self.assertRedirectsNoFollow(res, reverse(constants.FLAVORS_INDEX_URL)) @test.create_stubs({api.keystone: ('tenant_list',), api.nova: ('flavor_list', @@ -146,11 +147,11 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests): workflow_data = self._get_workflow_data(flavor, access=projects) - url = reverse('horizon:admin:flavors:create') + url = reverse(constants.FLAVORS_CREATE_URL) res = self.client.post(url, workflow_data) self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) + self.assertRedirectsNoFollow(res, reverse(constants.FLAVORS_INDEX_URL)) @test.create_stubs({api.keystone: ('tenant_list',), api.nova: ('flavor_list',)}) @@ -169,7 +170,7 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests): workflow_data = self._get_workflow_data(flavor) - url = reverse('horizon:admin:flavors:create') + url = reverse(constants.FLAVORS_CREATE_URL) res = self.client.post(url, workflow_data) self.assertFormErrors(res) @@ -195,7 +196,7 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests): # Flavor id already exists. workflow_data['flavor_id'] = flavor.id - url = reverse('horizon:admin:flavors:create') + url = reverse(constants.FLAVORS_CREATE_URL) res = self.client.post(url, workflow_data) self.assertFormErrors(res) @@ -228,12 +229,12 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests): workflow_data = self._get_workflow_data(flavor, access=projects) - url = reverse('horizon:admin:flavors:create') + url = reverse(constants.FLAVORS_CREATE_URL) res = self.client.post(url, workflow_data) self.assertNoFormErrors(res) self.assertMessageCount(error=1, warning=0) - self.assertRedirectsNoFollow(res, INDEX_URL) + self.assertRedirectsNoFollow(res, reverse(constants.FLAVORS_INDEX_URL)) @test.create_stubs({api.keystone: ('tenant_list',), api.nova: ('flavor_list',)}) @@ -251,7 +252,7 @@ class CreateFlavorWorkflowTests(BaseFlavorWorkflowTests): workflow_data = self._get_workflow_data(flavor) workflow_data["name"] = "" - url = reverse('horizon:admin:flavors:create') + url = reverse(constants.FLAVORS_CREATE_URL) res = self.client.post(url, workflow_data) self.assertFormErrors(res) @@ -278,10 +279,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): # Put all mocks created by mox into replay mode self.mox.ReplayAll() - url = reverse('horizon:admin:flavors:update', args=[flavor.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id]) res = self.client.get(url) - self.assertTemplateUsed(res, 'admin/flavors/update.html') + self.assertTemplateUsed(res, constants.FLAVORS_UPDATE_VIEW_TEMPLATE) workflow = res.context['workflow'] expected_name = workflows.UpdateFlavor.name @@ -314,10 +315,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): self.mox.ReplayAll() - url = reverse('horizon:admin:flavors:update', args=[flavor.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id]) res = self.client.get(url) - self.assertRedirectsNoFollow(res, INDEX_URL) + self.assertRedirectsNoFollow(res, reverse(constants.FLAVORS_INDEX_URL)) @test.create_stubs({api.keystone: ('tenant_list',), api.nova: ('flavor_get', @@ -368,10 +369,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): self.mox.ReplayAll() # run get test - url = reverse('horizon:admin:flavors:update', args=[flavor.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, "admin/flavors/update.html") + self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE) # run post test workflow_data = {'flavor_id': flavor.id, @@ -385,7 +386,8 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): resp = self.client.post(url, workflow_data) self.assertNoFormErrors(resp) self.assertMessageCount(success=1) - self.assertRedirectsNoFollow(resp, INDEX_URL) + self.assertRedirectsNoFollow(resp, + reverse(constants.FLAVORS_INDEX_URL)) @test.create_stubs({api.keystone: ('tenant_list',), api.nova: ('flavor_get', @@ -438,10 +440,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): self.mox.ReplayAll() # run get test - url = reverse('horizon:admin:flavors:update', args=[flavor.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, "admin/flavors/update.html") + self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE) # run post test workflow_data = {'flavor_id': flavor.id, @@ -455,7 +457,8 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): resp = self.client.post(url, workflow_data) self.assertNoFormErrors(resp) self.assertMessageCount(success=1) - self.assertRedirectsNoFollow(resp, INDEX_URL) + self.assertRedirectsNoFollow(resp, + reverse(constants.FLAVORS_INDEX_URL)) @test.create_stubs({api.keystone: ('tenant_list',), api.nova: ('flavor_get', @@ -509,10 +512,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): self.mox.ReplayAll() # run get test - url = reverse('horizon:admin:flavors:update', args=[flavor.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, "admin/flavors/update.html") + self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE) # run post test workflow_data = {'flavor_id': flavor.id, @@ -526,7 +529,8 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): resp = self.client.post(url, workflow_data) self.assertNoFormErrors(resp) self.assertMessageCount(error=1) - self.assertRedirectsNoFollow(resp, INDEX_URL) + self.assertRedirectsNoFollow(resp, + reverse(constants.FLAVORS_INDEX_URL)) @test.create_stubs({api.keystone: ('tenant_list',), api.nova: ('flavor_get', @@ -591,10 +595,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): self.mox.ReplayAll() # run get test - url = reverse('horizon:admin:flavors:update', args=[flavor.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, "admin/flavors/update.html") + self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE) # run post test data = self._get_workflow_data(new_flavor, access=flavor_projects) @@ -602,7 +606,8 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): resp = self.client.post(url, data) self.assertNoFormErrors(resp) self.assertMessageCount(error=1, warning=0) - self.assertRedirectsNoFollow(resp, INDEX_URL) + self.assertRedirectsNoFollow(resp, + reverse(constants.FLAVORS_INDEX_URL)) @test.create_stubs({api.keystone: ('tenant_list',), api.nova: ('flavor_get', @@ -624,10 +629,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): self.mox.ReplayAll() # run get test - url = reverse('horizon:admin:flavors:update', args=[flavor.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, "admin/flavors/update.html") + self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE) # run post test workflow_data = {'flavor_id': flavor.id, @@ -673,10 +678,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): self.mox.ReplayAll() # get test - url = reverse('horizon:admin:flavors:update', args=[flavor_a.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor_a.id]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, "admin/flavors/update.html") + self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE) # post test data = {'flavor_id': new_flavor.id, @@ -710,10 +715,10 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): self.mox.ReplayAll() # run get test - url = reverse('horizon:admin:flavors:update', args=[flavor.id]) + url = reverse(constants.FLAVORS_UPDATE_URL, args=[flavor.id]) resp = self.client.get(url) self.assertEqual(resp.status_code, 200) - self.assertTemplateUsed(resp, "admin/flavors/update.html") + self.assertTemplateUsed(resp, constants.FLAVORS_UPDATE_VIEW_TEMPLATE) # run post test workflow_data = {'flavor_id': flavor.id, @@ -757,3 +762,94 @@ class UpdateFlavorWorkflowTests(BaseFlavorWorkflowTests): data = {'eph_gb': -1} self.generic_update_flavor_invalid_data_form_fails(override_data=data, error_msg=error) + + +class FlavorUpdateMetadataViewTest(test.BaseAdminViewTests): + @test.create_stubs({api.nova: ('flavor_get_extras',), + api.glance: ('metadefs_namespace_list', + 'metadefs_namespace_get')}) + def test_flavor_metadata_get(self): + # + flavor = self.flavors.list()[3] + + namespaces = self.metadata_defs.list() + + api.nova.flavor_get_extras( + IsA(http.HttpRequest), + flavor.id + ).AndReturn([flavor.extra_specs]) + api.glance.metadefs_namespace_list( + IsA(http.HttpRequest), + filters={ + 'resource_types': ['OS::Nova::Flavor'] + } + ).AndReturn((namespaces, False, False)) + + for namespace in namespaces: + api.glance.metadefs_namespace_get( + IsA(http.HttpRequest), + namespace.namespace, + 'OS::Nova::Flavor' + ).AndReturn(namespace) + + self.mox.ReplayAll() + res = self.client.get( + reverse( + constants.FLAVORS_UPDATE_METADATA_URL, + kwargs={'id': flavor.id} + ) + ) + + self.assertEqual(res.status_code, 200) + self.assertTemplateUsed( + res, + constants.FLAVORS_UPDATE_METADATA_TEMPLATE + ) + self.assertTemplateUsed( + res, + constants.FLAVORS_UPDATE_METADATA_SUBTEMPLATE + ) + self.assertContains(res, 'namespace_1') + self.assertContains(res, 'namespace_2') + self.assertContains(res, 'namespace_3') + self.assertContains(res, 'namespace_4') + + @test.create_stubs({api.nova: ('flavor_get_extras', + 'flavor_extra_set', + 'flavor_extra_delete')}) + def test_flavor_metadata_update(self): + # + flavor = self.flavors.list()[3] + + api.nova.flavor_get_extras( + IsA(http.HttpRequest), + flavor.id + ).AndReturn([flavor.extra_specs]) + api.nova.flavor_extra_set( + IsA(http.HttpRequest), + flavor.id, + {'key_mock': 'value_mock'} + ).AndReturn(None) + api.nova.flavor_extra_delete( + IsA(http.HttpRequest), + flavor.id, + [] + ).AndReturn(None) + + self.mox.ReplayAll() + + metadata = [{'value': 'value_mock', 'key': 'key_mock'}] + formData = {'metadata': json.dumps(metadata)} + + res = self.client.post( + reverse( + constants.FLAVORS_UPDATE_METADATA_URL, + kwargs={'id': flavor.id} + ), + formData + ) + + self.assertEqual(res.status_code, 302) + self.assertNoFormErrors(res) + self.assertRedirectsNoFollow(res, reverse(constants.FLAVORS_INDEX_URL)) + self.assertMessageCount(success=1) diff --git a/openstack_dashboard/dashboards/admin/flavors/urls.py b/openstack_dashboard/dashboards/admin/flavors/urls.py index c0ee720f28..e64a757d6c 100644 --- a/openstack_dashboard/dashboards/admin/flavors/urls.py +++ b/openstack_dashboard/dashboards/admin/flavors/urls.py @@ -16,18 +16,16 @@ # License for the specific language governing permissions and limitations # under the License. -from django.conf.urls import include # noqa from django.conf.urls import patterns # noqa from django.conf.urls import url # noqa -from openstack_dashboard.dashboards.admin.flavors.extras \ - import urls as extras_urls from openstack_dashboard.dashboards.admin.flavors import views urlpatterns = patterns('openstack_dashboard.dashboards.admin.flavors.views', url(r'^$', views.IndexView.as_view(), name='index'), url(r'^create/$', views.CreateView.as_view(), name='create'), + url(r'^(?P[^/]+)/update_metadata/$', + views.UpdateMetadataView.as_view(), name='update_metadata'), url(r'^(?P[^/]+)/update/$', views.UpdateView.as_view(), name='update'), - url(r'^(?P[^/]+)/extras/', include(extras_urls, namespace='extras')), ) diff --git a/openstack_dashboard/dashboards/admin/flavors/views.py b/openstack_dashboard/dashboards/admin/flavors/views.py index 4203f546e8..31fe791e2b 100644 --- a/openstack_dashboard/dashboards/admin/flavors/views.py +++ b/openstack_dashboard/dashboards/admin/flavors/views.py @@ -16,15 +16,21 @@ # License for the specific language governing permissions and limitations # under the License. +import json + from django.core.urlresolvers import reverse_lazy from django.utils.translation import ugettext_lazy as _ from horizon import exceptions +from horizon import forms from horizon import tables +from horizon.utils import memoized from horizon import workflows from openstack_dashboard import api +from openstack_dashboard.dashboards.admin.flavors \ + import forms as project_forms from openstack_dashboard.dashboards.admin.flavors \ import tables as project_tables from openstack_dashboard.dashboards.admin.flavors \ @@ -78,3 +84,60 @@ class UpdateView(workflows.WorkflowView): 'disk_gb': flavor.disk, 'swap_mb': flavor.swap or 0, 'eph_gb': getattr(flavor, 'OS-FLV-EXT-DATA:ephemeral', None)} + + +class UpdateMetadataView(forms.ModalFormView): + template_name = "admin/flavors/update_metadata.html" + form_class = project_forms.UpdateMetadataForm + success_url = reverse_lazy('horizon:admin:flavors:index') + + def get_initial(self): + extra_specs = self.get_object() + extra_specs_dict = dict((i.key, i.value) + for i in extra_specs) + return {'id': self.kwargs["id"], 'metadata': extra_specs_dict} + + def get_context_data(self, **kwargs): + context = super(UpdateMetadataView, self).get_context_data(**kwargs) + + extra_specs = self.get_object() + extra_specs_dict = dict((i.key, i.value) for i in extra_specs) + + try: + context['existing_metadata'] = json.dumps(extra_specs_dict) + except Exception: + exceptions.handle(self.request, + _('Unable to retrieve flavor metadata.')) + + resource_type = 'OS::Nova::Flavor' + try: + metadata = {} + # metadefs_namespace_list() returns a tuple with list as 1st elem + metadata["namespaces"] = [ + api.glance.metadefs_namespace_get(self.request, x.namespace, + resource_type) + for x in api.glance.metadefs_namespace_list( + self.request, + filters={"resource_types": [resource_type]} + )[0] + ] + + context['available_metadata'] = json.dumps(metadata) + except Exception: + msg = _('Unable to retrieve available metadata for ' + 'flavors.') + exceptions.handle(self.request, msg) + + context['id'] = self.kwargs['id'] + return context + + @memoized.memoized_method + def get_object(self): + flavor_id = self.kwargs['id'] + try: + extra_specs = api.nova.flavor_get_extras(self.request, flavor_id) + except Exception: + msg = _('Unable to retrieve the flavor metadata.') + exceptions.handle(self.request, msg) + else: + return extra_specs diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index dfa3b8e2bf..fdf6c9b668 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -1281,6 +1281,7 @@ class InstanceTests(helpers.TestCase): if custom_flavor_sort == 'id': # Reverse sorted by id sorted_flavors = ( + ('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', 'm1.metadata'), ('dddddddd-dddd-dddd-dddd-dddddddddddd', 'm1.secret'), ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'm1.massive'), ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'm1.tiny'), @@ -1288,6 +1289,7 @@ class InstanceTests(helpers.TestCase): elif custom_flavor_sort == 'name': sorted_flavors = ( ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'm1.massive'), + ('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', 'm1.metadata'), ('dddddddd-dddd-dddd-dddd-dddddddddddd', 'm1.secret'), ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'm1.tiny'), ) @@ -1296,6 +1298,7 @@ class InstanceTests(helpers.TestCase): ('dddddddd-dddd-dddd-dddd-dddddddddddd', 'm1.secret'), ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'm1.tiny'), ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'm1.massive'), + ('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', 'm1.metadata'), ) else: # Default - sorted by RAM @@ -1303,6 +1306,7 @@ class InstanceTests(helpers.TestCase): ('aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa', 'm1.tiny'), ('bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb', 'm1.massive'), ('dddddddd-dddd-dddd-dddd-dddddddddddd', 'm1.secret'), + ('eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee', 'm1.metadata'), ) select_options = '\n'.join([ diff --git a/openstack_dashboard/local/local_settings.py.example b/openstack_dashboard/local/local_settings.py.example index 6031f3eb02..58597a5861 100644 --- a/openstack_dashboard/local/local_settings.py.example +++ b/openstack_dashboard/local/local_settings.py.example @@ -507,22 +507,19 @@ SECURITY_GROUP_RULES = { }, } -FLAVOR_EXTRA_KEYS = { - 'flavor_keys': [ - ('quota:disk_read_bytes_sec', _('Quota: Read bytes')), - ('quota:disk_write_bytes_sec', _('Quota: Write bytes')), - ('quota:cpu_quota', _('Quota: CPU')), - ('quota:cpu_period', _('Quota: CPU period')), - ('quota:vif_inbound_average', _('Quota: Inbound average')), - ('quota:vif_outbound_average', _('Quota: Outbound average')), - ('hw:cpu_sockets', _('Quota: CPU sockets')), - ('hw:cpu_cores', _('Quota: CPU cores')), - ('hw:cpu_threads', _('Quota: CPU threads')), - ('hw:cpu_max_sockets', _('Quota: Max CPU sockets')), - ('hw:cpu_max_cores', _('Quota: Max CPU cores')), - ('hw:cpu_max_threads', _('Quota: Max CPU threads')), - ] -} +# Deprecation Notice: +# +# The setting FLAVOR_EXTRA_KEYS has been deprecated. +# Please load extra spec metadata into the Glance Metadata Definition Catalog. +# +# The sample quota definitions can be found in: +# /etc/metadefs/compute-quota.json +# +# The metadata definition catalog supports CLI and API: +# $glance --os-image-api-version 2 help md-namespace-import +# $glance-manage db_load_metadefs +# +# See Metadata Definitions on: http://docs.openstack.org/developer/glance/ # Indicate to the Sahara data processing service whether or not # automatic floating IP allocation is in effect. If it is not diff --git a/openstack_dashboard/test/helpers.py b/openstack_dashboard/test/helpers.py index e427e81e2f..c9d5b0bd56 100644 --- a/openstack_dashboard/test/helpers.py +++ b/openstack_dashboard/test/helpers.py @@ -436,6 +436,7 @@ def my_custom_sort(flavor): 'm1.secret': 0, 'm1.tiny': 1, 'm1.massive': 2, + 'm1.metadata': 3, } return sort_order[flavor.name] diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py index 7db494e03a..0a581997dd 100644 --- a/openstack_dashboard/test/settings.py +++ b/openstack_dashboard/test/settings.py @@ -200,16 +200,5 @@ POLICY_FILES = { 'compute': 'nova_policy.json' } -FLAVOR_EXTRA_KEYS = { - 'flavor_keys': [ - ('quota:disk_read_bytes_sec', 'Quota: Read bytes'), - ('quota:disk_write_bytes_sec', 'Quota: Write bytes'), - ('quota:cpu_quota', 'Quota: CPU'), - ('quota:cpu_period', 'Quota: CPU period'), - ('quota:vif_inbound_average', 'Quota: Inbound average'), - ('quota:vif_outbound_average', 'Quota: Outbound average'), - ] -} - # The openstack_auth.user.Token object isn't JSON-serializable ATM SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' diff --git a/openstack_dashboard/test/test_data/nova_data.py b/openstack_dashboard/test/test_data/nova_data.py index e26e1a02d2..530d027136 100644 --- a/openstack_dashboard/test/test_data/nova_data.py +++ b/openstack_dashboard/test/test_data/nova_data.py @@ -41,6 +41,17 @@ from openstack_dashboard.usage import quotas as usage_quotas from openstack_dashboard.test.test_data import utils +class FlavorExtraSpecs(dict): + def __repr__(self): + return "" % self._info + + def __init__(self, info): + super(FlavorExtraSpecs, self).__init__() + self.__dict__.update(info) + self.update(info) + self._info = info + + SERVER_DATA = """ { "server": { @@ -271,7 +282,19 @@ def data(TEST): 'extra_specs': {}, 'os-flavor-access:is_public': False, 'OS-FLV-EXT-DATA:ephemeral': 2048}) - TEST.flavors.add(flavor_1, flavor_2, flavor_3) + flavor_4 = flavors.Flavor(flavors.FlavorManager(None), + {'id': "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee", + 'name': 'm1.metadata', + 'vcpus': 1000, + 'disk': 1024, + 'ram': 10000, + 'swap': 0, + 'extra_specs': FlavorExtraSpecs( + {'key': 'key_mock', + 'value': 'value_mock'}), + 'os-flavor-access:is_public': False, + 'OS-FLV-EXT-DATA:ephemeral': 2048}) + TEST.flavors.add(flavor_1, flavor_2, flavor_3, flavor_4) flavor_access_manager = flavor_access.FlavorAccessManager(None) flavor_access_1 = flavor_access.FlavorAccess(flavor_access_manager,