1#
2#   Licensed under the Apache License, Version 2.0 (the "License"); you may
3#   not use this file except in compliance with the License. You may obtain
4#   a copy of the License at
5#
6#        http://www.apache.org/licenses/LICENSE-2.0
7#
8#   Unless required by applicable law or agreed to in writing, software
9#   distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10#   WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11#   License for the specific language governing permissions and limitations
12#   under the License.
13#
14
15"""Volume v2 consistency group action implementations"""
16
17import logging
18
19from osc_lib.cli import format_columns
20from osc_lib.command import command
21from osc_lib import exceptions
22from osc_lib import utils
23
24from openstackclient.i18n import _
25
26
27LOG = logging.getLogger(__name__)
28
29
30def _find_volumes(parsed_args_volumes, volume_client):
31    result = 0
32    uuid = ''
33    for volume in parsed_args_volumes:
34        try:
35            volume_id = utils.find_resource(
36                volume_client.volumes, volume).id
37            uuid += volume_id + ','
38        except Exception as e:
39            result += 1
40            LOG.error(_("Failed to find volume with "
41                        "name or ID '%(volume)s':%(e)s")
42                      % {'volume': volume, 'e': e})
43
44    return result, uuid
45
46
47class AddVolumeToConsistencyGroup(command.Command):
48    _description = _("Add volume(s) to consistency group")
49
50    def get_parser(self, prog_name):
51        parser = super(AddVolumeToConsistencyGroup, self).get_parser(prog_name)
52        parser.add_argument(
53            'consistency_group',
54            metavar="<consistency-group>",
55            help=_('Consistency group to contain <volume> (name or ID)'),
56        )
57        parser.add_argument(
58            'volumes',
59            metavar='<volume>',
60            nargs='+',
61            help=_('Volume(s) to add to <consistency-group> (name or ID) '
62                   '(repeat option to add multiple volumes)'),
63        )
64        return parser
65
66    def take_action(self, parsed_args):
67        volume_client = self.app.client_manager.volume
68        result, add_uuid = _find_volumes(parsed_args.volumes, volume_client)
69
70        if result > 0:
71            total = len(parsed_args.volumes)
72            LOG.error(_("%(result)s of %(total)s volumes failed "
73                      "to add.") % {'result': result, 'total': total})
74
75        if add_uuid:
76            add_uuid = add_uuid.rstrip(',')
77            consistency_group_id = utils.find_resource(
78                volume_client.consistencygroups,
79                parsed_args.consistency_group).id
80            volume_client.consistencygroups.update(
81                consistency_group_id, add_volumes=add_uuid)
82
83
84class CreateConsistencyGroup(command.ShowOne):
85    _description = _("Create new consistency group.")
86
87    def get_parser(self, prog_name):
88        parser = super(CreateConsistencyGroup, self).get_parser(prog_name)
89        parser.add_argument(
90            "name",
91            metavar="<name>",
92            nargs="?",
93            help=_("Name of new consistency group (default to None)")
94        )
95        exclusive_group = parser.add_mutually_exclusive_group(required=True)
96        exclusive_group.add_argument(
97            "--volume-type",
98            metavar="<volume-type>",
99            help=_("Volume type of this consistency group (name or ID)")
100        )
101        exclusive_group.add_argument(
102            "--consistency-group-source",
103            metavar="<consistency-group>",
104            help=_("Existing consistency group (name or ID)")
105        )
106        exclusive_group.add_argument(
107            "--consistency-group-snapshot",
108            metavar="<consistency-group-snapshot>",
109            help=_("Existing consistency group snapshot (name or ID)")
110        )
111        parser.add_argument(
112            "--description",
113            metavar="<description>",
114            help=_("Description of this consistency group")
115        )
116        parser.add_argument(
117            "--availability-zone",
118            metavar="<availability-zone>",
119            help=_("Availability zone for this consistency group "
120                   "(not available if creating consistency group "
121                   "from source)"),
122        )
123        return parser
124
125    def take_action(self, parsed_args):
126        volume_client = self.app.client_manager.volume
127        if parsed_args.volume_type:
128            volume_type_id = utils.find_resource(
129                volume_client.volume_types,
130                parsed_args.volume_type).id
131            consistency_group = volume_client.consistencygroups.create(
132                volume_type_id,
133                name=parsed_args.name,
134                description=parsed_args.description,
135                availability_zone=parsed_args.availability_zone
136            )
137        else:
138            if parsed_args.availability_zone:
139                msg = _("'--availability-zone' option will not work "
140                        "if creating consistency group from source")
141                LOG.warning(msg)
142
143            consistency_group_id = None
144            consistency_group_snapshot = None
145            if parsed_args.consistency_group_source:
146                consistency_group_id = utils.find_resource(
147                    volume_client.consistencygroups,
148                    parsed_args.consistency_group_source).id
149            elif parsed_args.consistency_group_snapshot:
150                consistency_group_snapshot = utils.find_resource(
151                    volume_client.cgsnapshots,
152                    parsed_args.consistency_group_snapshot).id
153
154            consistency_group = (
155                volume_client.consistencygroups.create_from_src(
156                    consistency_group_snapshot,
157                    consistency_group_id,
158                    name=parsed_args.name,
159                    description=parsed_args.description
160                )
161            )
162
163        return zip(*sorted(consistency_group._info.items()))
164
165
166class DeleteConsistencyGroup(command.Command):
167    _description = _("Delete consistency group(s).")
168
169    def get_parser(self, prog_name):
170        parser = super(DeleteConsistencyGroup, self).get_parser(prog_name)
171        parser.add_argument(
172            'consistency_groups',
173            metavar='<consistency-group>',
174            nargs="+",
175            help=_('Consistency group(s) to delete (name or ID)'),
176        )
177        parser.add_argument(
178            '--force',
179            action='store_true',
180            default=False,
181            help=_("Allow delete in state other than error or available"),
182        )
183        return parser
184
185    def take_action(self, parsed_args):
186        volume_client = self.app.client_manager.volume
187        result = 0
188
189        for i in parsed_args.consistency_groups:
190            try:
191                consistency_group_id = utils.find_resource(
192                    volume_client.consistencygroups, i).id
193                volume_client.consistencygroups.delete(
194                    consistency_group_id, parsed_args.force)
195            except Exception as e:
196                result += 1
197                LOG.error(_("Failed to delete consistency group with "
198                            "name or ID '%(consistency_group)s':%(e)s")
199                          % {'consistency_group': i, 'e': e})
200
201        if result > 0:
202            total = len(parsed_args.consistency_groups)
203            msg = (_("%(result)s of %(total)s consistency groups failed "
204                   "to delete.") % {'result': result, 'total': total})
205            raise exceptions.CommandError(msg)
206
207
208class ListConsistencyGroup(command.Lister):
209    _description = _("List consistency groups.")
210
211    def get_parser(self, prog_name):
212        parser = super(ListConsistencyGroup, self).get_parser(prog_name)
213        parser.add_argument(
214            '--all-projects',
215            action="store_true",
216            help=_('Show details for all projects. Admin only. '
217                   '(defaults to False)')
218        )
219        parser.add_argument(
220            '--long',
221            action="store_true",
222            help=_('List additional fields in output')
223        )
224        return parser
225
226    def take_action(self, parsed_args):
227        if parsed_args.long:
228            columns = ['ID', 'Status', 'Availability Zone',
229                       'Name', 'Description', 'Volume Types']
230        else:
231            columns = ['ID', 'Status', 'Name']
232        volume_client = self.app.client_manager.volume
233        consistency_groups = volume_client.consistencygroups.list(
234            detailed=True,
235            search_opts={'all_tenants': parsed_args.all_projects}
236        )
237
238        return (columns, (
239            utils.get_item_properties(
240                s, columns,
241                formatters={'Volume Types': format_columns.ListColumn})
242            for s in consistency_groups))
243
244
245class RemoveVolumeFromConsistencyGroup(command.Command):
246    _description = _("Remove volume(s) from consistency group")
247
248    def get_parser(self, prog_name):
249        parser = \
250            super(RemoveVolumeFromConsistencyGroup, self).get_parser(prog_name)
251        parser.add_argument(
252            'consistency_group',
253            metavar="<consistency-group>",
254            help=_('Consistency group containing <volume> (name or ID)'),
255        )
256        parser.add_argument(
257            'volumes',
258            metavar='<volume>',
259            nargs='+',
260            help=_('Volume(s) to remove from <consistency-group> (name or ID) '
261                   '(repeat option to remove multiple volumes)'),
262        )
263        return parser
264
265    def take_action(self, parsed_args):
266        volume_client = self.app.client_manager.volume
267        result, remove_uuid = _find_volumes(parsed_args.volumes, volume_client)
268
269        if result > 0:
270            total = len(parsed_args.volumes)
271            LOG.error(_("%(result)s of %(total)s volumes failed "
272                      "to remove.") % {'result': result, 'total': total})
273
274        if remove_uuid:
275            remove_uuid = remove_uuid.rstrip(',')
276            consistency_group_id = utils.find_resource(
277                volume_client.consistencygroups,
278                parsed_args.consistency_group).id
279            volume_client.consistencygroups.update(
280                consistency_group_id, remove_volumes=remove_uuid)
281
282
283class SetConsistencyGroup(command.Command):
284    _description = _("Set consistency group properties")
285
286    def get_parser(self, prog_name):
287        parser = super(SetConsistencyGroup, self).get_parser(prog_name)
288        parser.add_argument(
289            'consistency_group',
290            metavar='<consistency-group>',
291            help=_('Consistency group to modify (name or ID)')
292        )
293        parser.add_argument(
294            '--name',
295            metavar='<name>',
296            help=_('New consistency group name'),
297        )
298        parser.add_argument(
299            '--description',
300            metavar='<description>',
301            help=_('New consistency group description'),
302        )
303        return parser
304
305    def take_action(self, parsed_args):
306        volume_client = self.app.client_manager.volume
307        kwargs = {}
308        if parsed_args.name:
309            kwargs['name'] = parsed_args.name
310        if parsed_args.description:
311            kwargs['description'] = parsed_args.description
312        if kwargs:
313            consistency_group_id = utils.find_resource(
314                volume_client.consistencygroups,
315                parsed_args.consistency_group).id
316            volume_client.consistencygroups.update(
317                consistency_group_id, **kwargs)
318
319
320class ShowConsistencyGroup(command.ShowOne):
321    _description = _("Display consistency group details.")
322
323    def get_parser(self, prog_name):
324        parser = super(ShowConsistencyGroup, self).get_parser(prog_name)
325        parser.add_argument(
326            "consistency_group",
327            metavar="<consistency-group>",
328            help=_("Consistency group to display (name or ID)")
329        )
330        return parser
331
332    def take_action(self, parsed_args):
333        volume_client = self.app.client_manager.volume
334        consistency_group = utils.find_resource(
335            volume_client.consistencygroups,
336            parsed_args.consistency_group)
337        return zip(*sorted(consistency_group._info.items()))
338