diff options
9 files changed, 243 insertions, 36 deletions
diff --git a/doc/source/locale/pt_BR/LC_MESSAGES/doc.po b/doc/source/locale/pt_BR/LC_MESSAGES/doc.po index d76da1a50..ad80870f2 100644 --- a/doc/source/locale/pt_BR/LC_MESSAGES/doc.po +++ b/doc/source/locale/pt_BR/LC_MESSAGES/doc.po @@ -1,14 +1,15 @@ # Rodrigo Loures <rmoraesloures@gmail.com>, 2018. #zanata +# Yago Lourenço <yagoasl@labnet.nce.ufrj.br>, 2022. #zanata msgid "" msgstr "" -"Project-Id-Version: horizon 16.0.0.0b2.dev26\n" +"Project-Id-Version: horizon 18.6.4.dev3\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2019-06-24 03:59-0500\n" +"POT-Creation-Date: 2022-04-04 10:12+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2018-10-19 10:05+0000\n" -"Last-Translator: Rodrigo Loures <rmoraesloures@gmail.com>\n" +"PO-Revision-Date: 2022-04-11 11:50+0000\n" +"Last-Translator: Yago Lourenço <yagoasl@labnet.nce.ufrj.br>\n" "Language-Team: Portuguese (Brazil)\n" "Language: pt_BR\n" "X-Generator: Zanata 4.3.3\n" @@ -20,15 +21,60 @@ msgstr ":ref:`genindex`" msgid ":ref:`modindex`" msgstr ":ref:`modindex`" +msgid "" +"A Python class representing a sub-navigation item (e.g. \"instances\") which " +"contains all the necessary logic (views, forms, tests, etc.) for that " +"interface." +msgstr "" +"Uma classe Python que representa um item de sub-navegação (por exemplo, " +"\"instâncias\") que contém toda a lógica necessária (visualizações, " +"formulários, testes, etc.) para essa interface." + +msgid "" +"A Python class representing a top-level navigation item (e.g. \"project\") " +"which provides a consistent API for Horizon-compatible applications." +msgstr "" +"Uma classe Python que representa um item de navegação de nível superior (por " +"exemplo, \"projeto\") que fornece uma API consistente para aplicativos " +"compatíveis com o Horizon." + +msgid "Contributor Docs" +msgstr "Documentos do Colaborador" + msgid "Dashboard" msgstr "Dashboard" +msgid "" +"For a more in-depth look at Horizon and its architecture, see the :ref:" +"`contributor-intro`." +msgstr "" +"Para uma visão mais detalhada do Horizon e sua arquitetura, veja a :ref:" +"`contributor-intro`." + +msgid "" +"For those wishing to develop Horizon itself, or go in-depth with building " +"your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes, the " +"following documentation is provided." +msgstr "" +"Para aqueles que desejam desenvolver o próprio Horizon, ou aprofundar a " +"construção de suas próprias classes :class:`~horizon.Dashboard` ou :class:" +"`~horizon.Panel`, a seguinte documentação é fornecida." + msgid "Glossary" msgstr "Glossário" msgid "Horizon" msgstr "Horizon" +msgid "" +"Horizon is the canonical implementation of `OpenStack's Dashboard <https://" +"github.com/openstack/horizon>`_, which provides a web based user interface " +"to OpenStack services including Nova, Swift, Keystone, etc." +msgstr "" +"Horizon é a implementação canônica do `OpenStack's Dashboard <https://github." +"com/openstack/horizon>`_, que fornece uma interface de usuário baseada na " +"web para serviços OpenStack, incluindo Nova, Swift, Keystone, etc." + msgid "Horizon: The OpenStack Dashboard Project" msgstr "Horizon: o projeto de Dashboard do Openstack" @@ -50,10 +96,30 @@ msgstr "Projeto" msgid "Release Notes" msgstr "Notas de versão" +msgid "See https://docs.openstack.org/releasenotes/horizon/." +msgstr "Veja https://docs.openstack.org/releasenotes/horizon/." + +msgid "" +"The OpenStack dashboard project. Also the name of the top-level Python " +"object which handles registration for the app." +msgstr "" +"O painel de projeto OpenStack. Também o nome do objeto Python de nível " +"superior que trata do registro do aplicativo." + msgid "To learn what you need to know to get going, see the :ref:`quickstart`." msgstr "" "Para aprender o que você precisa saber para iniciar, veja o :ref:" "`quickstart`." +msgid "" +"Used in user-facing text in place of the term \"Tenant\" which is Keystone's " +"word." +msgstr "" +"Usado em texto voltado para o usuário no lugar do termo \"Tenant\", que é a " +"palavra da Keystone." + +msgid "User Documentation" +msgstr "Documentação do usuário" + msgid "Using Horizon" msgstr "Usando o Horizon" diff --git a/openstack_auth/locale/pt_BR/LC_MESSAGES/django.po b/openstack_auth/locale/pt_BR/LC_MESSAGES/django.po index 36814acdf..3d8e00f7c 100644 --- a/openstack_auth/locale/pt_BR/LC_MESSAGES/django.po +++ b/openstack_auth/locale/pt_BR/LC_MESSAGES/django.po @@ -1,15 +1,16 @@ # Fernando Pimenta <fernando.c.pimenta@gmail.com>, 2018. #zanata # Marcelo Dieder <marcelodieder@gmail.com>, 2018. #zanata +# Yago Lourenço <yagoasl@labnet.nce.ufrj.br>, 2022. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" -"POT-Creation-Date: 2019-09-13 12:32+0000\n" +"POT-Creation-Date: 2022-04-04 10:12+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"PO-Revision-Date: 2018-10-03 07:54+0000\n" -"Last-Translator: Fernando Pimenta <fernando.c.pimenta@gmail.com>\n" +"PO-Revision-Date: 2022-04-11 11:39+0000\n" +"Last-Translator: Yago Lourenço <yagoasl@labnet.nce.ufrj.br>\n" "Language-Team: Portuguese (Brazil)\n" "Language: pt_BR\n" "X-Generator: Zanata 4.3.3\n" @@ -22,6 +23,14 @@ msgstr "" msgid "Authenticate using" msgstr "Autenticar utilizando" +msgid "Confirm password" +msgstr "Confirmar senha" + +msgid "Cookies may be turned off. Make sure cookies are enabled and try again." +msgstr "" +"Os cookies podem ser desativados. Certifique-se de que os cookies estão " +"ativados e tente novamente." + msgid "Could not find service provider ID on keystone." msgstr "Não foi possível localizar o ID do provedor do serviço no keystone." @@ -40,6 +49,9 @@ msgstr "Credenciais inválidas" msgid "K2K Federation not setup for this session" msgstr "Federação K2K não configurada para esta sessão" +msgid "New password" +msgstr "Nova Senha" + msgid "" "No authentication backend could be determined to handle the provided " "credentials." @@ -47,9 +59,24 @@ msgstr "" "Nenhum backend de autenticação pode ser determinado para lidar com as " "credenciais fornecidas." +msgid "Old password and new password must be different." +msgstr "A senha antiga e a nova senha devem ser diferentes." + +msgid "Original password" +msgstr "Senha Original" + msgid "Password" msgstr "Senha" +msgid "Password changed. Please log in to continue." +msgstr "Senha alterada. Por favor faça o login para continuar." + +msgid "Password expired." +msgstr "Senha expirada." + +msgid "Passwords do not match." +msgstr "As senhas não coincidem." + #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "Por favor, considere alterar sua senha, ela irá expirar em %s minutos." @@ -66,6 +93,10 @@ msgid "Service provider authentication failed. %s" msgstr "Autenticação do provedor de serviços falhou. %s" #, python-format +msgid "Switch to Keystone Provider \"%(keystone_provider)s\" successful." +msgstr "Mudar para o Provedor Keystone \"%(keystone_provider)s\" com sucesso." + +#, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Troca para o projeto \"%(project_name)s\" com sucesso." @@ -81,8 +112,14 @@ msgstr "Não é possível recuperar domínios autorizados." msgid "Unable to retrieve authorized projects." msgstr "Não é possível recuperar projetos autorizados." +msgid "Unable to update the user password." +msgstr "Não foi possível atualizar a senha do usuário." + msgid "User Name" msgstr "Nome de Usuário" msgid "You are not authorized for any projects or domains." msgstr "Você não está autorizado para nenhum projeto ou domínio." + +msgid "Your password has expired. Please set a new password." +msgstr "Sua senha expirou. Por favor, defina uma nova senha." diff --git a/openstack_dashboard/dashboards/admin/instances/views.py b/openstack_dashboard/dashboards/admin/instances/views.py index 8ca198772..d8a6e2cb8 100644 --- a/openstack_dashboard/dashboards/admin/instances/views.py +++ b/openstack_dashboard/dashboards/admin/instances/views.py @@ -108,6 +108,12 @@ class AdminIndexView(tables.PagedTableMixin, tables.DataTableView): exceptions.handle(self.request, ignore=True) return {} + def _get_images_by_name(self, image_name): + result = api.glance.image_list_detailed( + self.request, filters={'name': image_name}) + images = result[0] + return dict((image.id, image) for image in images) + def _get_flavors(self): # Gather our flavors to correlate against IDs try: @@ -151,22 +157,32 @@ class AdminIndexView(tables.PagedTableMixin, tables.DataTableView): self._needs_filter_first = False - instances = self._get_instances(search_opts, sort_dir) results = futurist_utils.call_functions_parallel( - (self._get_images, [tuple(instances)]), self._get_flavors, self._get_tenants) - image_dict, flavor_dict, tenant_dict = results + flavor_dict, tenant_dict = results - non_api_filter_info = ( + non_api_filter_info = [ ('project', 'tenant_id', tenant_dict.values()), - ('image_name', 'image', image_dict.values()), ('flavor_name', 'flavor', flavor_dict.values()), - ) + ] + + filter_by_image_name = 'image_name' in search_opts + if filter_by_image_name: + image_dict = self._get_images_by_name(search_opts['image_name']) + non_api_filter_info.append( + ('image_name', 'image', image_dict.values()) + ) + if not views.process_non_api_filters(search_opts, non_api_filter_info): self._more = False return [] + instances = self._get_instances(search_opts, sort_dir) + + if not filter_by_image_name: + image_dict = self._get_images(tuple(instances)) + # Loop through instances to get image, flavor and tenant info. for inst in instances: if hasattr(inst, 'image') and isinstance(inst.image, dict): diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index 56af9b8f3..5c4b18db1 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 \ @@ -332,7 +334,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 = ( @@ -799,8 +801,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 ' @@ -1055,7 +1057,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/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index b7ef8cac8..722ca8668 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -4602,12 +4602,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', 'tenant_absolute_limits', 'is_feature_available', 'extension_supported')}) - def test_instance_resize_get(self): + def _test_instance_resize_get(self, server, nova_api_lt_2_47=False): server = self.servers.first() self.mock_server_get.return_value = server self.mock_flavor_list.return_value = self.flavors.list() @@ -4618,14 +4632,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)) @@ -4640,6 +4675,15 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self._check_extension_supported({'DiskConfig': 1, 'ServerGroups': 1}) + 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() @@ -4655,10 +4699,9 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_get', - 'flavor_list',)}) + '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 @@ -4672,6 +4715,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 6e7a76295..cdc4d59b3 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 @@ -599,19 +601,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 @@ -632,7 +624,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 1fb4869a5..8634842f0 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/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. |