From 431fd6c16b721eb30102e001466ad2dced50d8cb Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Fri, 22 Mar 2019 18:16:26 +0900 Subject: [PATCH] Drop cinder consistency group support Cinder consistency group has been replaced by the generic group feature. Horizon support of the generic group (in the project dashboard) is available since Rocky release and it covers all existing support for consistency group in horizon. The consistency group support is horizon was marked as deprecated in Stein release [1]. This commit drops the consistency group support. [1] https://review.openstack.org/#/c/626846/ Change-Id: I11187d2b03b7e0033a6c6ba3f8be25b8b5e4dd74 --- openstack_dashboard/api/cinder.py | 129 ------ openstack_dashboard/api/microversions.py | 1 - .../cinder_policy.d/consistencygroup.yaml | 10 - .../dashboards/admin/volume_groups/tests.py | 2 +- .../project/cg_snapshots/__init__.py | 0 .../dashboards/project/cg_snapshots/forms.py | 76 ---- .../dashboards/project/cg_snapshots/panel.py | 51 --- .../dashboards/project/cg_snapshots/tables.py | 118 ----- .../dashboards/project/cg_snapshots/tabs.py | 34 -- .../templates/cg_snapshots/_create.html | 9 - .../cg_snapshots/_detail_overview.html | 46 -- .../templates/cg_snapshots/_update.html | 7 - .../templates/cg_snapshots/create.html | 7 - .../templates/cg_snapshots/update.html | 7 - .../dashboards/project/cg_snapshots/tests.py | 174 -------- .../dashboards/project/cg_snapshots/urls.py | 25 -- .../dashboards/project/cg_snapshots/views.py | 153 ------- .../dashboards/project/cgroups/__init__.py | 0 .../dashboards/project/cgroups/forms.py | 216 --------- .../dashboards/project/cgroups/panel.py | 51 --- .../dashboards/project/cgroups/tables.py | 175 -------- .../dashboards/project/cgroups/tabs.py | 34 -- .../templates/cgroups/_clone_cgroup.html | 9 - .../templates/cgroups/_create_snapshot.html | 10 - .../cgroups/templates/cgroups/_delete.html | 9 - .../templates/cgroups/_detail_overview.html | 34 -- .../templates/cgroups/_remove_vols.html | 7 - .../templates/cgroups/_snapshot_limits.html | 42 -- .../cgroups/templates/cgroups/_update.html | 7 - .../templates/cgroups/clone_cgroup.html | 7 - .../cgroups/templates/cgroups/create.html | 7 - .../templates/cgroups/create_snapshot.html | 7 - .../cgroups/templates/cgroups/delete.html | 7 - .../templates/cgroups/remove_vols.html | 7 - .../cgroups/templates/cgroups/update.html | 7 - .../dashboards/project/cgroups/tests.py | 355 --------------- .../dashboards/project/cgroups/urls.py | 44 -- .../dashboards/project/cgroups/views.py | 320 -------------- .../dashboards/project/cgroups/workflows.py | 414 ------------------ .../dashboards/project/vg_snapshots/forms.py | 1 - .../dashboards/project/volume_groups/tests.py | 2 +- .../_1340_project_consistency_groups_panel.py | 9 - .../_1350_project_cg_snapshots_panel.py | 10 - openstack_dashboard/settings.py | 1 - openstack_dashboard/test/settings.py | 10 - .../test/test_data/cinder_data.py | 48 -- .../test/unit/api/test_cinder.py | 79 ---- .../notes/drop-cgroup-c1f6b169ba10a5d3.yaml | 8 + 48 files changed, 10 insertions(+), 2776 deletions(-) delete mode 100644 openstack_dashboard/conf/cinder_policy.d/consistencygroup.yaml delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/__init__.py delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/forms.py delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/panel.py delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/tables.py delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/tabs.py delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_create.html delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_detail_overview.html delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_update.html delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/create.html delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/update.html delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/tests.py delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/urls.py delete mode 100644 openstack_dashboard/dashboards/project/cg_snapshots/views.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/__init__.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/forms.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/panel.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/tables.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/tabs.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_clone_cgroup.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_create_snapshot.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_delete.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_detail_overview.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_remove_vols.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_snapshot_limits.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_update.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/clone_cgroup.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create_snapshot.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/delete.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/remove_vols.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/templates/cgroups/update.html delete mode 100644 openstack_dashboard/dashboards/project/cgroups/tests.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/urls.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/views.py delete mode 100644 openstack_dashboard/dashboards/project/cgroups/workflows.py delete mode 100644 openstack_dashboard/enabled/_1340_project_consistency_groups_panel.py delete mode 100644 openstack_dashboard/enabled/_1350_project_cg_snapshots_panel.py create mode 100644 releasenotes/notes/drop-cgroup-c1f6b169ba10a5d3.yaml diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index 2bc6a6d2fb..34b7204829 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -120,18 +120,6 @@ class VolumeType(BaseCinderAPIResourceWrapper): 'os-extended-snapshot-attributes:project_id'] -class VolumeConsistencyGroup(BaseCinderAPIResourceWrapper): - - _attrs = ['id', 'name', 'description', 'status', 'availability_zone', - 'created_at', 'volume_types'] - - -class VolumeCGSnapshot(BaseCinderAPIResourceWrapper): - - _attrs = ['id', 'name', 'description', 'status', - 'created_at', 'consistencygroup_id'] - - class VolumeBackup(BaseCinderAPIResourceWrapper): _attrs = ['id', 'name', 'description', 'container', 'size', 'status', @@ -583,123 +571,6 @@ def volume_snapshot_reset_state(request, snapshot_id, state): snapshot_id, state) -@profiler.trace -def volume_cgroup_get(request, cgroup_id): - cgroup = cinderclient(request).consistencygroups.get(cgroup_id) - return VolumeConsistencyGroup(cgroup) - - -@profiler.trace -def volume_cgroup_get_with_vol_type_names(request, cgroup_id): - cgroup = volume_cgroup_get(request, cgroup_id) - vol_types = volume_type_list(request) - cgroup.volume_type_names = [] - for vol_type_id in cgroup.volume_types: - for vol_type in vol_types: - if vol_type.id == vol_type_id: - cgroup.volume_type_names.append(vol_type.name) - break - return cgroup - - -@profiler.trace -def volume_cgroup_list(request, search_opts=None): - c_client = cinderclient(request) - if c_client is None: - return [] - return [VolumeConsistencyGroup(s) for s in c_client.consistencygroups.list( - search_opts=search_opts)] - - -@profiler.trace -def volume_cgroup_list_with_vol_type_names(request, search_opts=None): - cgroups = volume_cgroup_list(request, search_opts) - vol_types = volume_type_list(request) - for cgroup in cgroups: - cgroup.volume_type_names = [] - for vol_type_id in cgroup.volume_types: - for vol_type in vol_types: - if vol_type.id == vol_type_id: - cgroup.volume_type_names.append(vol_type.name) - break - - return cgroups - - -@profiler.trace -def volume_cgroup_create(request, volume_types, name, - description=None, availability_zone=None): - data = {'name': name, - 'description': description, - 'availability_zone': availability_zone} - - cgroup = cinderclient(request).consistencygroups.create(volume_types, - **data) - return VolumeConsistencyGroup(cgroup) - - -@profiler.trace -def volume_cgroup_create_from_source(request, name, cg_snapshot_id=None, - source_cgroup_id=None, - description=None, - user_id=None, project_id=None): - return VolumeConsistencyGroup( - cinderclient(request).consistencygroups.create_from_src( - cg_snapshot_id, - source_cgroup_id, - name, - description, - user_id, - project_id)) - - -@profiler.trace -def volume_cgroup_delete(request, cgroup_id, force=False): - return cinderclient(request).consistencygroups.delete(cgroup_id, force) - - -@profiler.trace -def volume_cgroup_update(request, cgroup_id, name=None, description=None, - add_vols=None, remove_vols=None): - cgroup_data = {} - if name: - cgroup_data['name'] = name - if description: - cgroup_data['description'] = description - if add_vols: - cgroup_data['add_volumes'] = add_vols - if remove_vols: - cgroup_data['remove_volumes'] = remove_vols - return cinderclient(request).consistencygroups.update(cgroup_id, - **cgroup_data) - - -def volume_cg_snapshot_create(request, cgroup_id, name, - description=None): - return VolumeCGSnapshot( - cinderclient(request).cgsnapshots.create( - cgroup_id, - name, - description)) - - -def volume_cg_snapshot_get(request, cg_snapshot_id): - cgsnapshot = cinderclient(request).cgsnapshots.get(cg_snapshot_id) - return VolumeCGSnapshot(cgsnapshot) - - -def volume_cg_snapshot_list(request, search_opts=None): - c_client = cinderclient(request) - if c_client is None: - return [] - return [VolumeCGSnapshot(s) for s in c_client.cgsnapshots.list( - search_opts=search_opts)] - - -def volume_cg_snapshot_delete(request, cg_snapshot_id): - return cinderclient(request).cgsnapshots.delete(cg_snapshot_id) - - @memoized def volume_backup_supported(request): """This method will determine if cinder supports backup.""" diff --git a/openstack_dashboard/api/microversions.py b/openstack_dashboard/api/microversions.py index 8b7fa9f1a9..3d52894348 100644 --- a/openstack_dashboard/api/microversions.py +++ b/openstack_dashboard/api/microversions.py @@ -40,7 +40,6 @@ MICROVERSION_FEATURES = { }, "cinder": { "groups": ["3.27", "3.43", "3.48", "3.58"], - "consistency_groups": ["2.0", "3.10"], "message_list": ["3.5", "3.29"], "limits_project_id_query": ["3.43", "3.50", "3.55"], } diff --git a/openstack_dashboard/conf/cinder_policy.d/consistencygroup.yaml b/openstack_dashboard/conf/cinder_policy.d/consistencygroup.yaml deleted file mode 100644 index 6d907b972b..0000000000 --- a/openstack_dashboard/conf/cinder_policy.d/consistencygroup.yaml +++ /dev/null @@ -1,10 +0,0 @@ -# extra policies for consistency group -"consistencygroup:create" : "" -"consistencygroup:create_cgsnapshot" : "" -"consistencygroup:delete": "" -"consistencygroup:delete_cgsnapshot": "" -"consistencygroup:get": "" -"consistencygroup:get_all": "" -"consistencygroup:get_all_cgsnapshots": "" -"consistencygroup:get_cgsnapshot": "" -"consistencygroup:update": "" diff --git a/openstack_dashboard/dashboards/admin/volume_groups/tests.py b/openstack_dashboard/dashboards/admin/volume_groups/tests.py index b0f9ffe159..579ea9b86f 100644 --- a/openstack_dashboard/dashboards/admin/volume_groups/tests.py +++ b/openstack_dashboard/dashboards/admin/volume_groups/tests.py @@ -71,7 +71,7 @@ class AdminVolumeGroupTests(test.BaseAdminViewTests): @test.create_mocks({api.cinder: ['group_get', 'group_delete']}) def test_delete_group_delete_volumes_flag(self): - group = self.cinder_consistencygroups.first() + group = self.cinder_groups.first() formData = {'delete_volumes': True} self.mock_group_get.return_value = group diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/__init__.py b/openstack_dashboard/dashboards/project/cg_snapshots/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/forms.py b/openstack_dashboard/dashboards/project/cg_snapshots/forms.py deleted file mode 100644 index f54b779365..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/forms.py +++ /dev/null @@ -1,76 +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.urls import reverse -from django.utils.translation import ugettext_lazy as _ - -from horizon import exceptions -from horizon import forms -from horizon import messages - -from openstack_dashboard.api import cinder - - -class CreateCGroupForm(forms.SelfHandlingForm): - name = forms.CharField(max_length=255, label=_("Consistency Group Name")) - description = forms.CharField(max_length=255, - widget=forms.Textarea(attrs={'rows': 4}), - label=_("Description"), - required=False) - snapshot_source = forms.ChoiceField( - label=_("Use snapshot as a source"), - widget=forms.ThemableSelectWidget( - attrs={'class': 'snapshot-selector'}, - data_attrs=('name'), - transform=lambda x: "%s" % (x.name)), - required=False) - - def prepare_snapshot_source_field(self, request, cg_snapshot_id): - try: - cg_snapshot = cinder.volume_cg_snapshot_get(request, - cg_snapshot_id) - self.fields['snapshot_source'].choices = ((cg_snapshot_id, - cg_snapshot),) - except Exception: - exceptions.handle(request, - _('Unable to load the specified snapshot.')) - - def __init__(self, request, *args, **kwargs): - super(CreateCGroupForm, self).__init__(request, *args, **kwargs) - - # populate cgroup_id - cg_snapshot_id = kwargs.get('initial', {}).get('cg_snapshot_id', []) - self.fields['cg_snapshot_id'] = forms.CharField( - widget=forms.HiddenInput(), - initial=cg_snapshot_id) - self.prepare_snapshot_source_field(request, cg_snapshot_id) - - def handle(self, request, data): - try: - - message = _('Creating consistency group "%s".') % data['name'] - cgroup = cinder.volume_cgroup_create_from_source( - request, - data['name'], - cg_snapshot_id=data['cg_snapshot_id'], - description=data['description']) - - messages.info(request, message) - return cgroup - except Exception: - redirect = reverse("horizon:project:cg_snapshots:index") - msg = _('Unable to create consistency ' - 'group "%s" from snapshot.') % data['name'] - exceptions.handle(request, - msg, - redirect=redirect) diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/panel.py b/openstack_dashboard/dashboards/project/cg_snapshots/panel.py deleted file mode 100644 index eae401de34..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/panel.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2017 Rackspace, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging - -from django.utils.translation import ugettext_lazy as _ - -import horizon - -from openstack_dashboard import api -from openstack_dashboard import policy - -LOG = logging.getLogger(__name__) - - -class CGSnapshots(horizon.Panel): - name = _("Consistency Group Snapshots") - slug = 'cg_snapshots' - permissions = ( - ('openstack.services.volume', 'openstack.services.volumev2', - 'openstack.services.volumev3'), - ) - policy_rules = (("volume", "consistencygroup:get_all_cgsnapshots"),) - - def allowed(self, context): - request = context['request'] - try: - return ( - super(CGSnapshots, self).allowed(context) and - request.user.has_perms(self.permissions) and - policy.check(self.policy_rules, request) and - api.cinder.get_microversion(request, 'consistency_groups') and - not api.cinder.get_microversion(request, 'groups') - ) - except Exception: - LOG.error("Call to list enabled services failed. This is likely " - "due to a problem communicating with the Cinder " - "endpoint. Consistency Group Snapshot panel will not be " - "displayed.") - return False diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/tables.py b/openstack_dashboard/dashboards/project/cg_snapshots/tables.py deleted file mode 100644 index 22a71bcd8b..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/tables.py +++ /dev/null @@ -1,118 +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.utils.translation import pgettext_lazy -from django.utils.translation import ugettext_lazy as _ -from django.utils.translation import ungettext_lazy - -from horizon import tables - -from openstack_dashboard.api import cinder -from openstack_dashboard import policy - - -class CreateVolumeCGroup(policy.PolicyTargetMixin, tables.LinkAction): - name = "create_cgroup" - verbose_name = _("Create Consistency Group") - url = "horizon:project:cg_snapshots:create_cgroup" - classes = ("ajax-modal",) - policy_rules = (("volume", "consistencygroup:create"),) - - -class DeleteVolumeCGSnapshot(policy.PolicyTargetMixin, tables.DeleteAction): - name = "delete_cg_snapshot" - policy_rules = (("volume", "consistencygroup:delete_cgsnapshot"),) - - @staticmethod - def action_present(count): - return ungettext_lazy( - u"Delete Snapshot", - u"Delete Snapshots", - count - ) - - @staticmethod - def action_past(count): - return ungettext_lazy( - u"Scheduled deletion of Snapshot", - u"Scheduled deletion of Snapshots", - count - ) - - def delete(self, request, obj_id): - cinder.volume_cg_snapshot_delete(request, obj_id) - - -class UpdateRow(tables.Row): - ajax = True - - def get_data(self, request, cg_snapshot_id): - cg_snapshot = cinder.volume_cg_snapshot_get(request, cg_snapshot_id) - return cg_snapshot - - -class VolumeCGSnapshotsFilterAction(tables.FilterAction): - - def filter(self, table, cg_snapshots, filter_string): - """Naive case-insensitive search.""" - query = filter_string.lower() - return [cg_snapshot for cg_snapshot in cg_snapshots - if query in cg_snapshot.name.lower()] - - -class CGSnapshotsTable(tables.DataTable): - STATUS_CHOICES = ( - ("in-use", True), - ("available", True), - ("creating", None), - ("error", False), - ) - STATUS_DISPLAY_CHOICES = ( - ("available", - pgettext_lazy("Current status of Consistency Group Snapshot", - u"Available")), - ("in-use", - pgettext_lazy("Current status of Consistency Group Snapshot", - u"In-use")), - ("error", - pgettext_lazy("Current status of Consistency Group Snapshot", - u"Error")), - ) - - name = tables.Column("name", - verbose_name=_("Name"), - link="horizon:project:cg_snapshots:cg_snapshot_detail") - description = tables.Column("description", - verbose_name=_("Description"), - truncate=40) - status = tables.Column("status", - verbose_name=_("Status"), - status=True, - status_choices=STATUS_CHOICES, - display_choices=STATUS_DISPLAY_CHOICES) - - def get_object_id(self, cg_snapshot): - return cg_snapshot.id - - class Meta(object): - name = "volume_cg_snapshots" - verbose_name = _("Consistency Group Snapshots") - table_actions = (VolumeCGSnapshotsFilterAction, - DeleteVolumeCGSnapshot) - row_actions = (CreateVolumeCGroup, - DeleteVolumeCGSnapshot,) - row_class = UpdateRow - status_columns = ("status",) - permissions = [ - ('openstack.services.volume', 'openstack.services.volumev2', - 'openstack.services.volumev3') - ] diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/tabs.py b/openstack_dashboard/dashboards/project/cg_snapshots/tabs.py deleted file mode 100644 index 4c9cd997b4..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/tabs.py +++ /dev/null @@ -1,34 +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.urls import reverse -from django.utils.translation import ugettext_lazy as _ - -from horizon import tabs - - -class OverviewTab(tabs.Tab): - name = _("Overview") - slug = "overview" - template_name = "project/cg_snapshots/_detail_overview.html" - - def get_context_data(self, request): - cg_snapshot = self.tab_group.kwargs['cg_snapshot'] - return {"cg_snapshot": cg_snapshot} - - def get_redirect_url(self): - return reverse('horizon:project:cg_snapshots:index') - - -class CGSnapshotsDetailTabs(tabs.TabGroup): - slug = "cg_snapshots_details" - tabs = (OverviewTab,) diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_create.html b/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_create.html deleted file mode 100644 index ac3104a6d6..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_create.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} - -{% block modal-body-right %} -
-

{% blocktrans %}Create a Consistency Group that will contain newly created volumes cloned from each of the snapshots in the source Consistency Group Snapshot.{% endblocktrans %}

- {% include "project/volumes/_volume_limits.html" with usages=usages %} -
-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_detail_overview.html b/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_detail_overview.html deleted file mode 100644 index 007398eaa4..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_detail_overview.html +++ /dev/null @@ -1,46 +0,0 @@ -{% load i18n sizeformat parse_date %} - -
-
-
{% trans "Name" %}
-
{{ cg_snapshot.name }}
-
{% trans "ID" %}
-
{{ cg_snapshot.id }}
- {% if cg_snapshot.description %} -
{% trans "Description" %}
-
{{ cg_snapshot.description }}
- {% endif %} -
{% trans "Status" %}
-
{{ cg_snapshot.status|capfirst }}
-
{% trans "Consistency Group" %}
-
- - {% if cg_snapshot.cg_name %} - {{ cg_snapshot.cg_name }} - {% else %} - {{ cg_snapshot.consistencygroup_id }} - {% endif %} - -
-
- -

{% trans "Snapshot Volume Types" %}

-
-
- {% for vol_type_names in cg_snapshot.volume_type_names %} -
{{ vol_type_names }}
- {% endfor %} -
- -

{% trans "Snapshot Volumes" %}

-
-
- {% for vol_names in cg_snapshot.volume_names %} -
{{ vol_names }}
- {% empty %} -
- {% trans "No assigned volumes" %} -
- {% endfor %} -
-
diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_update.html b/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_update.html deleted file mode 100644 index 1e9415e2c0..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_update.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block modal-body-right %} -

{% trans "Modify the name and description of a volume consistency group snapshot." %}

-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/create.html b/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/create.html deleted file mode 100644 index 3666d1f506..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/create.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block main %} - {% include 'project/cg_snapshots/_create.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/update.html b/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/update.html deleted file mode 100644 index 0b4ba8ba49..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/update.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block main %} - {% include 'project/cg_snapshots/_update.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/tests.py b/openstack_dashboard/dashboards/project/cg_snapshots/tests.py deleted file mode 100644 index 601962deca..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/tests.py +++ /dev/null @@ -1,174 +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.urls import reverse -import mock - -from openstack_dashboard.api import cinder -from openstack_dashboard.test import helpers as test - - -INDEX_URL = reverse('horizon:project:cg_snapshots:index') - - -class CGroupSnapshotTests(test.TestCase): - @test.create_mocks({cinder: ('volume_cg_snapshot_get', - 'volume_cgroup_create_from_source',)}) - def test_create_cgroup_from_snapshot(self): - cgroup = self.cinder_consistencygroups.first() - cg_snapshot = self.cinder_cg_snapshots.first() - formData = {'cg_snapshot_id': cg_snapshot.id, - 'name': 'test CG SS Create', - 'description': 'test desc'} - - self.mock_volume_cg_snapshot_get.return_value = cg_snapshot - self.mock_volume_cgroup_create_from_source.return_value = cgroup - - url = reverse('horizon:project:cg_snapshots:create_cgroup', - args=[cg_snapshot.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow( - res, reverse('horizon:project:cgroups:index')) - - self.mock_volume_cg_snapshot_get.assert_called_once_with( - test.IsHttpRequest(), cg_snapshot.id) - self.mock_volume_cgroup_create_from_source.assert_called_once_with( - test.IsHttpRequest(), - formData['name'], - cg_snapshot_id=formData['cg_snapshot_id'], - description=formData['description']) - - @test.create_mocks({cinder: ('volume_cg_snapshot_get', - 'volume_cgroup_create_from_source',)}) - def test_create_cgroup_from_snapshot_exception(self): - cg_snapshot = self.cinder_cg_snapshots.first() - new_cg_name = 'test CG SS Create' - formData = {'cg_snapshot_id': cg_snapshot.id, - 'name': new_cg_name, - 'description': 'test desc'} - - self.mock_volume_cg_snapshot_get.return_value = cg_snapshot - self.mock_volume_cgroup_create_from_source.side_effect = \ - self.exceptions.cinder - - url = reverse('horizon:project:cg_snapshots:create_cgroup', - args=[cg_snapshot.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - # There are a bunch of backslashes for formatting in the message from - # the response, so remove them when validating the error message. - self.assertIn('Unable to create consistency group "%s" from snapshot.' - % new_cg_name, - res.cookies.output().replace('\\', '')) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cg_snapshot_get.assert_called_once_with( - test.IsHttpRequest(), cg_snapshot.id) - self.mock_volume_cgroup_create_from_source.assert_called_once_with( - test.IsHttpRequest(), - formData['name'], - cg_snapshot_id=formData['cg_snapshot_id'], - description=formData['description']) - - @test.create_mocks({cinder: ('volume_cg_snapshot_list', - 'volume_cg_snapshot_delete',)}) - def test_delete_cgroup_snapshot(self): - cg_snapshots = self.cinder_cg_snapshots.list() - cg_snapshot = self.cinder_cg_snapshots.first() - - self.mock_volume_cg_snapshot_list.return_value = cg_snapshots - self.mock_volume_cg_snapshot_delete.return_value = None - - form_data = {'action': 'volume_cg_snapshots__delete_cg_snapshot__%s' - % cg_snapshot.id} - res = self.client.post(INDEX_URL, form_data, follow=True) - self.assertEqual(res.status_code, 200) - self.assertIn("Scheduled deletion of Snapshot: %s" % cg_snapshot.name, - [m.message for m in res.context['messages']]) - - self.assert_mock_multiple_calls_with_same_arguments( - self.mock_volume_cg_snapshot_list, 2, - mock.call(test.IsHttpRequest())) - self.mock_volume_cg_snapshot_delete.assert_called_once_with( - test.IsHttpRequest(), cg_snapshot.id) - - @test.create_mocks({cinder: ('volume_cg_snapshot_list', - 'volume_cg_snapshot_delete',)}) - def test_delete_cgroup_snapshot_exception(self): - cg_snapshots = self.cinder_cg_snapshots.list() - cg_snapshot = self.cinder_cg_snapshots.first() - - self.mock_volume_cg_snapshot_list.return_value = cg_snapshots - self.mock_volume_cg_snapshot_delete.side_effect = \ - self.exceptions.cinder - - form_data = {'action': 'volume_cg_snapshots__delete_cg_snapshot__%s' - % cg_snapshot.id} - res = self.client.post(INDEX_URL, form_data, follow=True) - self.assertEqual(res.status_code, 200) - self.assertIn("Unable to delete snapshot: %s" % cg_snapshot.name, - [m.message for m in res.context['messages']]) - - self.assert_mock_multiple_calls_with_same_arguments( - self.mock_volume_cg_snapshot_list, 2, - mock.call(test.IsHttpRequest())) - self.mock_volume_cg_snapshot_delete.assert_called_once_with( - test.IsHttpRequest(), cg_snapshot.id) - - @test.create_mocks({cinder: ('volume_cg_snapshot_get', - 'volume_cgroup_get', - 'volume_type_get', - 'volume_list',)}) - def test_detail_view(self): - cg_snapshot = self.cinder_cg_snapshots.first() - cgroup = self.cinder_consistencygroups.first() - volume_type = self.cinder_volume_types.first() - volumes = self.cinder_volumes.list() - - self.mock_volume_cg_snapshot_get.return_value = cg_snapshot - self.mock_volume_cgroup_get.return_value = cgroup - self.mock_volume_type_get.return_value = volume_type - self.mock_volume_list.return_value = volumes - - url = reverse( - 'horizon:project:cg_snapshots:cg_snapshot_detail', - args=[cg_snapshot.id]) - res = self.client.get(url) - self.assertNoFormErrors(res) - self.assertEqual(res.status_code, 200) - - self.mock_volume_cg_snapshot_get.assert_called_once_with( - test.IsHttpRequest(), cg_snapshot.id) - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - self.mock_volume_type_get.assert_called_once_with( - test.IsHttpRequest(), volume_type.id) - search_opts = {'consistencygroup_id': cgroup.id} - self.mock_volume_list.assert_called_once_with( - test.IsHttpRequest(), search_opts=search_opts) - - @test.create_mocks({cinder: ('volume_cg_snapshot_get',)}) - def test_detail_view_with_exception(self): - cg_snapshot = self.cinder_cg_snapshots.first() - - self.mock_volume_cg_snapshot_get.side_effect = self.exceptions.cinder - - url = reverse( - 'horizon:project:cg_snapshots:cg_snapshot_detail', - args=[cg_snapshot.id]) - res = self.client.get(url) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cg_snapshot_get.assert_called_once_with( - test.IsHttpRequest(), cg_snapshot.id) diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/urls.py b/openstack_dashboard/dashboards/project/cg_snapshots/urls.py deleted file mode 100644 index 2e517a575e..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/urls.py +++ /dev/null @@ -1,25 +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.conf.urls import url - -from openstack_dashboard.dashboards.project.cg_snapshots import views - -urlpatterns = [ - url(r'^$', views.CGSnapshotsView.as_view(), name='index'), - url(r'^(?P[^/]+)/cg_snapshot_detail/$', - views.DetailView.as_view(), - name='cg_snapshot_detail'), - url(r'^(?P[^/]+)/create_cgroup/$', - views.CreateCGroupView.as_view(), - name='create_cgroup'), -] diff --git a/openstack_dashboard/dashboards/project/cg_snapshots/views.py b/openstack_dashboard/dashboards/project/cg_snapshots/views.py deleted file mode 100644 index 15f799f1b1..0000000000 --- a/openstack_dashboard/dashboards/project/cg_snapshots/views.py +++ /dev/null @@ -1,153 +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.urls import reverse -from django.urls 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 import tabs -from horizon.utils import memoized - -from openstack_dashboard import api -from openstack_dashboard.api import cinder -from openstack_dashboard.usage import quotas - -from openstack_dashboard.dashboards.project.cg_snapshots \ - import forms as cg_snapshot_forms -from openstack_dashboard.dashboards.project.cg_snapshots \ - import tables as cg_snapshot_tables -from openstack_dashboard.dashboards.project.cg_snapshots \ - import tabs as cg_snapshot_tabs - -CGROUP_INFO_FIELDS = ("name", - "description") - -INDEX_URL = "horizon:project:cg_snapshots:index" - - -class CGSnapshotsView(tables.DataTableView): - table_class = cg_snapshot_tables.CGSnapshotsTable - page_title = _("Consistency Group Snapshots") - - def get_data(self): - try: - cg_snapshots = api.cinder.volume_cg_snapshot_list(self.request) - except Exception: - cg_snapshots = [] - exceptions.handle(self.request, _("Unable to retrieve " - "volume consistency group " - "snapshots.")) - return cg_snapshots - - -class DetailView(tabs.TabView): - tab_group_class = cg_snapshot_tabs.CGSnapshotsDetailTabs - template_name = 'horizon/common/_detail.html' - page_title = "{{ cg_snapshot.name|default:cg_snapshot.id }}" - - def get_context_data(self, **kwargs): - context = super(DetailView, self).get_context_data(**kwargs) - cg_snapshot = self.get_data() - table = cg_snapshot_tables.CGSnapshotsTable(self.request) - context["cg_snapshot"] = cg_snapshot - context["url"] = self.get_redirect_url() - context["actions"] = table.render_row_actions(cg_snapshot) - return context - - @memoized.memoized_method - def get_data(self): - try: - cg_snapshot_id = self.kwargs['cg_snapshot_id'] - cg_snapshot = api.cinder.volume_cg_snapshot_get(self.request, - cg_snapshot_id) - - cgroup_id = cg_snapshot.consistencygroup_id - cgroup = api.cinder.volume_cgroup_get(self.request, - cgroup_id) - cg_snapshot.cg_name = cgroup.name - cg_snapshot.volume_type_names = [] - for vol_type_id in cgroup.volume_types: - vol_type = api.cinder.volume_type_get(self.request, - vol_type_id) - cg_snapshot.volume_type_names.append(vol_type.name) - - cg_snapshot.volume_names = [] - search_opts = {'consistencygroup_id': cgroup_id} - volumes = api.cinder.volume_list(self.request, - search_opts=search_opts) - for volume in volumes: - cg_snapshot.volume_names.append(volume.name) - - except Exception: - redirect = self.get_redirect_url() - exceptions.handle(self.request, - _('Unable to retrieve consistency group ' - 'snapshot details.'), - redirect=redirect) - return cg_snapshot - - @staticmethod - def get_redirect_url(): - return reverse(INDEX_URL) - - def get_tabs(self, request, *args, **kwargs): - cg_snapshot = self.get_data() - return self.tab_group_class(request, cg_snapshot=cg_snapshot, **kwargs) - - -class CreateCGroupView(forms.ModalFormView): - form_class = cg_snapshot_forms.CreateCGroupForm - template_name = 'project/cg_snapshots/create.html' - submit_url = "horizon:project:cg_snapshots:create_cgroup" - success_url = reverse_lazy('horizon:project:cgroups:index') - page_title = _("Create Volume Consistency Group") - - def get_context_data(self, **kwargs): - context = super(CreateCGroupView, self).get_context_data(**kwargs) - context['cg_snapshot_id'] = self.kwargs['cg_snapshot_id'] - args = (self.kwargs['cg_snapshot_id'],) - context['submit_url'] = reverse(self.submit_url, args=args) - try: - # get number of volumes we will be creating - cg_snapshot = cinder.volume_cg_snapshot_get( - self.request, context['cg_snapshot_id']) - - cgroup_id = cg_snapshot.consistencygroup_id - - search_opts = {'consistencygroup_id': cgroup_id} - volumes = api.cinder.volume_list(self.request, - search_opts=search_opts) - num_volumes = len(volumes) - usages = quotas.tenant_quota_usages( - self.request, targets=('volumes', 'gigabytes')) - if (usages['volumes']['used'] + num_volumes > - usages['volumes']['quota']): - raise ValueError(_('Unable to create consistency group due to ' - 'exceeding volume quota limit.')) - else: - context['numRequestedItems'] = num_volumes - context['usages'] = usages - - except ValueError as e: - exceptions.handle(self.request, e.message) - return None - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve consistency ' - 'group information.')) - return context - - def get_initial(self): - return {'cg_snapshot_id': self.kwargs["cg_snapshot_id"]} diff --git a/openstack_dashboard/dashboards/project/cgroups/__init__.py b/openstack_dashboard/dashboards/project/cgroups/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openstack_dashboard/dashboards/project/cgroups/forms.py b/openstack_dashboard/dashboards/project/cgroups/forms.py deleted file mode 100644 index 6712da5172..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/forms.py +++ /dev/null @@ -1,216 +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.urls import reverse -from django.utils.translation import ugettext_lazy as _ - -from horizon import exceptions -from horizon import forms -from horizon import messages - -from openstack_dashboard.api import cinder - - -class UpdateForm(forms.SelfHandlingForm): - name = forms.CharField(max_length=255, label=_("Name")) - description = forms.CharField(max_length=255, - widget=forms.Textarea(attrs={'rows': 4}), - label=_("Description"), - required=False) - - def clean(self): - cleaned_data = super(UpdateForm, self).clean() - new_desc = cleaned_data.get('description') - old_desc = self.initial['description'] - if old_desc and not new_desc: - error_msg = _("Description is required.") - self._errors['description'] = self.error_class([error_msg]) - return cleaned_data - - return cleaned_data - - def handle(self, request, data): - cgroup_id = self.initial['cgroup_id'] - - try: - cinder.volume_cgroup_update(request, - cgroup_id, - data['name'], - data['description']) - - message = _('Updating volume consistency ' - 'group "%s"') % data['name'] - messages.info(request, message) - return True - except Exception: - redirect = reverse("horizon:project:cgroups:index") - exceptions.handle(request, - _('Unable to update volume consistency group.'), - redirect=redirect) - - -class RemoveVolsForm(forms.SelfHandlingForm): - def handle(self, request, data): - cgroup_id = self.initial['cgroup_id'] - name = self.initial['name'] - search_opts = {'consistencygroup_id': cgroup_id} - - try: - # get list of assigned volumes - assigned_vols = [] - volumes = cinder.volume_list(request, - search_opts=search_opts) - for volume in volumes: - assigned_vols.append(volume.id) - - assigned_vols_str = ",".join(assigned_vols) - cinder.volume_cgroup_update(request, - cgroup_id, - remove_vols=assigned_vols_str) - - message = _('Removing volumes from volume consistency ' - 'group "%s"') % name - messages.info(request, message) - return True - - except Exception: - redirect = reverse("horizon:project:cgroups:index") - exceptions.handle(request, _('Errors occurred in removing volumes ' - 'from consistency group.'), - redirect=redirect) - - -class DeleteForm(forms.SelfHandlingForm): - delete_volumes = forms.BooleanField(label=_("Delete Volumes"), - required=False) - - def handle(self, request, data): - cgroup_id = self.initial['cgroup_id'] - name = self.initial['name'] - delete_volumes = data['delete_volumes'] - - try: - cinder.volume_cgroup_delete(request, - cgroup_id, - force=delete_volumes) - message = _('Deleting volume consistency ' - 'group "%s"') % name - messages.success(request, message) - return True - - except Exception: - redirect = reverse("horizon:project:cgroups:index") - exceptions.handle(request, _('Errors occurred in deleting ' - 'consistency group.'), - redirect=redirect) - - -class CreateSnapshotForm(forms.SelfHandlingForm): - name = forms.CharField(max_length=255, label=_("Snapshot Name")) - description = forms.CharField(max_length=255, - widget=forms.Textarea(attrs={'rows': 4}), - label=_("Description"), - required=False) - - def __init__(self, request, *args, **kwargs): - super(CreateSnapshotForm, self).__init__(request, *args, **kwargs) - - # populate cgroup_id - cgroup_id = kwargs.get('initial', {}).get('cgroup_id', []) - self.fields['cgroup_id'] = forms.CharField(widget=forms.HiddenInput(), - initial=cgroup_id) - - def handle(self, request, data): - try: - message = _('Creating consistency group snapshot "%s".') \ - % data['name'] - snapshot = cinder.volume_cg_snapshot_create(request, - data['cgroup_id'], - data['name'], - data['description']) - - messages.info(request, message) - return snapshot - except Exception as e: - redirect = reverse("horizon:project:cgroups:index") - msg = _('Unable to create consistency group snapshot.') - if e.code == 413: - msg = _('Requested snapshot would exceed the allowed quota.') - else: - search_opts = {'consistentcygroup_id': data['cgroup_id']} - volumes = cinder.volume_list(request, search_opts=search_opts) - if not volumes: - msg = _('Unable to create snapshot. Consistency group ' - 'must contain volumes.') - - exceptions.handle(request, - msg, - redirect=redirect) - - -class CloneCGroupForm(forms.SelfHandlingForm): - name = forms.CharField(max_length=255, label=_("Consistency Group Name")) - description = forms.CharField(max_length=255, - widget=forms.Textarea(attrs={'rows': 4}), - label=_("Description"), - required=False) - cgroup_source = forms.ChoiceField( - label=_("Use a consistency group as source"), - widget=forms.ThemableSelectWidget( - attrs={'class': 'image-selector'}, - data_attrs=('name'), - transform=lambda x: "%s" % (x.name)), - required=False) - - def prepare_cgroup_source_field(self, request, cgroup_id): - try: - cgroup = cinder.volume_cgroup_get(request, - cgroup_id) - self.fields['cgroup_source'].choices = ((cgroup_id, - cgroup),) - except Exception: - exceptions.handle(request, _('Unable to load the specified ' - 'consistency group.')) - - def __init__(self, request, *args, **kwargs): - super(CloneCGroupForm, self).__init__(request, *args, **kwargs) - - # populate cgroup_id - cgroup_id = kwargs.get('initial', {}).get('cgroup_id', []) - self.fields['cgroup_id'] = forms.CharField(widget=forms.HiddenInput(), - initial=cgroup_id) - self.prepare_cgroup_source_field(request, cgroup_id) - - def handle(self, request, data): - try: - message = _('Creating consistency group "%s".') % data['name'] - cgroup = cinder.volume_cgroup_create_from_source( - request, - data['name'], - source_cgroup_id=data['cgroup_id'], - description=data['description']) - - messages.info(request, message) - return cgroup - except Exception: - redirect = reverse("horizon:project:cgroups:index") - msg = _('Unable to clone consistency group.') - - search_opts = {'consistentcygroup_id': data['cgroup_id']} - volumes = cinder.volume_list(request, search_opts=search_opts) - if not volumes: - msg = _('Unable to clone empty consistency group.') - - exceptions.handle(request, - msg, - redirect=redirect) diff --git a/openstack_dashboard/dashboards/project/cgroups/panel.py b/openstack_dashboard/dashboards/project/cgroups/panel.py deleted file mode 100644 index bda7986fc0..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/panel.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright 2017 Rackspace, Inc. -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -import logging - -from django.utils.translation import ugettext_lazy as _ - -import horizon - -from openstack_dashboard import api -from openstack_dashboard import policy - -LOG = logging.getLogger(__name__) - - -class CGroups(horizon.Panel): - name = _("Consistency Groups") - slug = 'cgroups' - permissions = ( - ('openstack.services.volume', 'openstack.services.volumev2', - 'openstack.services.volumev3'), - ) - policy_rules = (("volume", "consistencygroup:get_all"),) - - def allowed(self, context): - request = context['request'] - try: - return ( - super(CGroups, self).allowed(context) and - request.user.has_perms(self.permissions) and - policy.check(self.policy_rules, request) and - api.cinder.get_microversion(request, 'consistency_groups') and - not api.cinder.get_microversion(request, 'groups') - ) - except Exception: - LOG.error("Call to list enabled services failed. This is likely " - "due to a problem communicating with the Cinder " - "endpoint. Consistency Group panel will not be " - "displayed.") - return False diff --git a/openstack_dashboard/dashboards/project/cgroups/tables.py b/openstack_dashboard/dashboards/project/cgroups/tables.py deleted file mode 100644 index fa3e08aca8..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/tables.py +++ /dev/null @@ -1,175 +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.utils.translation import pgettext_lazy -from django.utils.translation import ugettext_lazy as _ - -from horizon import exceptions -from horizon import tables - -from openstack_dashboard.api import cinder -from openstack_dashboard import policy - - -class CreateVolumeCGroup(policy.PolicyTargetMixin, tables.LinkAction): - name = "create" - verbose_name = _("Create Consistency Group") - url = "horizon:project:cgroups:create" - classes = ("ajax-modal",) - icon = "plus" - policy_rules = (("volume", "consistencygroup:create"),) - - -class DeleteVolumeCGroup(policy.PolicyTargetMixin, tables.LinkAction): - name = "deletecg" - verbose_name = _("Delete Consistency Group") - url = "horizon:project:cgroups:delete" - classes = ("ajax-modal", "btn-danger") - policy_rules = (("volume", "consistencygroup:delete"), ) - - -class RemoveAllVolumes(policy.PolicyTargetMixin, tables.LinkAction): - name = "remove_vols" - verbose_name = _("Remove Volumes from Consistency Group") - url = "horizon:project:cgroups:remove_volumes" - classes = ("ajax-modal",) - policy_rules = (("volume", "consistencygroup:update"), ) - - -class EditVolumeCGroup(policy.PolicyTargetMixin, tables.LinkAction): - name = "edit" - verbose_name = _("Edit Consistency Group") - url = "horizon:project:cgroups:update" - classes = ("ajax-modal",) - policy_rules = (("volume", "consistencygroup:update"),) - - -class ManageVolumes(policy.PolicyTargetMixin, tables.LinkAction): - name = "manage" - verbose_name = _("Manage Volumes") - url = "horizon:project:cgroups:manage" - classes = ("ajax-modal",) - policy_rules = (("volume", "consistencygroup:update"),) - - def allowed(self, request, cgroup=None): - if hasattr(cgroup, 'status'): - return cgroup.status != 'error' - else: - return False - - -class CreateSnapshot(policy.PolicyTargetMixin, tables.LinkAction): - name = "create_snapshot" - verbose_name = _("Create Snapshot") - url = "horizon:project:cgroups:create_snapshot" - classes = ("ajax-modal",) - policy_rules = (("volume", "consistencygroup:create_cgsnapshot"),) - - def allowed(self, request, cgroup=None): - if hasattr(cgroup, 'status'): - return cgroup.status != 'error' - else: - return False - - -class CloneCGroup(policy.PolicyTargetMixin, tables.LinkAction): - name = "clone_cgroup" - verbose_name = _("Clone Consistency Group") - url = "horizon:project:cgroups:clone_cgroup" - classes = ("ajax-modal",) - policy_rules = (("volume", "consistencygroup:create"),) - - def allowed(self, request, cgroup=None): - if hasattr(cgroup, 'status'): - return cgroup.status != 'error' - else: - return False - - -class UpdateRow(tables.Row): - ajax = True - - def get_data(self, request, cgroup_id): - try: - cgroup = cinder.volume_cgroup_get_with_vol_type_names(request, - cgroup_id) - except Exception: - exceptions.handle(request, _('Unable to display ' - 'consistency group.')) - return cgroup - - -class VolumeCGroupsFilterAction(tables.FilterAction): - - def filter(self, table, cgroups, filter_string): - """Naive case-insensitive search.""" - query = filter_string.lower() - return [cgroup for cgroup in cgroups - if query in cgroup.name.lower()] - - -def get_volume_types(cgroup): - vtypes_str = '' - if hasattr(cgroup, 'volume_type_names'): - vtypes_str = ",".join(cgroup.volume_type_names) - return vtypes_str - - -class VolumeCGroupsTable(tables.DataTable): - STATUS_CHOICES = ( - ("in-use", True), - ("available", True), - ("creating", None), - ("error", False), - ) - STATUS_DISPLAY_CHOICES = ( - ("available", - pgettext_lazy("Current status of Consistency Group", u"Available")), - ("in-use", - pgettext_lazy("Current status of Consistency Group", u"In-use")), - ("error", - pgettext_lazy("Current status of Consistency Group", u"Error")), - ) - - name = tables.WrappingColumn("name", - verbose_name=_("Name"), - link="horizon:project:cgroups:detail") - description = tables.Column("description", - verbose_name=_("Description"), - truncate=40) - status = tables.Column("status", - verbose_name=_("Status"), - status=True, - status_choices=STATUS_CHOICES, - display_choices=STATUS_DISPLAY_CHOICES) - availability_zone = tables.Column("availability_zone", - verbose_name=_("Availability Zone")) - volume_type = tables.Column(get_volume_types, - verbose_name=_("Volume Type(s)")) - - def get_object_id(self, cgroup): - return cgroup.id - - class Meta(object): - name = "volume_cgroups" - verbose_name = _("Volume Consistency Groups") - table_actions = (CreateVolumeCGroup, - VolumeCGroupsFilterAction) - row_actions = (ManageVolumes, - EditVolumeCGroup, - CreateSnapshot, - CloneCGroup, - RemoveAllVolumes, - DeleteVolumeCGroup) - row_class = UpdateRow - status_columns = ("status",) - permissions = ['openstack.services.volume'] diff --git a/openstack_dashboard/dashboards/project/cgroups/tabs.py b/openstack_dashboard/dashboards/project/cgroups/tabs.py deleted file mode 100644 index 83b4073ccd..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/tabs.py +++ /dev/null @@ -1,34 +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.urls import reverse -from django.utils.translation import ugettext_lazy as _ - -from horizon import tabs - - -class OverviewTab(tabs.Tab): - name = _("Overview") - slug = "overview" - template_name = ("project/cgroups/_detail_overview.html") - - def get_context_data(self, request): - cgroup = self.tab_group.kwargs['cgroup'] - return {"cgroup": cgroup} - - def get_redirect_url(self): - return reverse('horizon:project:cgroups:index') - - -class CGroupsDetailTabs(tabs.TabGroup): - slug = "cgroup_details" - tabs = (OverviewTab,) diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_clone_cgroup.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_clone_cgroup.html deleted file mode 100644 index 8288034169..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_clone_cgroup.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} - -{% block modal-body-right %} -
-

{% blocktrans %}Clone each of the volumes in the source Consistency Group, and then add them to a newly created Consistency Group.{% endblocktrans %}

- {% include "project/volumes/_volume_limits.html" with usages=usages snapshot_quota=False %} -
-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_create_snapshot.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_create_snapshot.html deleted file mode 100644 index e98e55a7fd..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_create_snapshot.html +++ /dev/null @@ -1,10 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} - -{% block modal-body-right %} -
-

{% blocktrans %}Create a snapshot for each volume contained in the Consistency Group.{% endblocktrans %}

-

{% blocktrans %}Snapshots can only be created for Consistency Groups that contain volumes.{% endblocktrans %}

- {% include "project/cgroups/_snapshot_limits.html" with usages=usages snapshot_quota=True %} -
-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_delete.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_delete.html deleted file mode 100644 index 7443c26ec2..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_delete.html +++ /dev/null @@ -1,9 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block modal-body-right %} -

{% trans "Volume consistency groups can not be deleted if they contain volumes." %}

-

{% trans "Check the "Delete Volumes" box to also delete any volumes associated with this consistency group." %}

-

{% trans "Note that a volume can not be deleted if it is "attached" or has any dependent snapshots." %}

-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_detail_overview.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_detail_overview.html deleted file mode 100644 index 1ea4de2559..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_detail_overview.html +++ /dev/null @@ -1,34 +0,0 @@ -{% load i18n sizeformat parse_date %} - -
-
-
{% trans "Name" %}
-
{{ cgroup.name }}
-
{% trans "ID" %}
-
{{ cgroup.id }}
-
{% trans "Description" %}
-
{{ cgroup.description }}
-
{% trans "Status" %}
-
{{ cgroup.status|capfirst }}
-
- -

{% trans "Volume Types" %}

-
-
- {% for vol_type_names in cgroup.volume_type_names %} -
{{ vol_type_names }}
- {% endfor %} -
- -

{% trans "Volumes" %}

-
-
- {% for vol_names in cgroup.volume_names %} -
{{ vol_names }}
- {% empty %} -
- {% trans "No assigned volumes" %} -
- {% endfor %} -
-
diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_remove_vols.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_remove_vols.html deleted file mode 100644 index 2ea4749ffa..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_remove_vols.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block modal-body %} -

{% trans "This action will unassign all volumes that are currently contained in this consistency group." %}

-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_snapshot_limits.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_snapshot_limits.html deleted file mode 100644 index 7aa6b09b25..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_snapshot_limits.html +++ /dev/null @@ -1,42 +0,0 @@ -{% extends "project/volumes/_volume_limits.html" %} -{% load i18n horizon humanize %} - -{% block title %} - {% trans "From here you can create a snapshot of a volume." %} -{% endblock %} - -{% block head %} - {% trans "Snapshot Limits" %} -{% endblock %} - -{% block gigabytes_used %} - {{ usages.gigabytes.used|intcomma }} -{% endblock %} - -{% block gigabytes_used_progress %} - "{{ usages.gigabytes.used }}" -{% endblock %} - -{% block type_title %} - {% trans "Number of Snapshots" %} -{% endblock %} - -{% block used %} - {{ usages.snapshots.used|intcomma }} -{% endblock %} - -{% block total %} - {{ usages.snapshots.quota|intcomma|quota }} -{% endblock %} - -{% block type_id %} - "quota_snapshots" -{% endblock %} - -{% block total_progress %} - "{{ usages.snapshots.quota }}" -{% endblock %} - -{% block used_progress %} - "{{ usages.snapshots.used }}" -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_update.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_update.html deleted file mode 100644 index 82d3e5b3b6..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_update.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends "horizon/common/_modal_form.html" %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block modal-body-right %} -

{% trans "Modify the name and description of a volume consistency group." %}

-{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/clone_cgroup.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/clone_cgroup.html deleted file mode 100644 index 8592b14bc8..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/clone_cgroup.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block main %} - {% include 'project/cgroups/_clone_cgroup.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create.html deleted file mode 100644 index ec93bb561f..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block main %} - {% include 'horizon/common/_workflow.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create_snapshot.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create_snapshot.html deleted file mode 100644 index 987db068b3..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create_snapshot.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block main %} - {% include 'project/cgroups/_create_snapshot.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/delete.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/delete.html deleted file mode 100644 index 1932fdc0c2..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/delete.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block main %} - {% include 'project/cgroups/_delete.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/remove_vols.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/remove_vols.html deleted file mode 100644 index e1e7cbd82f..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/remove_vols.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block main %} - {% include 'project/cgroups/_remove_vols.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/update.html b/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/update.html deleted file mode 100644 index ea8a2addb8..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/templates/cgroups/update.html +++ /dev/null @@ -1,7 +0,0 @@ -{% extends 'base.html' %} -{% load i18n %} -{% block title %}{{ page_title }}{% endblock %} - -{% block main %} - {% include 'project/cgroups/_update.html' %} -{% endblock %} diff --git a/openstack_dashboard/dashboards/project/cgroups/tests.py b/openstack_dashboard/dashboards/project/cgroups/tests.py deleted file mode 100644 index 5e76b5bfef..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/tests.py +++ /dev/null @@ -1,355 +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.urls import reverse -from django.utils.http import urlunquote -import mock - -from openstack_dashboard.api import cinder -from openstack_dashboard.test import helpers as test - - -INDEX_URL = reverse('horizon:project:cgroups:index') -VOLUME_CGROUPS_SNAP_INDEX_URL = urlunquote(reverse( - 'horizon:project:cg_snapshots:index')) - - -class ConsistencyGroupTests(test.TestCase): - @test.create_mocks({cinder: ('extension_supported', - 'availability_zone_list', - 'volume_type_list', - 'volume_type_list_with_qos_associations', - 'volume_cgroup_list', - 'volume_cgroup_create')}) - def test_create_cgroup(self): - cgroup = self.cinder_consistencygroups.first() - volume_types = self.cinder_volume_types.list() - volume_type_id = self.cinder_volume_types.first().id - az = self.cinder_availability_zones.first().zoneName - formData = {'volume_types': '1', - 'name': 'test CG', - 'description': 'test desc', - 'availability_zone': az, - 'add_vtypes_to_cgroup_role_member': [volume_type_id]} - - self.mock_extension_supported.return_value = True - self.mock_availability_zone_list.return_value = \ - self.cinder_availability_zones.list() - self.mock_volume_type_list.return_value = volume_types - self.mock_volume_type_list_with_qos_associations.return_value = \ - volume_types - self.mock_volume_cgroup_list.return_value = \ - self.cinder_consistencygroups.list() - self.mock_volume_cgroup_create.return_value = cgroup - - url = reverse('horizon:project:cgroups:create') - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_extension_supported.assert_called_once_with( - test.IsHttpRequest(), 'AvailabilityZones') - self.mock_availability_zone_list.assert_called_once_with( - test.IsHttpRequest()) - self.mock_volume_type_list.assert_called_once_with( - test.IsHttpRequest()) - self.mock_volume_type_list_with_qos_associations \ - .assert_called_once_with(test.IsHttpRequest()) - self.mock_volume_cgroup_list.assert_called_once_with( - test.IsHttpRequest()) - self.mock_volume_cgroup_create.assert_called_once_with( - test.IsHttpRequest(), - formData['volume_types'], - formData['name'], - description=formData['description'], - availability_zone=formData['availability_zone']) - - @test.create_mocks({cinder: ('extension_supported', - 'availability_zone_list', - 'volume_type_list', - 'volume_type_list_with_qos_associations', - 'volume_cgroup_list', - 'volume_cgroup_create')}) - def test_create_cgroup_exception(self): - volume_types = self.cinder_volume_types.list() - volume_type_id = self.cinder_volume_types.first().id - az = self.cinder_availability_zones.first().zoneName - formData = {'volume_types': '1', - 'name': 'test CG', - 'description': 'test desc', - 'availability_zone': az, - 'add_vtypes_to_cgroup_role_member': [volume_type_id]} - - self.mock_extension_supported.return_value = True - self.mock_availability_zone_list.return_value = \ - self.cinder_availability_zones.list() - self.mock_volume_type_list.return_value = volume_types - self.mock_volume_type_list_with_qos_associations.return_value = \ - volume_types - self.mock_volume_cgroup_list.return_value = \ - self.cinder_consistencygroups.list() - self.mock_volume_cgroup_create.side_effect = self.exceptions.cinder - - url = reverse('horizon:project:cgroups:create') - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - self.assertIn("Unable to create consistency group.", - res.cookies.output()) - - self.mock_extension_supported.assert_called_once_with( - test.IsHttpRequest(), 'AvailabilityZones') - self.mock_availability_zone_list.assert_called_once_with( - test.IsHttpRequest()) - self.mock_volume_type_list.assert_called_once_with( - test.IsHttpRequest()) - self.mock_volume_type_list_with_qos_associations \ - .assert_called_once_with(test.IsHttpRequest()) - self.mock_volume_cgroup_list.assert_called_once_with( - test.IsHttpRequest()) - self.mock_volume_cgroup_create.assert_called_once_with( - test.IsHttpRequest(), - formData['volume_types'], - formData['name'], - description=formData['description'], - availability_zone=formData['availability_zone']) - - @test.create_mocks({cinder: ('volume_cgroup_get', - 'volume_cgroup_delete')}) - def test_delete_cgroup(self): - cgroup = self.cinder_consistencygroups.first() - - self.mock_volume_cgroup_get.return_value = cgroup - self.mock_volume_cgroup_delete.return_value = None - - url = reverse('horizon:project:cgroups:delete', - args=[cgroup.id]) - res = self.client.post(url) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - self.mock_volume_cgroup_delete.assert_called_once_with( - test.IsHttpRequest(), cgroup.id, force=False) - - @test.create_mocks({cinder: ('volume_cgroup_get', - 'volume_cgroup_delete')}) - def test_delete_cgroup_force_flag(self): - cgroup = self.cinder_consistencygroups.first() - formData = {'delete_volumes': True} - - self.mock_volume_cgroup_get.return_value = cgroup - self.mock_volume_cgroup_delete.return_value = None - - url = reverse('horizon:project:cgroups:delete', - args=[cgroup.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - self.mock_volume_cgroup_delete.assert_called_once_with( - test.IsHttpRequest(), cgroup.id, force=True) - - @test.create_mocks({cinder: ('volume_cgroup_get', - 'volume_cgroup_delete')}) - def test_delete_cgroup_exception(self): - cgroup = self.cinder_consistencygroups.first() - formData = {'delete_volumes': False} - - self.mock_volume_cgroup_get.return_value = cgroup - self.mock_volume_cgroup_delete.side_effect = self.exceptions.cinder - - url = reverse('horizon:project:cgroups:delete', - args=[cgroup.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - self.mock_volume_cgroup_delete.assert_called_once_with( - test.IsHttpRequest(), cgroup.id, force=False) - - def test_update_cgroup_add_vol(self): - self._test_update_cgroup_add_remove_vol(add=True) - - def test_update_cgroup_remove_vol(self): - self._test_update_cgroup_add_remove_vol(add=False) - - @test.create_mocks({cinder: ('volume_list', - 'volume_type_list', - 'volume_cgroup_get', - 'volume_cgroup_update')}) - def _test_update_cgroup_add_remove_vol(self, add=True): - cgroup = self.cinder_consistencygroups.first() - volume_types = self.cinder_volume_types.list() - volumes = (self.cinder_volumes.list() + - self.cinder_cgroup_volumes.list()) - - cgroup_voltype_names = [t.name for t in volume_types - if t.id in cgroup.volume_types] - compat_volumes = [v for v in volumes - if v.volume_type in cgroup_voltype_names] - compat_volume_ids = [v.id for v in compat_volumes] - assigned_volume_ids = [v.id for v in compat_volumes - if getattr(v, 'consistencygroup_id', None)] - add_volume_ids = [v.id for v in compat_volumes - if v.id not in assigned_volume_ids] - - new_volums = compat_volume_ids if add else [] - formData = { - 'default_add_volumes_to_cgroup_role': 'member', - 'add_volumes_to_cgroup_role_member': new_volums, - } - - self.mock_volume_list.return_value = volumes - self.mock_volume_type_list.return_value = volume_types - self.mock_volume_cgroup_get.return_value = cgroup - self.mock_volume_cgroup_update.return_value = cgroup - - url = reverse('horizon:project:cgroups:manage', - args=[cgroup.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.assert_mock_multiple_calls_with_same_arguments( - self.mock_volume_list, 2, - mock.call(test.IsHttpRequest())) - self.mock_volume_type_list.assert_called_once_with( - test.IsHttpRequest()) - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - if add: - self.mock_volume_cgroup_update.assert_called_once_with( - test.IsHttpRequest(), cgroup.id, - name=cgroup.name, - add_vols=','.join(add_volume_ids), - remove_vols='') - else: - self.mock_volume_cgroup_update.assert_called_once_with( - test.IsHttpRequest(), cgroup.id, - name=cgroup.name, - add_vols='', - remove_vols=','.join(assigned_volume_ids)) - - @test.create_mocks({cinder: ('volume_cgroup_get', - 'volume_cgroup_update')}) - def test_update_cgroup_name_and_description(self): - cgroup = self.cinder_consistencygroups.first() - formData = {'volume_types': '1', - 'name': 'test CG-new', - 'description': 'test desc-new'} - - self.mock_volume_cgroup_get.return_value = cgroup - self.mock_volume_cgroup_update.return_value = cgroup - - url = reverse('horizon:project:cgroups:update', - args=[cgroup.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - self.mock_volume_cgroup_update.assert_called_once_with( - test.IsHttpRequest(), cgroup.id, - formData['name'], - formData['description']) - - @test.create_mocks({cinder: ('volume_cgroup_get', - 'volume_cgroup_update')}) - def test_update_cgroup_with_exception(self): - cgroup = self.cinder_consistencygroups.first() - formData = {'volume_types': '1', - 'name': 'test CG-new', - 'description': 'test desc-new'} - - self.mock_volume_cgroup_get.return_value = cgroup - self.mock_volume_cgroup_update.side_effect = self.exceptions.cinder - - url = reverse('horizon:project:cgroups:update', - args=[cgroup.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - self.mock_volume_cgroup_update.assert_called_once_with( - test.IsHttpRequest(), cgroup.id, - formData['name'], - formData['description']) - - @test.create_mocks({cinder: ('volume_cgroup_get',)}) - def test_detail_view_with_exception(self): - cgroup = self.cinder_consistencygroups.first() - - self.mock_volume_cgroup_get.side_effect = self.exceptions.cinder - - url = reverse('horizon:project:cgroups:detail', - args=[cgroup.id]) - res = self.client.get(url) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - - @test.create_mocks({cinder: ('volume_cg_snapshot_create',)}) - def test_create_snapshot(self): - cgroup = self.cinder_consistencygroups.first() - cg_snapshot = self.cinder_cg_snapshots.first() - formData = {'cgroup_id': cgroup.id, - 'name': 'test CG Snapshot', - 'description': 'test desc'} - - self.mock_volume_cg_snapshot_create.return_value = cg_snapshot - - url = reverse('horizon:project:cgroups:create_snapshot', - args=[cgroup.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, VOLUME_CGROUPS_SNAP_INDEX_URL) - - self.mock_volume_cg_snapshot_create.assert_called_once_with( - test.IsHttpRequest(), - formData['cgroup_id'], - formData['name'], - formData['description']) - - @test.create_mocks({cinder: ('volume_cgroup_get', - 'volume_cgroup_create_from_source')}) - def test_create_clone(self): - cgroup = self.cinder_consistencygroups.first() - formData = {'cgroup_id': cgroup.id, - 'name': 'test CG Clone', - 'description': 'test desc'} - self.mock_volume_cgroup_get.return_value = cgroup - self.mock_volume_cgroup_create_from_source.return_value = cgroup - - url = reverse('horizon:project:cgroups:clone_cgroup', - args=[cgroup.id]) - res = self.client.post(url, formData) - self.assertNoFormErrors(res) - self.assertRedirectsNoFollow(res, INDEX_URL) - - self.mock_volume_cgroup_get.assert_called_once_with( - test.IsHttpRequest(), cgroup.id) - self.mock_volume_cgroup_create_from_source.assert_called_once_with( - test.IsHttpRequest(), - formData['name'], - source_cgroup_id=formData['cgroup_id'], - description=formData['description']) diff --git a/openstack_dashboard/dashboards/project/cgroups/urls.py b/openstack_dashboard/dashboards/project/cgroups/urls.py deleted file mode 100644 index 13af0016cb..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/urls.py +++ /dev/null @@ -1,44 +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.conf.urls import url - -from openstack_dashboard.dashboards.project.cgroups import views - - -urlpatterns = [ - url(r'^$', views.CGroupsView.as_view(), name='index'), - url(r'^create/$', - views.CreateView.as_view(), - name='create'), - url(r'^(?P[^/]+)/update/$', - views.UpdateView.as_view(), - name='update'), - url(r'^(?P[^/]+)/remove_volumese/$', - views.RemoveVolumesView.as_view(), - name='remove_volumes'), - url(r'^(?P[^/]+)/delete/$', - views.DeleteView.as_view(), - name='delete'), - url(r'^(?P[^/]+)/manage/$', - views.ManageView.as_view(), - name='manage'), - url(r'^(?P[^/]+)/create_snapshot/$', - views.CreateSnapshotView.as_view(), - name='create_snapshot'), - url(r'^(?P[^/]+)/clone_cgroup/$', - views.CloneCGroupView.as_view(), - name='clone_cgroup'), - url(r'^(?P[^/]+)$', - views.DetailView.as_view(), - name='detail'), -] diff --git a/openstack_dashboard/dashboards/project/cgroups/views.py b/openstack_dashboard/dashboards/project/cgroups/views.py deleted file mode 100644 index e8df48f421..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/views.py +++ /dev/null @@ -1,320 +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.urls import reverse -from django.urls 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 import tabs -from horizon.utils import memoized -from horizon import workflows - -from openstack_dashboard import api -from openstack_dashboard.api import cinder -from openstack_dashboard.usage import quotas - -from openstack_dashboard.dashboards.project.cgroups \ - import forms as vol_cgroup_forms -from openstack_dashboard.dashboards.project.cgroups \ - import tables as vol_cgroup_tables -from openstack_dashboard.dashboards.project.cgroups \ - import tabs as vol_cgroup_tabs -from openstack_dashboard.dashboards.project.cgroups \ - import workflows as vol_cgroup_workflows - -CGROUP_INFO_FIELDS = ("name", - "description") - -INDEX_URL = "horizon:project:cgroups:index" - - -class CGroupsView(tables.DataTableView): - table_class = vol_cgroup_tables.VolumeCGroupsTable - page_title = _("Consistency Groups") - - def get_data(self): - try: - cgroups = api.cinder.volume_cgroup_list_with_vol_type_names( - self.request) - - except Exception: - cgroups = [] - exceptions.handle(self.request, _("Unable to retrieve " - "volume consistency groups.")) - return cgroups - - -class CreateView(workflows.WorkflowView): - workflow_class = vol_cgroup_workflows.CreateCGroupWorkflow - template_name = 'project/cgroups/create.html' - page_title = _("Create Volume Consistency Group") - - -class UpdateView(forms.ModalFormView): - template_name = 'project/cgroups/update.html' - page_title = _("Edit Consistency Group") - form_class = vol_cgroup_forms.UpdateForm - success_url = reverse_lazy('horizon:project:cgroups:index') - submit_url = "horizon:project:cgroups:update" - - def get_initial(self): - cgroup = self.get_object() - return {'cgroup_id': self.kwargs["cgroup_id"], - 'name': cgroup.name, - 'description': cgroup.description} - - def get_context_data(self, **kwargs): - context = super(UpdateView, self).get_context_data(**kwargs) - context['cgroup_id'] = self.kwargs['cgroup_id'] - args = (self.kwargs['cgroup_id'],) - context['submit_url'] = reverse(self.submit_url, args=args) - return context - - def get_object(self): - cgroup_id = self.kwargs['cgroup_id'] - try: - self._object = cinder.volume_cgroup_get(self.request, cgroup_id) - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve consistency group ' - 'details.'), - redirect=reverse(INDEX_URL)) - return self._object - - -class RemoveVolumesView(forms.ModalFormView): - template_name = 'project/cgroups/remove_vols.html' - page_title = _("Remove Volumes from Consistency Group") - form_class = vol_cgroup_forms.RemoveVolsForm - success_url = reverse_lazy('horizon:project:cgroups:index') - submit_url = "horizon:project:cgroups:remove_volumes" - - def get_initial(self): - cgroup = self.get_object() - return {'cgroup_id': self.kwargs["cgroup_id"], - 'name': cgroup.name} - - def get_context_data(self, **kwargs): - context = super(RemoveVolumesView, self).get_context_data(**kwargs) - context['cgroup_id'] = self.kwargs['cgroup_id'] - args = (self.kwargs['cgroup_id'],) - context['submit_url'] = reverse(self.submit_url, args=args) - return context - - def get_object(self): - cgroup_id = self.kwargs['cgroup_id'] - try: - self._object = cinder.volume_cgroup_get(self.request, cgroup_id) - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve consistency group ' - 'details.'), - redirect=reverse(INDEX_URL)) - return self._object - - -class DeleteView(forms.ModalFormView): - template_name = 'project/cgroups/delete.html' - page_title = _("Delete Consistency Group") - form_class = vol_cgroup_forms.DeleteForm - success_url = reverse_lazy('horizon:project:cgroups:index') - submit_url = "horizon:project:cgroups:delete" - submit_label = page_title - - def get_initial(self): - cgroup = self.get_object() - return {'cgroup_id': self.kwargs["cgroup_id"], - 'name': cgroup.name} - - def get_context_data(self, **kwargs): - context = super(DeleteView, self).get_context_data(**kwargs) - context['cgroup_id'] = self.kwargs['cgroup_id'] - args = (self.kwargs['cgroup_id'],) - context['submit_url'] = reverse(self.submit_url, args=args) - return context - - def get_object(self): - cgroup_id = self.kwargs['cgroup_id'] - try: - self._object = cinder.volume_cgroup_get(self.request, cgroup_id) - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve consistency group ' - 'details.'), - redirect=reverse(INDEX_URL)) - return self._object - - -class ManageView(workflows.WorkflowView): - workflow_class = vol_cgroup_workflows.UpdateCGroupWorkflow - - def get_context_data(self, **kwargs): - context = super(ManageView, self).get_context_data(**kwargs) - context['cgroup_id'] = self.kwargs["cgroup_id"] - return context - - def _get_object(self, *args, **kwargs): - cgroup_id = self.kwargs['cgroup_id'] - try: - cgroup = cinder.volume_cgroup_get(self.request, cgroup_id) - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve consistency group ' - 'details.'), - redirect=reverse(INDEX_URL)) - return cgroup - - def get_initial(self): - cgroup = self._get_object() - return {'cgroup_id': cgroup.id, - 'name': cgroup.name, - 'description': cgroup.description, - 'vtypes': getattr(cgroup, "volume_types")} - - -class CreateSnapshotView(forms.ModalFormView): - form_class = vol_cgroup_forms.CreateSnapshotForm - page_title = _("Create Consistency Group Snapshot") - template_name = 'project/cgroups/create_snapshot.html' - submit_label = _("Create Snapshot") - submit_url = "horizon:project:cgroups:create_snapshot" - success_url = reverse_lazy('horizon:project:cg_snapshots:index') - - def get_context_data(self, **kwargs): - context = super(CreateSnapshotView, self).get_context_data(**kwargs) - context['cgroup_id'] = self.kwargs['cgroup_id'] - args = (self.kwargs['cgroup_id'],) - context['submit_url'] = reverse(self.submit_url, args=args) - try: - # get number of snapshots we will be creating - search_opts = {'consistencygroup_id': context['cgroup_id']} - volumes = api.cinder.volume_list(self.request, - search_opts=search_opts) - num_volumes = len(volumes) - usages = quotas.tenant_quota_usages( - self.request, targets=('snapshots', 'gigabytes')) - if (usages['snapshots']['used'] + num_volumes > - usages['snapshots']['quota']): - raise ValueError(_('Unable to create snapshots due to ' - 'exceeding snapshot quota limit.')) - else: - context['numRequestedItems'] = num_volumes - context['usages'] = usages - - except ValueError as e: - exceptions.handle(self.request, e.message) - return None - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve consistency ' - 'group information.')) - return context - - def get_initial(self): - return {'cgroup_id': self.kwargs["cgroup_id"]} - - -class CloneCGroupView(forms.ModalFormView): - form_class = vol_cgroup_forms.CloneCGroupForm - page_title = _("Clone Consistency Group") - template_name = 'project/cgroups/clone_cgroup.html' - submit_label = _("Clone Consistency Group") - submit_url = "horizon:project:cgroups:clone_cgroup" - success_url = reverse_lazy('horizon:project:cgroups:index') - - def get_context_data(self, **kwargs): - context = super(CloneCGroupView, self).get_context_data(**kwargs) - context['cgroup_id'] = self.kwargs['cgroup_id'] - args = (self.kwargs['cgroup_id'],) - context['submit_url'] = reverse(self.submit_url, args=args) - try: - # get number of volumes we will be creating - cgroup_id = context['cgroup_id'] - - search_opts = {'consistencygroup_id': cgroup_id} - volumes = api.cinder.volume_list(self.request, - search_opts=search_opts) - num_volumes = len(volumes) - usages = quotas.tenant_quota_usages( - self.request, targets=('volumes', 'gigabytes')) - if (usages['volumes']['used'] + num_volumes > - usages['volumes']['quota']): - raise ValueError(_('Unable to create consistency group due to ' - 'exceeding volume quota limit.')) - else: - context['numRequestedItems'] = num_volumes - context['usages'] = usages - - except ValueError as e: - exceptions.handle(self.request, e.message) - return None - except Exception: - exceptions.handle(self.request, - _('Unable to retrieve consistency ' - 'group information.')) - return context - - def get_initial(self): - return {'cgroup_id': self.kwargs["cgroup_id"]} - - -class DetailView(tabs.TabView): - tab_group_class = vol_cgroup_tabs.CGroupsDetailTabs - template_name = 'horizon/common/_detail.html' - page_title = "{{ cgroup.name|default:cgroup.id }}" - - def get_context_data(self, **kwargs): - context = super(DetailView, self).get_context_data(**kwargs) - cgroup = self.get_data() - table = vol_cgroup_tables.VolumeCGroupsTable(self.request) - context["cgroup"] = cgroup - context["url"] = self.get_redirect_url() - context["actions"] = table.render_row_actions(cgroup) - return context - - @memoized.memoized_method - def get_data(self): - try: - cgroup_id = self.kwargs['cgroup_id'] - cgroup = api.cinder.volume_cgroup_get(self.request, - cgroup_id) - cgroup.volume_type_names = [] - for vol_type_id in cgroup.volume_types: - vol_type = api.cinder.volume_type_get(self.request, - vol_type_id) - cgroup.volume_type_names.append(vol_type.name) - - cgroup.volume_names = [] - search_opts = {'consistencygroup_id': cgroup_id} - volumes = api.cinder.volume_list(self.request, - search_opts=search_opts) - for volume in volumes: - cgroup.volume_names.append(volume.name) - - except Exception: - redirect = self.get_redirect_url() - exceptions.handle(self.request, - _('Unable to retrieve consistency group ' - 'details.'), - redirect=redirect) - return cgroup - - @staticmethod - def get_redirect_url(): - return reverse('horizon:project:cgroups:index') - - def get_tabs(self, request, *args, **kwargs): - cgroup = self.get_data() - return self.tab_group_class(request, cgroup=cgroup, **kwargs) diff --git a/openstack_dashboard/dashboards/project/cgroups/workflows.py b/openstack_dashboard/dashboards/project/cgroups/workflows.py deleted file mode 100644 index e912171fd9..0000000000 --- a/openstack_dashboard/dashboards/project/cgroups/workflows.py +++ /dev/null @@ -1,414 +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.utils.translation import ugettext_lazy as _ - -from horizon import exceptions -from horizon import forms -from horizon import workflows - -from openstack_dashboard import api -from openstack_dashboard.api import cinder - -INDEX_URL = "horizon:project:cgroups:index" -CGROUP_VOLUME_MEMBER_SLUG = "update_members" - - -def cinder_az_supported(request): - try: - return cinder.extension_supported(request, 'AvailabilityZones') - except Exception: - exceptions.handle(request, _('Unable to determine if availability ' - 'zones extension is supported.')) - return False - - -def availability_zones(request): - zone_list = [] - if cinder_az_supported(request): - try: - zones = api.cinder.availability_zone_list(request) - zone_list = [(zone.zoneName, zone.zoneName) - for zone in zones if zone.zoneState['available']] - zone_list.sort() - except Exception: - exceptions.handle(request, _('Unable to retrieve availability ' - 'zones.')) - if not zone_list: - zone_list.insert(0, ("", _("No availability zones found"))) - elif len(zone_list) > 1: - zone_list.insert(0, ("", _("Any Availability Zone"))) - - return zone_list - - -class AddCGroupInfoAction(workflows.Action): - name = forms.CharField(label=_("Name"), - max_length=255) - description = forms.CharField(widget=forms.widgets.Textarea( - attrs={'rows': 4}), - label=_("Description"), - required=False) - availability_zone = forms.ChoiceField( - label=_("Availability Zone"), - required=False, - widget=forms.ThemableSelectWidget( - attrs={'class': 'switched', - 'data-switch-on': 'source', - 'data-source-no_source_type': _('Availability Zone'), - 'data-source-image_source': _('Availability Zone')})) - - def __init__(self, request, *args, **kwargs): - super(AddCGroupInfoAction, self).__init__(request, - *args, - **kwargs) - self.fields['availability_zone'].choices = \ - availability_zones(request) - - class Meta(object): - name = _("Consistency Group Information") - help_text = _("Volume consistency groups provide a mechanism for " - "creating snapshots of multiple volumes at the same " - "point-in-time to ensure data consistency\n\n" - "A consistency group can support more than one volume " - "type, but it can only contain volumes hosted by the " - "same back end.") - slug = "set_cgroup_info" - - def clean(self): - cleaned_data = super(AddCGroupInfoAction, self).clean() - name = cleaned_data.get('name') - - try: - cgroups = cinder.volume_cgroup_list(self.request) - except Exception: - msg = _('Unable to get consistency group list') - exceptions.check_message(["Connection", "refused"], msg) - raise - - if cgroups is not None and name is not None: - for cgroup in cgroups: - if cgroup.name.lower() == name.lower(): - # ensure new name has reasonable length - formatted_name = name - if len(name) > 20: - formatted_name = name[:14] + "..." + name[-3:] - raise forms.ValidationError( - _('The name "%s" is already used by ' - 'another consistency group.') - % formatted_name - ) - - return cleaned_data - - -class AddCGroupInfoStep(workflows.Step): - action_class = AddCGroupInfoAction - contributes = ("availability_zone", - "description", - "name") - - -class AddVolumeTypesToCGroupAction(workflows.MembershipAction): - def __init__(self, request, *args, **kwargs): - super(AddVolumeTypesToCGroupAction, self).__init__(request, - *args, - **kwargs) - err_msg = _('Unable to get the available volume types') - - default_role_field_name = self.get_default_role_field_name() - self.fields[default_role_field_name] = forms.CharField(required=False) - self.fields[default_role_field_name].initial = 'member' - - field_name = self.get_member_field_name('member') - self.fields[field_name] = forms.MultipleChoiceField(required=False) - - vtypes = [] - try: - vtypes = cinder.volume_type_list(request) - except Exception: - exceptions.handle(request, err_msg) - - vtype_list = [(vtype.id, vtype.name) - for vtype in vtypes] - self.fields[field_name].choices = vtype_list - - class Meta(object): - name = _("Manage Volume Types") - slug = "add_vtypes_to_cgroup" - - def clean(self): - cleaned_data = super(AddVolumeTypesToCGroupAction, self).clean() - volume_types = cleaned_data.get('add_vtypes_to_cgroup_role_member') - if not volume_types: - raise forms.ValidationError( - _('At least one volume type must be assigned ' - 'to a consistency group.') - ) - - return cleaned_data - - -class AddVolTypesToCGroupStep(workflows.UpdateMembersStep): - action_class = AddVolumeTypesToCGroupAction - help_text = _("Add volume types to this consistency group. " - "Multiple volume types can be added to the same " - "consistency group only if they are associated with " - "same back end.") - available_list_title = _("All available volume types") - members_list_title = _("Selected volume types") - no_available_text = _("No volume types found.") - no_members_text = _("No volume types selected.") - show_roles = False - contributes = ("volume_types",) - - def contribute(self, data, context): - if data: - member_field_name = self.get_member_field_name('member') - context['volume_types'] = data.get(member_field_name, []) - return context - - -class AddVolumesToCGroupAction(workflows.MembershipAction): - def __init__(self, request, *args, **kwargs): - super(AddVolumesToCGroupAction, self).__init__(request, - *args, - **kwargs) - err_msg = _('Unable to get the available volumes') - - default_role_field_name = self.get_default_role_field_name() - self.fields[default_role_field_name] = forms.CharField(required=False) - self.fields[default_role_field_name].initial = 'member' - - field_name = self.get_member_field_name('member') - self.fields[field_name] = forms.MultipleChoiceField(required=False) - - vtypes = self.initial['vtypes'] - try: - # get names of volume types associated with CG - vtype_names = [] - volume_types = cinder.volume_type_list(request) - for volume_type in volume_types: - if volume_type.id in vtypes: - vtype_names.append(volume_type.name) - - # collect volumes that are associated with volume types - vol_list = [] - volumes = cinder.volume_list(request) - for volume in volumes: - if volume.volume_type in vtype_names: - cgroup_id = None - vol_is_available = False - in_this_cgroup = False - if hasattr(volume, 'consistencygroup_id'): - # this vol already belongs to a CG. - # only include it here if it belongs to this CG - cgroup_id = volume.consistencygroup_id - - if not cgroup_id: - # put this vol in the available list - vol_is_available = True - elif cgroup_id == self.initial['cgroup_id']: - # put this vol in the assigned to CG list - vol_is_available = True - in_this_cgroup = True - - if vol_is_available: - vol_list.append({'volume_name': volume.name, - 'volume_id': volume.id, - 'in_cgroup': in_this_cgroup, - 'is_duplicate': False}) - - sorted_vol_list = sorted(vol_list, key=lambda k: k['volume_name']) - - # mark any duplicate volume names - for index, volume in enumerate(sorted_vol_list): - if index < len(sorted_vol_list) - 1: - if volume['volume_name'] == \ - sorted_vol_list[index + 1]['volume_name']: - volume['is_duplicate'] = True - sorted_vol_list[index + 1]['is_duplicate'] = True - - # update display with all available vols and those already - # assigned to consistency group - available_vols = [] - assigned_vols = [] - for volume in sorted_vol_list: - if volume['is_duplicate']: - # add id to differentiate volumes to user - entry = volume['volume_name'] + \ - " [" + volume['volume_id'] + "]" - else: - entry = volume['volume_name'] - available_vols.append((volume['volume_id'], entry)) - if volume['in_cgroup']: - assigned_vols.append(volume['volume_id']) - - except Exception: - exceptions.handle(request, err_msg) - - self.fields[field_name].choices = \ - available_vols - self.fields[field_name].initial = assigned_vols - - class Meta(object): - name = _("Manage Volumes") - slug = "add_volumes_to_cgroup" - - -class AddVolumesToCGroupStep(workflows.UpdateMembersStep): - action_class = AddVolumesToCGroupAction - help_text = _("Add/remove volumes to/from this consistency group. " - "Only volumes associated with the volume type(s) assigned " - "to this consistency group will be available for selection.") - available_list_title = _("All available volumes") - members_list_title = _("Selected volumes") - no_available_text = _("No volumes found.") - no_members_text = _("No volumes selected.") - show_roles = False - depends_on = ("cgroup_id", "name", "vtypes") - contributes = ("volumes",) - - def contribute(self, data, context): - if data: - member_field_name = self.get_member_field_name('member') - context['volumes'] = data.get(member_field_name, []) - return context - - -class CreateCGroupWorkflow(workflows.Workflow): - slug = "create_cgroup" - name = _("Create Consistency Group") - finalize_button_name = _("Create Consistency Group") - failure_message = _('Unable to create consistency group.') - success_message = _('Created new volume consistency group') - success_url = INDEX_URL - default_steps = (AddCGroupInfoStep, - AddVolTypesToCGroupStep) - - def handle(self, request, context): - selected_vol_types = context['volume_types'] - - try: - vol_types = cinder.volume_type_list_with_qos_associations( - request) - except Exception: - msg = _('Unable to get volume type list') - exceptions.check_message(["Connection", "refused"], msg) - return False - - # ensure that all selected volume types share same backend name - backend_name = None - invalid_backend = False - for selected_vol_type in selected_vol_types: - if invalid_backend: - continue - for vol_type in vol_types: - if selected_vol_type != vol_type.id: - continue - if (hasattr(vol_type, "extra_specs") and - 'volume_backend_name' in vol_type.extra_specs): - vol_type_backend = \ - vol_type.extra_specs['volume_backend_name'] - if vol_type_backend is None: - invalid_backend = True - break - if backend_name is None: - backend_name = vol_type_backend - if vol_type_backend != backend_name: - invalid_backend = True - break - else: - invalid_backend = True - break - - if invalid_backend: - msg = _('All selected volume types must be associated ' - 'with the same volume backend name.') - exceptions.handle(request, msg) - return False - - try: - vtypes_str = ",".join(context['volume_types']) - self.object = \ - cinder.volume_cgroup_create( - request, - vtypes_str, - context['name'], - description=context['description'], - availability_zone=context['availability_zone']) - except Exception: - exceptions.handle(request, _('Unable to create consistency ' - 'group.')) - return False - - return True - - -class UpdateCGroupWorkflow(workflows.Workflow): - slug = "update_cgroup" - name = _("Add/Remove Consistency Group Volumes") - finalize_button_name = _("Submit") - success_message = _('Updated volumes for consistency group "%s".') - failure_message = _('Unable to update volumes for consistency group') - success_url = INDEX_URL - default_steps = (AddVolumesToCGroupStep,) - - def handle(self, request, context): - cgroup_id = context['cgroup_id'] - add_vols = [] - remove_vols = [] - try: - selected_volumes = context['volumes'] - volumes = cinder.volume_list(request) - - # scan all volumes and make correct consistency group is set - for volume in volumes: - selected = False - for selection in selected_volumes: - if selection == volume.id: - selected = True - break - - if selected: - # ensure this volume is in this consistency group - if hasattr(volume, 'consistencygroup_id'): - if volume.consistencygroup_id != cgroup_id: - add_vols.append(volume.id) - else: - add_vols.append(volume.id) - else: - # ensure this volume is not in our consistency group - if hasattr(volume, 'consistencygroup_id'): - if volume.consistencygroup_id == cgroup_id: - # remove from this CG - remove_vols.append(volume.id) - - add_vols_str = ",".join(add_vols) - remove_vols_str = ",".join(remove_vols) - - if not add_vols_str and not remove_vols_str: - # nothing to change - return True - - cinder.volume_cgroup_update(request, - cgroup_id, - name=context['name'], - add_vols=add_vols_str, - remove_vols=remove_vols_str) - - except Exception: - # error message supplied by form - return False - - return True diff --git a/openstack_dashboard/dashboards/project/vg_snapshots/forms.py b/openstack_dashboard/dashboards/project/vg_snapshots/forms.py index 7b4bbbfc5f..7831420847 100644 --- a/openstack_dashboard/dashboards/project/vg_snapshots/forms.py +++ b/openstack_dashboard/dashboards/project/vg_snapshots/forms.py @@ -47,7 +47,6 @@ class CreateGroupForm(forms.SelfHandlingForm): def __init__(self, request, *args, **kwargs): super(CreateGroupForm, self).__init__(request, *args, **kwargs) - # populate cgroup_id vg_snapshot_id = kwargs.get('initial', {}).get('vg_snapshot_id', []) self.fields['vg_snapshot_id'] = forms.CharField( widget=forms.HiddenInput(), diff --git a/openstack_dashboard/dashboards/project/volume_groups/tests.py b/openstack_dashboard/dashboards/project/volume_groups/tests.py index 8e08705524..4cd88fb985 100644 --- a/openstack_dashboard/dashboards/project/volume_groups/tests.py +++ b/openstack_dashboard/dashboards/project/volume_groups/tests.py @@ -152,7 +152,7 @@ class VolumeGroupTests(test.TestCase): @test.create_mocks({cinder: ['group_get', 'group_delete']}) def test_delete_group_delete_volumes_flag(self): - group = self.cinder_consistencygroups.first() + group = self.cinder_groups.first() formData = {'delete_volumes': True} self.mock_group_get.return_value = group diff --git a/openstack_dashboard/enabled/_1340_project_consistency_groups_panel.py b/openstack_dashboard/enabled/_1340_project_consistency_groups_panel.py deleted file mode 100644 index 1450e4a9cd..0000000000 --- a/openstack_dashboard/enabled/_1340_project_consistency_groups_panel.py +++ /dev/null @@ -1,9 +0,0 @@ -# The slug of the panel to be added to HORIZON_CONFIG. Required. -PANEL = 'cgroups' -# The slug of the dashboard the PANEL associated with. Required. -PANEL_DASHBOARD = 'project' -# The slug of the panel group the PANEL is associated with. -PANEL_GROUP = 'volumes' - -# Python panel class of the PANEL to be added. -ADD_PANEL = 'openstack_dashboard.dashboards.project.cgroups.panel.CGroups' diff --git a/openstack_dashboard/enabled/_1350_project_cg_snapshots_panel.py b/openstack_dashboard/enabled/_1350_project_cg_snapshots_panel.py deleted file mode 100644 index 1709ef49fb..0000000000 --- a/openstack_dashboard/enabled/_1350_project_cg_snapshots_panel.py +++ /dev/null @@ -1,10 +0,0 @@ -# The slug of the panel to be added to HORIZON_CONFIG. Required. -PANEL = 'cg_snapshots' -# The slug of the dashboard the PANEL associated with. Required. -PANEL_DASHBOARD = 'project' -# The slug of the panel group the PANEL is associated with. -PANEL_GROUP = 'volumes' - -# Python panel class of the PANEL to be added. -ADD_PANEL = \ - 'openstack_dashboard.dashboards.project.cg_snapshots.panel.CGSnapshots' diff --git a/openstack_dashboard/settings.py b/openstack_dashboard/settings.py index 6527bfd6e3..32a3f5e2c6 100644 --- a/openstack_dashboard/settings.py +++ b/openstack_dashboard/settings.py @@ -289,7 +289,6 @@ POLICY_FILES = { # in POLICY_DIRS by default. POLICY_DIRS = { 'compute': ['nova_policy.d'], - 'volume': ['cinder_policy.d'], } SECRET_KEY = None diff --git a/openstack_dashboard/test/settings.py b/openstack_dashboard/test/settings.py index 34d87300fa..33ff960865 100644 --- a/openstack_dashboard/test/settings.py +++ b/openstack_dashboard/test/settings.py @@ -275,16 +275,6 @@ TEST_GLOBAL_MOCKS_ON_PANELS = { '.aggregates.panel.Aggregates.can_access'), 'return_value': True, }, - 'cgroups': { - 'method': ('openstack_dashboard.dashboards.project' - '.cgroups.panel.CGroups.allowed'), - 'return_value': True, - }, - 'cg_snapshots': { - 'method': ('openstack_dashboard.dashboards.project' - '.cg_snapshots.panel.CGSnapshots.allowed'), - 'return_value': True, - }, 'domains': { 'method': ('openstack_dashboard.dashboards.identity' '.domains.panel.Domains.can_access'), diff --git a/openstack_dashboard/test/test_data/cinder_data.py b/openstack_dashboard/test/test_data/cinder_data.py index ad2734df71..98b29b915b 100644 --- a/openstack_dashboard/test/test_data/cinder_data.py +++ b/openstack_dashboard/test/test_data/cinder_data.py @@ -13,8 +13,6 @@ # under the License. from cinderclient.v2 import availability_zones -from cinderclient.v2 import cgsnapshots -from cinderclient.v2 import consistencygroups from cinderclient.v2.contrib import list_extensions as cinder_list_extensions from cinderclient.v2 import pools from cinderclient.v2 import qos_specs @@ -55,9 +53,6 @@ def data(TEST): TEST.cinder_availability_zones = utils.TestDataContainer() TEST.cinder_volume_transfers = utils.TestDataContainer() TEST.cinder_pools = utils.TestDataContainer() - TEST.cinder_consistencygroups = utils.TestDataContainer() - TEST.cinder_cgroup_volumes = utils.TestDataContainer() - TEST.cinder_cg_snapshots = utils.TestDataContainer() TEST.cinder_groups = utils.TestDataContainer() TEST.cinder_group_types = utils.TestDataContainer() TEST.cinder_group_snapshots = utils.TestDataContainer() @@ -456,49 +451,6 @@ def data(TEST): TEST.cinder_pools.add(pool1) TEST.cinder_pools.add(pool2) - # volume consistency groups - cgroup_1 = consistencygroups.Consistencygroup( - consistencygroups.ConsistencygroupManager(None), - {'id': u'1', - 'name': u'cg_1', - 'description': 'cg 1 description', - 'volume_types': ['1'], - 'volume_type_names': []}) - - cgroup_2 = consistencygroups.Consistencygroup( - consistencygroups.ConsistencygroupManager(None), - {'id': u'2', - 'name': u'cg_2', - 'description': 'cg 2 description', - 'volume_types': ['1'], - 'volume_type_names': []}) - - TEST.cinder_consistencygroups.add(cgroup_1) - TEST.cinder_consistencygroups.add(cgroup_2) - - volume_for_consistency_group = volumes.Volume( - volumes.VolumeManager(None), - {'id': "11023e92-8008-4c8b-8059-7f2293ff3881", - 'status': 'available', - 'size': 40, - 'name': 'Volume name', - 'display_description': 'Volume description', - 'created_at': '2014-01-27 10:30:00', - 'volume_type': 'vol_type_1', - 'attachments': [], - 'consistencygroup_id': u'1'}) - TEST.cinder_cgroup_volumes.add(api.cinder.Volume( - volume_for_consistency_group)) - - # volume consistency group snapshots - cg_snapshot_1 = cgsnapshots.Cgsnapshot( - cgsnapshots.CgsnapshotManager(None), - {'id': u'1', - 'name': u'cg_ss_1', - 'description': 'cg_ss 1 description', - 'consistencygroup_id': u'1'}) - TEST.cinder_cg_snapshots.add(cg_snapshot_1) - group_type_1 = group_types.GroupType( group_types.GroupTypeManager(None), { diff --git a/openstack_dashboard/test/unit/api/test_cinder.py b/openstack_dashboard/test/unit/api/test_cinder.py index 70531c4340..c25fd56142 100644 --- a/openstack_dashboard/test/unit/api/test_cinder.py +++ b/openstack_dashboard/test/unit/api/test_cinder.py @@ -446,85 +446,6 @@ class CinderApiTests(test.APIMockTestCase): self.assertEqual(default_volume_type, volume_type) cinderclient.volume_types.default.assert_called_once() - @mock.patch.object(api.cinder, 'cinderclient') - def test_cgroup_list(self, mock_cinderclient): - cgroups = self.cinder_consistencygroups.list() - cinderclient = mock_cinderclient.return_value - - mock_cgs = cinderclient.consistencygroups.list - mock_cgs.return_value = cgroups - - api_cgroups = api.cinder.volume_cgroup_list(self.request) - - self.assertEqual(len(cgroups), len(api_cgroups)) - mock_cgs.assert_called_once_with(search_opts=None) - - @mock.patch.object(api.cinder, 'cinderclient') - def test_cgroup_get(self, mock_cinderclient): - cgroup = self.cinder_consistencygroups.first() - cinderclient = mock_cinderclient.return_value - - mock_cg = cinderclient.consistencygroups.get - mock_cg.return_value = cgroup - - api_cgroup = api.cinder.volume_cgroup_get(self.request, cgroup.id) - - mock_cg.assert_called_once_with(cgroup.id) - self.assertEqual(api_cgroup.name, cgroup.name) - self.assertEqual(api_cgroup.description, cgroup.description) - self.assertEqual(api_cgroup.volume_types, cgroup.volume_types) - - @mock.patch.object(api.cinder, 'cinderclient') - def test_cgroup_list_with_vol_type_names(self, mock_cinderclient): - cgroups = self.cinder_consistencygroups.list() - volume_types_list = self.cinder_volume_types.list() - cinderclient = mock_cinderclient.return_value - - mock_cgs = cinderclient.consistencygroups.list - mock_cgs.return_value = cgroups - - mock_volume_types = cinderclient.volume_types.list - mock_volume_types.return_value = volume_types_list - - api_cgroups = api.cinder.volume_cgroup_list_with_vol_type_names( - self.request) - - mock_cgs.assert_called_once_with(search_opts=None) - mock_volume_types.assert_called_once() - self.assertEqual(len(cgroups), len(api_cgroups)) - for i in range(len(api_cgroups[0].volume_type_names)): - self.assertEqual(volume_types_list[i].name, - api_cgroups[0].volume_type_names[i]) - - @mock.patch.object(api.cinder, 'cinderclient') - def test_cgsnapshot_list(self, mock_cinderclient): - cgsnapshots = self.cinder_cg_snapshots.list() - cinderclient = mock_cinderclient.return_value - - mock_cg_snapshots = cinderclient.cgsnapshots.list - mock_cg_snapshots.return_value = cgsnapshots - - api_cgsnapshots = api.cinder.volume_cg_snapshot_list(self.request) - - mock_cg_snapshots.assert_called_once_with(search_opts=None) - self.assertEqual(len(cgsnapshots), len(api_cgsnapshots)) - - @mock.patch.object(api.cinder, 'cinderclient') - def test_cgsnapshot_get(self, mock_cinderclient): - cgsnapshot = self.cinder_cg_snapshots.first() - cinderclient = mock_cinderclient.return_value - - mock_cg_snapshot = cinderclient.cgsnapshots.get - mock_cg_snapshot.return_value = cgsnapshot - - api_cgsnapshot = api.cinder.volume_cg_snapshot_get(self.request, - cgsnapshot.id) - mock_cg_snapshot.assert_called_once_with(cgsnapshot.id) - self.assertEqual(api_cgsnapshot.name, cgsnapshot.name) - self.assertEqual(api_cgsnapshot.description, cgsnapshot.description) - self.assertEqual(api_cgsnapshot.consistencygroup_id, - cgsnapshot.consistencygroup_id) - class CinderApiVersionTests(test.TestCase): diff --git a/releasenotes/notes/drop-cgroup-c1f6b169ba10a5d3.yaml b/releasenotes/notes/drop-cgroup-c1f6b169ba10a5d3.yaml new file mode 100644 index 0000000000..11dd202c62 --- /dev/null +++ b/releasenotes/notes/drop-cgroup-c1f6b169ba10a5d3.yaml @@ -0,0 +1,8 @@ +--- +upgrade: + - | + Cinder consistency group support in horizon has been dropped + in Train release. It was deprecated in Pike release in Cinder + and deprecated in Stein release in Horizon. + The feature is superseded by the generic group feature and + horizon provides full support of the generic group.