diff options
17 files changed, 242 insertions, 232 deletions
diff --git a/.zuul.d/project.yaml b/.zuul.d/project.yaml index 05578fc05..0395f4afb 100644 --- a/.zuul.d/project.yaml +++ b/.zuul.d/project.yaml @@ -4,7 +4,6 @@ - horizon-cross-jobs - horizon-nodejs10-jobs - horizon-non-primary-django-jobs - - openstack-lower-constraints-jobs - openstack-python3-wallaby-jobs - periodic-stable-jobs - publish-openstack-docs-pti diff --git a/horizon/templates/auth/_login_form.html b/horizon/templates/auth/_login_form.html index 66ce8ff19..a4a4abad5 100644 --- a/horizon/templates/auth/_login_form.html +++ b/horizon/templates/auth/_login_form.html @@ -52,13 +52,13 @@ </p> </div> {% endif %} - {% if request.COOKIES.logout_reason %} - {% if request.COOKIES.logout_status == "success" %} + {% if logout_reason %} + {% if logout_status == "success" %} <div class="form-group clearfix error help-block alert alert-success" id="logout_reason"> {% else %} <div class="form-group clearfix error help-block alert alert-danger" id="logout_reason"> {% endif %} - <p>{{ request.COOKIES.logout_reason }}</p> + <p>{{ logout_reason }}</p> </div> {% endif %} {% if csrf_failure %} diff --git a/horizon/templates/auth/_password_form.html b/horizon/templates/auth/_password_form.html index 45ed92011..3968e767e 100644 --- a/horizon/templates/auth/_password_form.html +++ b/horizon/templates/auth/_password_form.html @@ -31,13 +31,13 @@ </div> {%endif%} <fieldset hz-login-finder> - {% if request.COOKIES.logout_reason %} - {% if request.COOKIES.logout_status == "success" %} + {% if logout_reason %} + {% if logout_status == "success" %} <div class="form-group clearfix error help-block alert alert-success" id="logout_reason"> {% else %} <div class="form-group clearfix error help-block alert alert-danger" id="logout_reason"> {% endif %} - <p>{{ request.COOKIES.logout_reason }}</p> + <p>{{ logout_reason }}</p> </div> {% endif %} {% include "horizon/common/_form_fields.html" %} diff --git a/horizon/utils/functions.py b/horizon/utils/functions.py index 1052c8ff8..d454156af 100644 --- a/horizon/utils/functions.py +++ b/horizon/utils/functions.py @@ -43,7 +43,7 @@ def add_logout_reason(request, response, reason, status='success'): # Store the translated string in the cookie lang = translation.get_language_from_request(request) with translation.override(lang): - reason = str(reason) + reason = force_text(reason).encode('unicode_escape').decode('ascii') response.set_cookie('logout_reason', reason, max_age=10) response.set_cookie('logout_status', status, max_age=10) diff --git a/lower-constraints.txt b/lower-constraints.txt deleted file mode 100644 index 8ef2e0f52..000000000 --- a/lower-constraints.txt +++ /dev/null @@ -1,157 +0,0 @@ -alabaster==0.7.10 -amqp==2.1.1 -appdirs==1.4.0 -asn1crypto==0.23.0 -Babel==2.6.0 -bandit==1.4.0 -cachetools==2.0.0 -cffi==1.14.0 -chardet==3.0.4 -cliff==2.8.0 -cmd2==0.8.0 -colorama==0.3.9 -contextlib2==0.4.0 -coverage==4.0 -cryptography==3.0 -debtcollector==1.2.0 -decorator==3.4.0 -deprecation==1.0 -Django==2.2 -django-appconf==1.0.2 -django-compressor==2.0 -django-debreach==1.4.2 -django-pyscss==2.0.2 -docutils==0.11 -dogpile.cache==0.6.2 -dulwich==0.15.0 -enmerkar==0.7.1 -eventlet==0.18.2 -extras==1.0.0 -fasteners==0.7.0 -fixtures==3.0.0 -freezegun==0.3.15 -futurist==1.2.0 -greenlet==0.4.10 -idna==2.6 -imagesize==0.7.1 -iso8601==0.1.11 -Jinja2==2.10 -jmespath==0.9.0 -jsonpatch==1.16 -jsonpointer==1.13 -jsonschema==2.6.0 -keystoneauth1==3.4.0 -kombu==4.0.0 -linecache2==1.0.0 -MarkupSafe==1.0 -mccabe==0.6.0 -monotonic==0.6 -msgpack-python==0.4.0 -munch==2.1.0 -netaddr==0.7.18 -netifaces==0.10.4 -nodeenv==0.9.4 -openstacksdk==0.11.2 -os-client-config==1.28.0 -os-service-types==1.2.0 -osc-lib==1.8.0 -oslo.concurrency==3.26.0 -oslo.config==5.2.0 -oslo.context==2.22.0 -oslo.i18n==3.15.3 -oslo.log==3.36.0 -oslo.messaging==5.29.0 -oslo.middleware==3.31.0 -oslo.policy==3.2.0 -oslo.serialization==2.18.0 -oslo.service==1.24.0 -oslo.upgradecheck==0.1.1 -oslo.utils==3.40.0 -osprofiler==2.3.0 -Paste==2.0.2 -PasteDeploy==1.5.0 -pbr==2.0.0 -pep8==1.5.7 -pika==0.10.0 -pika-pool==0.1.3 -positional==1.2.1 -prettytable==0.7.2 -pycodestyle==2.5.0 -pycparser==2.18 -pyflakes==2.1.0 -Pygments==2.2.0 -pyinotify==0.9.6 -pymongo==3.0.2 -pyOpenSSL==19.1.0 -pyparsing==2.1.0 -pyperclip==1.5.27 -pyScss==1.3.7 -pytest==5.3.5 -pytest-django==3.8.0 -pytest-html==2.0.1 -python-cinderclient==5.0.0 -python-dateutil==2.8.1 -python-glanceclient==2.8.0 -python-keystoneclient==3.22.0 -python-memcached==1.59 -python-mimeparse==1.6.0 -python-neutronclient==6.7.0 -python-novaclient==9.1.0 -python-swiftclient==3.2.0 -pytz==2013.6 -PyYAML==3.12 -rcssmin==1.0.6 -reno==3.1.0 -repoze.lru==0.7 -requests==2.14.2 -requestsexceptions==1.2.0 -restructuredtext-lint==1.1.1 -rfc3986==0.3.1 -rjsmin==1.0.12 -Routes==2.3.1 -selenium==2.50.1 -semantic-version==2.3.1 -simplejson==3.5.1 -six==1.12.0 -snowballstemmer==1.2.1 -statsd==3.2.1 -stevedore==1.20.0 -tenacity==3.2.1 -termcolor==1.1.0 -testscenarios==0.4 -testtools==2.2.0 -traceback2==1.4.0 -unittest2==1.1.0 -vine==1.1.4 -warlock==1.2.0 -WebOb==1.7.1 -wrapt==1.11 -XStatic==1.0.0 -XStatic-Angular==1.5.8.0 -XStatic-Angular-Bootstrap==2.2.0.0 -XStatic-Angular-FileUpload==12.0.4.0 -XStatic-Angular-Gettext==2.3.8.0 -XStatic-Angular-lrdragndrop==1.0.2.2 -XStatic-Angular-Schema-Form==0.8.13.0 -XStatic-Bootstrap-Datepicker==1.3.1.0 -XStatic-Bootstrap-SCSS==3.3.7.1 -XStatic-bootswatch==3.3.7.0 -XStatic-D3==3.5.17.0 -XStatic-Font-Awesome==4.7.0.0 -XStatic-Hogan==2.0.0.2 -XStatic-Jasmine==2.4.1.1 -XStatic-jQuery==1.12.4.1 -XStatic-JQuery-Migrate==1.2.1.1 -XStatic-jquery-ui==1.12.1.1 -XStatic-JQuery.quicksearch==2.0.3.1 -XStatic-JQuery.TableSorter==2.14.5.1 -XStatic-JSEncrypt==2.3.1.1 -XStatic-mdi==1.6.50.2 -XStatic-objectpath==1.2.1.0 -XStatic-Rickshaw==1.5.0.0 -XStatic-roboto-fontface==0.5.0.0 -XStatic-smart-table==1.4.13.2 -XStatic-Spin==1.2.5.2 -XStatic-term.js==0.0.7.0 -XStatic-tv4==1.2.7.0 -xvfbwrapper==0.1.3 diff --git a/openstack_auth/locale/ru/LC_MESSAGES/django.po b/openstack_auth/locale/ru/LC_MESSAGES/django.po index ce00724df..3b02d5691 100644 --- a/openstack_auth/locale/ru/LC_MESSAGES/django.po +++ b/openstack_auth/locale/ru/LC_MESSAGES/django.po @@ -2,16 +2,17 @@ # Ilya Alekseyev <ilyaalekseyev@acm.org>, 2018. #zanata # Roman Gorshunov <roman.gorshunov@att.com>, 2019. #zanata # Dmitriy Rabotyagov <noonedeadpunk@ya.ru>, 2020. #zanata +# Roman Gorshunov <roman.gorshunov@att.com>, 2021. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2020-01-08 17:20+0000\n" +"POT-Creation-Date: 2021-10-01 09:12+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2020-01-08 12:42+0000\n" -"Last-Translator: Dmitriy Rabotyagov <noonedeadpunk@ya.ru>\n" +"PO-Revision-Date: 2021-09-06 04:36+0000\n" +"Last-Translator: Roman Gorshunov <roman.gorshunov@att.com>\n" "Language-Team: Russian\n" "Language: ru\n" "X-Generator: Zanata 4.3.3\n" @@ -27,6 +28,11 @@ msgstr "Аутентифицировать с использованием" msgid "Confirm password" msgstr "Подтвердите пароль" +msgid "Cookies may be turned off. Make sure cookies are enabled and try again." +msgstr "" +"Механизм Cookies может быть выключен. Убедитесь, что cookies разрешены и " +"попробуйте снова." + msgid "Could not find service provider ID on keystone." msgstr "Не удалось найти ID поставщика служб в Keystone." diff --git a/openstack_auth/views.py b/openstack_auth/views.py index 353dc9915..451ac8422 100644 --- a/openstack_auth/views.py +++ b/openstack_auth/views.py @@ -66,6 +66,11 @@ def get_csrf_reason(reason): return reason +def set_logout_reason(res, msg): + msg = msg.encode('unicode_escape').decode('ascii') + res.set_cookie('logout_reason', msg, max_age=10) + + # TODO(stephenfin): Migrate to CBV @sensitive_post_parameters() @csrf_protect @@ -122,6 +127,9 @@ def login(request): choices = settings.WEBSSO_CHOICES reason = get_csrf_reason(request.GET.get('csrf_failure')) + logout_reason = request.COOKIES.get( + 'logout_reason', '').encode('ascii').decode('unicode_escape') + logout_status = request.COOKIES.get('logout_status') extra_context = { 'redirect_field_name': auth.REDIRECT_FIELD_NAME, 'csrf_failure': reason, @@ -131,6 +139,8 @@ def login(request): 'single_value': '', 'label': '', }, + 'logout_reason': logout_reason, + 'logout_status': logout_status, } if request.is_ajax(): @@ -150,7 +160,7 @@ def login(request): res = django_http.HttpResponseRedirect( reverse('password', args=[exc.user_id])) msg = _("Your password has expired. Please set a new password.") - res.set_cookie('logout_reason', msg, max_age=10) + set_logout_reason(res, msg) # Save the region in the cookie, this is used as the default # selected region next time the Login form loads. @@ -201,7 +211,7 @@ def websso(request): else: msg = 'Login failed: %s' % exc res = django_http.HttpResponseRedirect(settings.LOGIN_URL) - res.set_cookie('logout_reason', msg, max_age=10) + set_logout_reason(res, msg) return res auth_user.set_session_from_user(request, request.user) @@ -373,7 +383,7 @@ def switch_keystone_provider(request, keystone_provider=None, except exceptions.KeystoneAuthException as exc: msg = 'Keystone provider switch failed: %s' % exc res = django_http.HttpResponseRedirect(settings.LOGIN_URL) - res.set_cookie('logout_reason', msg, max_age=10) + set_logout_reason(res, msg) return res auth.login(request, request.user) auth_user.set_session_from_user(request, request.user) @@ -403,5 +413,5 @@ class PasswordView(edit_views.FormView): # We have no session here, so regular messages don't work. msg = _('Password changed. Please log in to continue.') res = django_http.HttpResponseRedirect(self.success_url) - res.set_cookie('logout_reason', msg, max_age=10) + set_logout_reason(res, msg) return res diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index 674ebdca2..53e8b4b23 100644 --- a/openstack_dashboard/dashboards/project/instances/tables.py +++ b/openstack_dashboard/dashboards/project/instances/tables.py @@ -39,6 +39,8 @@ from horizon.utils import filters from openstack_dashboard import api from openstack_dashboard.dashboards.project.floating_ips import workflows from openstack_dashboard.dashboards.project.instances import tabs +from openstack_dashboard.dashboards.project.instances \ + import utils as instance_utils from openstack_dashboard.dashboards.project.instances.workflows \ import resize_instance from openstack_dashboard.dashboards.project.instances.workflows \ @@ -324,7 +326,7 @@ class ToggleSuspend(tables.BatchAction): if self.suspended: self.current_present_action = RESUME policy_rules = ( - ("compute", "os_compute_api:os-rescue"),) + ("compute", "os_compute_api:os-suspend-server:resume"),) else: self.current_present_action = SUSPEND policy_rules = ( @@ -789,8 +791,8 @@ class UpdateRow(tables.Row): def get_data(self, request, instance_id): instance = api.nova.server_get(request, instance_id) try: - instance.full_flavor = api.nova.flavor_get(request, - instance.flavor["id"]) + instance.full_flavor = instance_utils.resolve_flavor(request, + instance) except Exception: exceptions.handle(request, _('Unable to retrieve flavor information ' @@ -1041,7 +1043,7 @@ def get_flavor(instance): "size_disk": size_disk, "size_ram": size_ram, "vcpus": instance.full_flavor.vcpus, - "flavor_id": instance.full_flavor.id + "flavor_id": getattr(instance.full_flavor, 'id', None) } return template.loader.render_to_string(template_name, context) return _("Not available") diff --git a/openstack_dashboard/dashboards/project/instances/templates/instances/_flavors_and_quotas.html b/openstack_dashboard/dashboards/project/instances/templates/instances/_flavors_and_quotas.html index 1f4b272a4..99a6e321b 100644 --- a/openstack_dashboard/dashboards/project/instances/templates/instances/_flavors_and_quotas.html +++ b/openstack_dashboard/dashboards/project/instances/templates/instances/_flavors_and_quotas.html @@ -20,7 +20,7 @@ <div class="quota_title"> <strong class="pull-left">{% trans "Number of Instances" %}</strong> <span class="pull-right"> - {% blocktrans trimmed with used=usages.instances.used|intcomma quota=usages.instances.quota|intcomma|quotainf %} + {% blocktrans trimmed with used=usages.totalInstancesUsed|intcomma quota=usages.maxTotalInstances|intcomma|quotainf %} {{ used }} of {{ quota }} Used {% endblocktrans %} </span> @@ -30,9 +30,9 @@ <div id="{{ resize_instance|yesno:"quota_resize_instance,quota_instances" }}" class="quota_bar" data-progress-indicator-flavor - data-quota-limit="{{ usages.instances.quota }}" - data-quota-used="{{ usages.instances.used }}"> - {% widthratio usages.instances.used usages.instances.quota 100 as instance_percent %} + data-quota-limit="{{ usages.maxTotalInstances }}" + data-quota-used="{{ usages.totalInstancesUsed }}"> + {% widthratio usages.totalInstancesUsed usages.maxTotalInstances 100 as instance_percent %} {% bs_progress_bar instance_percent 0 %} </div> {{ endminifyspace }} @@ -40,7 +40,7 @@ <div class="quota_title"> <strong class="pull-left">{% trans "Number of VCPUs" %}</strong> <span class="pull-right"> - {% blocktrans trimmed with used=usages.cores.used|intcomma quota=usages.cores.quota|intcomma|quotainf %} + {% blocktrans trimmed with used=usages.totalCoresUsed|intcomma quota=usages.maxTotalCores|intcomma|quotainf %} {{ used }} of {{ quota }} Used {% endblocktrans %} </span> @@ -50,9 +50,9 @@ <div id="quota_vcpus" class="quota_bar" data-progress-indicator-flavor - data-quota-limit="{{ usages.cores.quota }}" - data-quota-used="{{ usages.cores.used }}"> - {% widthratio usages.cores.used usages.cores.quota 100 as vcpu_percent %} + data-quota-limit="{{ usages.maxTotalCores }}" + data-quota-used="{{ usages.totalCoresUsed }}"> + {% widthratio usages.totalCoresUsed usages.maxTotalCores 100 as vcpu_percent %} {% bs_progress_bar vcpu_percent 0 %} </div> {{ endminifyspace }} @@ -60,7 +60,7 @@ <div class="quota_title"> <strong class="pull-left">{% trans "Total RAM" %}</strong> <span class="pull-right"> - {% blocktrans trimmed with used=usages.ram.used|intcomma quota=usages.ram.quota|intcomma|quotainf %} + {% blocktrans trimmed with used=usages.totalRAMUsed|intcomma quota=usages.maxTotalRAMSize|intcomma|quotainf %} {{ used }} of {{ quota }} MB Used {% endblocktrans %} </span> @@ -70,9 +70,9 @@ <div id="quota_ram" class="quota_bar" data-progress-indicator-flavor - data-quota-limit="{{ usages.ram.quota }}" - data-quota-used="{{ usages.ram.used }}"> - {% widthratio usages.ram.used usages.ram.quota 100 as vcpu_percent %} + data-quota-limit="{{ usages.maxTotalRAMSize }}" + data-quota-used="{{ usages.totalRAMUsed }}"> + {% widthratio usages.totalRAMUsed usages.maxTotalRAMSize 100 as vcpu_percent %} {% bs_progress_bar vcpu_percent 0 %} </div> {{ endminifyspace }} @@ -81,7 +81,7 @@ <div class="quota_title"> <strong class="pull-left">{% trans "Number of Volumes" %}</strong> <span class="pull-right"> - {% blocktrans with used=usages.volumes.used|intcomma quota=usages.volumes.quota|intcomma|quotainf %} + {% blocktrans with used=usages.totalVolumesUsed|intcomma quota=usages.maxTotalVolumes|intcomma|quotainf %} {{ used }} of {{ quota }} Used {% endblocktrans %} </span> @@ -89,16 +89,16 @@ <div id="quota_volume" class="quota_bar" data-progress-indicator-flavor - data-quota-limit="{{ usages.volumes.quota }}" - data-quota-used="{{ usages.volumes.used }}"> - {% widthratio usages.volumes.used usages.volumes.quota 100 as volume_percent %} + data-quota-limit="{{ usages.maxTotalVolumes }}" + data-quota-used="{{ usages.totalVolumesUsed }}"> + {% widthratio usages.totalVolumesUsed usages.maxTotalVolumes 100 as volume_percent %} {% bs_progress_bar volume_percent 0 %} </div> <div class="quota_title"> <strong class="pull-left">{% trans "Total Volume Storage" %}</strong> <span class="pull-right"> - {% blocktrans with used=usages.gigabytes.used|intcomma quota=usages.gigabytes.quota|intcomma|quotainf %} + {% blocktrans with used=usages.totalGigabytesUsed|intcomma quota=usages.maxTotalVolumeGigabytes|intcomma|quotainf %} {{ used }} of {{ quota }} GiB Used {% endblocktrans %} </span> @@ -106,9 +106,9 @@ <div id="quota_volume_storage" class="quota_bar" data-progress-indicator-flavor - data-quota-limit="{{ usages.gigabytes.quota }}" - data-quota-used="{{ usages.gigabytes.used }}"> - {% widthratio usages.gigabytes.used usages.gigabytes.quota 100 as volume_storage_percent %} + data-quota-limit="{{ usages.maxTotalVolumeGigabytes }}" + data-quota-used="{{ usages.totalGigabytesUsed }}"> + {% widthratio usages.totalGigabytesUsed usages.maxTotalVolumeGigabytes 100 as volume_storage_percent %} {% bs_progress_bar volume_storage_percent 0 %} </div> {% endif %} diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 8fc35415b..c359b6efe 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -4139,13 +4139,26 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): def test_disassociate_floating_ip_with_release(self): self._test_disassociate_floating_ip(is_release=True) + def _populate_server_flavor_nova_api_ge_2_47(self, server): + flavor_id = server.flavor['id'] + flavor = self.flavors.get(id=flavor_id) + server.flavor = { + 'original_name': flavor.name, + 'vcpus': flavor.vcpus, + 'ram': flavor.ram, + 'swap': flavor.swap, + 'disk': flavor.disk, + 'ephemeral': flavor.ephemeral, + 'extra_specs': flavor.extra_specs, + } + return server + @helpers.create_mocks({api.nova: ('server_get', 'flavor_list', 'server_group_list', 'tenant_absolute_limits', 'is_feature_available')}) - def test_instance_resize_get(self): - server = self.servers.first() + def _test_instance_resize_get(self, server, nova_api_lt_2_47=False): self.mock_server_get.return_value = server self.mock_flavor_list.return_value = self.flavors.list() self.mock_server_group_list.return_value = self.server_groups.list() @@ -4154,14 +4167,35 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): url = reverse('horizon:project:instances:resize', args=[server.id]) res = self.client.get(url) + workflow = res.context['workflow'] self.assertTemplateUsed(res, views.WorkflowView.template_name) + self.assertEqual(res.context['workflow'].name, + workflows.ResizeInstance.name) + self.assertContains(res, 'Disk Partition') config_drive_field_label = 'Configuration Drive' self.assertNotContains(res, config_drive_field_label) + step = workflow.get_step("flavor_choice") + self.assertEqual(step.action.initial['old_flavor_name'], + self.flavors.first().name) + + step = workflow.get_step("setadvancedaction") + self.assertEqual(step.action.fields['disk_config'].label, + 'Disk Partition') + self.assertQuerysetEqual(workflow.steps, + ['<SetFlavorChoice: flavor_choice>', + '<SetAdvanced: setadvancedaction>']) option = '<option value="%s">%s</option>' + + def is_original_flavor(server, flavor, nova_api_lt_2_47): + if nova_api_lt_2_47: + return flavor.id == server.flavor['id'] + else: + return flavor.name == server.flavor['original_name'] + for flavor in self.flavors.list(): - if flavor.id == server.flavor['id']: + if is_original_flavor(server, flavor, nova_api_lt_2_47): self.assertNotContains(res, option % (flavor.id, flavor.name)) else: self.assertContains(res, option % (flavor.id, flavor.name)) @@ -4176,6 +4210,15 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self.mock_tenant_absolute_limits.assert_called_once_with( helpers.IsHttpRequest(), reserved=True) + def test_instance_resize_get_nova_api_lt_2_47(self): + server = self.servers.first() + self._test_instance_resize_get(server, nova_api_lt_2_47=True) + + def test_instance_resize_get_nova_api_ge_2_47(self): + server = self.servers.first() + self._populate_server_flavor_nova_api_ge_2_47(server) + self._test_instance_resize_get(server) + @helpers.create_mocks({api.nova: ('server_get',)}) def test_instance_resize_get_server_get_exception(self): server = self.servers.first() @@ -4194,7 +4237,6 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): 'flavor_list',)}) def test_instance_resize_get_flavor_list_exception(self): server = self.servers.first() - self.mock_server_get.return_value = server self.mock_flavor_list.side_effect = self.exceptions.nova @@ -4208,6 +4250,8 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): server.id) self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) + # TODO(amotoki): This is requred only when nova API <=2.46 is used. + # Once server_get() uses nova API >=2.47 only, this test can be droppped. @helpers.create_mocks({api.nova: ('server_get', 'flavor_list', 'flavor_get', diff --git a/openstack_dashboard/dashboards/project/instances/utils.py b/openstack_dashboard/dashboards/project/instances/utils.py index 11bd5dc68..0639690db 100644 --- a/openstack_dashboard/dashboards/project/instances/utils.py +++ b/openstack_dashboard/dashboards/project/instances/utils.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import namedtuple import logging from operator import itemgetter @@ -232,3 +233,45 @@ def server_group_field_data(request): return [("", _("Select Server Group")), ] + server_groups_list return [("", _("No server groups available")), ] + + +def resolve_flavor(request, instance, flavors=None, **kwargs): + """Resolves name of instance flavor independent of API microversion + + :param request: django http request object + :param instance: api._nova.Server instance to resolve flavor + :param flavors: dict of flavors already retrieved + :param kwargs: flavor parameters to return if hit some flavor discrepancy + :return: flavor name or default placeholder + """ + def flavor_from_dict(flavor_dict): + """Creates flavor-like objects from dictionary + + :param flavor_dict: dictionary contains vcpu, ram, name, etc. values + :return: novaclient.v2.flavors.Flavor like object + """ + return namedtuple('Flavor', flavor_dict.keys())(*flavor_dict.values()) + + if flavors is None: + flavors = {} + flavor_id = instance.flavor.get('id') + if flavor_id: # Nova API <=2.46 + if flavor_id in flavors: + return flavors[flavor_id] + try: + return api.nova.flavor_get(request, flavor_id) + except Exception: + msg = _('Unable to retrieve flavor information ' + 'for instance "%s".') % instance.id + exceptions.handle(request, msg, ignore=True) + fallback_flavor = { + 'vcpus': 0, 'ram': 0, 'disk': 0, 'ephemeral': 0, 'swap': 0, + 'name': _('Not available'), + 'original_name': _('Not available'), + 'extra_specs': {}, + } + fallback_flavor.update(kwargs) + return flavor_from_dict(fallback_flavor) + else: + instance.flavor['name'] = instance.flavor['original_name'] + return flavor_from_dict(instance.flavor) diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py index f767c538f..29f3a1b8d 100644 --- a/openstack_dashboard/dashboards/project/instances/views.py +++ b/openstack_dashboard/dashboards/project/instances/views.py @@ -51,6 +51,8 @@ from openstack_dashboard.dashboards.project.instances \ from openstack_dashboard.dashboards.project.instances \ import tabs as project_tabs from openstack_dashboard.dashboards.project.instances \ + import utils as instance_utils +from openstack_dashboard.dashboards.project.instances \ import workflows as project_workflows from openstack_dashboard.dashboards.project.networks.ports \ import views as port_views @@ -607,19 +609,9 @@ class ResizeView(workflows.WorkflowView): redirect = reverse("horizon:project:instances:index") msg = _('Unable to retrieve instance details.') exceptions.handle(self.request, msg, redirect=redirect) - flavor_id = instance.flavor['id'] flavors = self.get_flavors() - if flavor_id in flavors: - instance.flavor_name = flavors[flavor_id].name - else: - try: - flavor = api.nova.flavor_get(self.request, flavor_id) - instance.flavor_name = flavor.name - except Exception: - msg = _('Unable to retrieve flavor information for instance ' - '"%s".') % instance_id - exceptions.handle(self.request, msg, ignore=True) - instance.flavor_name = _("Not available") + flavor = instance_utils.resolve_flavor(self.request, instance, flavors) + instance.flavor_name = flavor.name return instance @memoized.memoized_method @@ -640,7 +632,6 @@ class ResizeView(workflows.WorkflowView): initial.update( {'instance_id': self.kwargs['instance_id'], 'name': getattr(_object, 'name', None), - 'old_flavor_id': _object.flavor['id'], 'old_flavor_name': getattr(_object, 'flavor_name', ''), 'flavors': self.get_flavors()}) return initial diff --git a/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py b/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py index bbc6906c5..80b12383d 100644 --- a/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py +++ b/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py @@ -47,11 +47,12 @@ class SetFlavorChoiceAction(workflows.Action): "_flavors_and_quotas.html") def populate_flavor_choices(self, request, context): - old_flavor_id = context.get('old_flavor_id') + old_flavor_name = context.get('old_flavor_name') flavors = context.get('flavors').values() # Remove current flavor from the list of flavor choices - flavors = [flavor for flavor in flavors if flavor.id != old_flavor_id] + flavors = [flavor for flavor in flavors + if flavor.name != old_flavor_name] if flavors: if len(flavors) > 1: diff --git a/openstack_dashboard/locale/es/LC_MESSAGES/django.po b/openstack_dashboard/locale/es/LC_MESSAGES/django.po index 86fb62c58..318e82c55 100644 --- a/openstack_dashboard/locale/es/LC_MESSAGES/django.po +++ b/openstack_dashboard/locale/es/LC_MESSAGES/django.po @@ -1,26 +1,26 @@ # Alberto Laporte <alberto.riveralaporte@rackspace.com>, 2015. #zanata # Eduardo Gonzalez Gutierrez <dabarren@gmail.com>, 2015. #zanata # OpenStack Infra <zanata@openstack.org>, 2015. #zanata -# Alberto Molina Coballes <alb.molina@gmail.com>, 2016. #zanata +# Alberto Molina Coballes <alberto@tinaja.es>, 2016. #zanata # Eddie Ramirez <djedi.r@gmail.com>, 2016. #zanata # Eduardo Gonzalez Gutierrez <dabarren@gmail.com>, 2016. #zanata # Pablo Caruana <pcaruana@redhat.com>, 2016. #zanata # Pablo Iranzo Gómez <Pablo.Iranzo@gmail.com>, 2016. #zanata -# Alberto Molina Coballes <alb.molina@gmail.com>, 2017. #zanata +# Alberto Molina Coballes <alberto@tinaja.es>, 2017. #zanata # Zeus Arias Lucero <zeusariaslucero@gmail.com>, 2017. #zanata -# Alberto Molina Coballes <alb.molina@gmail.com>, 2018. #zanata -# Alberto Molina Coballes <alb.molina@gmail.com>, 2019. #zanata -# Alberto Molina Coballes <alb.molina@gmail.com>, 2021. #zanata +# Alberto Molina Coballes <alberto@tinaja.es>, 2018. #zanata +# Alberto Molina Coballes <alberto@tinaja.es>, 2019. #zanata +# Alberto Molina Coballes <alberto@tinaja.es>, 2021. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2021-04-20 14:20+0000\n" +"POT-Creation-Date: 2021-10-18 17:35+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2021-05-23 07:02+0000\n" -"Last-Translator: Alberto Molina Coballes <alb.molina@gmail.com>\n" +"PO-Revision-Date: 2021-11-20 05:25+0000\n" +"Last-Translator: Alberto Molina Coballes <alberto@tinaja.es>\n" "Language-Team: Spanish\n" "Language: es\n" "X-Generator: Zanata 4.3.3\n" @@ -828,6 +828,13 @@ msgid "Building" msgstr "Construyendo" msgid "" +"By default, group type is created as public. To create a private group type, " +"uncheck this field." +msgstr "" +"El tipo de grupo se crea como público de forma predeterminada. Para crear un " +"tipo de grupo privado, desmarque este campo." + +msgid "" "By default, volume type is created as public. To create a private volume " "type, uncheck this field." msgstr "" @@ -2948,6 +2955,10 @@ msgstr "Tipos de grupos" msgid "Group has been updated successfully." msgstr "Se ha actualizado el grupo correctamente." +#, python-format +msgid "Group type name \"%s\" already exists." +msgstr "Ya existe un tipo de grupo con nombre \"%s\"." + msgid "Group type name can not be empty." msgstr "El tipo de grupo no puede estar vacío" @@ -3480,6 +3491,13 @@ msgid "Key Size (bits)" msgstr "Tamaño de clave (bits)" msgid "" +"Key names can only contain alphanumeric characters, underscores, periods, " +"colons and hyphens" +msgstr "" +"Los nombres de clave sólo pueden contener caracteres alfanuméricos, guiones " +"bajos, puntos, dos puntos y guiones." + +msgid "" "Key pair name may only contain letters, numbers, underscores, spaces, and " "hyphens and may not be white space." msgstr "" @@ -3802,6 +3820,10 @@ msgid "Modified domain \"%s\"." msgstr "Se ha modificado el dominio \"%s\"." #, python-format +msgid "Modified flavor access of \"%s\"." +msgstr "Se ha modificado el acceso al sabor \"%s\"." + +#, python-format msgid "Modified instance \"%s\"." msgstr "Modificada la instancia \"%s\"." @@ -5759,6 +5781,10 @@ msgid "Successfully created encryption for volume type: %s" msgstr "Se ha creado correctamente la encriptación de volumen: %s" #, python-format +msgid "Successfully created group type: %s" +msgstr "Se ha creado correctamente el tipo de grupo : %s" + +#, python-format msgid "Successfully created security group: %s" msgstr "El grupo de seguridad: %s fue creado correctamente" @@ -6185,6 +6211,16 @@ msgid "The specified port is invalid." msgstr "El puerto especificado no es válido." msgid "" +"The status of a volume backup is normally managed automatically. In some " +"circumstances an administrator may need to explicitly update the status " +"value. This is equivalent to the <tt>cinder backup-reset-state</tt> command." +msgstr "" +"El estado de una copia de seguridad de volumen Normalmente se controla " +"automáticamente. En algunas circunstancias, un administrador puede que tenga " +"que actualizar de forma explícita el valor de estado. Esto es equivalente " +"al comando<tt>cinder backup-reset-state</tt> ." + +msgid "" "The status of a volume is normally managed automatically. In some " "circumstances an administrator may need to explicitly update the status " "value. This is equivalent to the <tt>openstack volume set --state</tt> " @@ -6795,6 +6831,10 @@ msgid "Unable to modify domain \"%s\"." msgstr "No ha sido posible modificar el dominio \"%s\"." #, python-format +msgid "Unable to modify flavor access of \"%s\"." +msgstr "No ha sido posible modificar el acceso al sabor \"%s\"." + +#, python-format msgid "Unable to modify instance \"%s\"." msgstr "No ha sido posible modificar la instancia \"%s\"." @@ -8241,12 +8281,27 @@ msgstr "e.j Si/ No" msgid "e.g. Yes/No" msgstr "p. ej. Sí/No" +msgid "" +"flavor id can only contain alphanumeric characters, underscores, periods, " +"hyphens, spaces." +msgstr "" +"Los id de sabor sólo pueden contener caracteres alfanuméricos, guión bajo, " +"puntos, guiones y espacios." + msgid "front-end" msgstr "acceso a procesos" msgid "instance" msgstr "instancia" +#, python-format +msgid "" +"key with name \"%s\" already exists.Use Edit to update the value, else " +"create key with different name." +msgstr "" +"Ya existe una clave con nombre \"%s\". Utilice Editar para actualizar el " +"valor o cree una clave con un nombre diferente." + msgid "no" msgstr "no" diff --git a/openstack_dashboard/locale/ru/LC_MESSAGES/djangojs.po b/openstack_dashboard/locale/ru/LC_MESSAGES/djangojs.po index b6242f029..97b78e5e0 100644 --- a/openstack_dashboard/locale/ru/LC_MESSAGES/djangojs.po +++ b/openstack_dashboard/locale/ru/LC_MESSAGES/djangojs.po @@ -20,16 +20,17 @@ # Ilya Alekseyev <ilyaalekseyev@acm.org>, 2019. #zanata # Roman Gorshunov <roman.gorshunov@att.com>, 2019. #zanata # Dmitriy Rabotyagov <noonedeadpunk@ya.ru>, 2020. #zanata +# Roman Gorshunov <roman.gorshunov@att.com>, 2021. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2020-09-16 17:23+0000\n" +"POT-Creation-Date: 2021-10-01 09:11+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2020-09-16 04:40+0000\n" -"Last-Translator: Dmitriy Rabotyagov <noonedeadpunk@ya.ru>\n" +"PO-Revision-Date: 2021-09-06 04:37+0000\n" +"Last-Translator: Roman Gorshunov <roman.gorshunov@att.com>\n" "Language-Team: Russian\n" "Language: ru\n" "X-Generator: Zanata 4.3.3\n" @@ -633,6 +634,9 @@ msgstr[2] "Подтвердить удаление пользователей" msgid "Confirm Password" msgstr "Подтвердите пароль" +msgid "Confirm password" +msgstr "Подтвердите пароль" + msgid "Connecting" msgstr "Подключение" @@ -2172,6 +2176,9 @@ msgstr "Родительский порт" msgid "Password" msgstr "Пароль" +msgid "Passwords do not match." +msgstr "Пароли не совпадают." + msgid "Pending Delete" msgstr "Ожидает удаления" @@ -2269,6 +2276,9 @@ msgstr[2] "Проекты" msgid "Project ID" msgstr " ID проекта" +msgid "Project Name" +msgstr "Имя проекта" + msgid "" "Project networks are created by users.\n" " These networks are fully isolated and are project-specific." @@ -2533,6 +2543,9 @@ msgstr "Выберите одно" msgid "Select one or more" msgstr "Выберите одну или несколько" +msgid "Select one or more ports" +msgstr "Выберите один или несколько портов" + msgid "Select one or more security groups from the available groups below." msgstr "Выберите одну или более групп безопасности из доступных." @@ -2615,6 +2628,9 @@ msgstr "" msgid "Service type is not enabled: %(desiredType)s" msgstr "Тип службы не включен: %(desiredType)s" +msgid "Set admin password" +msgstr "Пароль администратора" + #, python-format msgid "Setting is not enabled: %(setting)s" msgstr "Настройка не включена: %(setting)s" diff --git a/releasenotes/notes/bug_1963652_fix_policy_for_resume-a719efb23054c708.yaml b/releasenotes/notes/bug_1963652_fix_policy_for_resume-a719efb23054c708.yaml new file mode 100644 index 000000000..6c2b31faf --- /dev/null +++ b/releasenotes/notes/bug_1963652_fix_policy_for_resume-a719efb23054c708.yaml @@ -0,0 +1,6 @@ +--- +fixes: + - | + Previously, ToggleSuspend class checked os-rescue policy for resume operation. + By this fix, the class checks 'os_compute_api:os-suspend-server:resume' policy + to align to resume operation. @@ -27,12 +27,6 @@ commands = find . -type f -name "*.pyc" -delete bash {toxinidir}/tools/unit_tests.sh {toxinidir} {posargs} -[testenv:lower-constraints] -deps = - -c{toxinidir}/lower-constraints.txt - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt - [testenv:venv] envdir = {toxworkdir}/venv commands = {posargs} |