1# Licensed under the Apache License, Version 2.0 (the "License"); you may
2# not use this file except in compliance with the License. You may obtain
3# a copy of the License at
4#
5#      http://www.apache.org/licenses/LICENSE-2.0
6#
7# Unless required by applicable law or agreed to in writing, software
8# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10# License for the specific language governing permissions and limitations
11# under the License.
12
13from openstack.block_storage import _base_proxy
14from openstack.block_storage.v3 import availability_zone
15from openstack.block_storage.v3 import backup as _backup
16from openstack.block_storage.v3 import capabilities as _capabilities
17from openstack.block_storage.v3 import extension as _extension
18from openstack.block_storage.v3 import group_type as _group_type
19from openstack.block_storage.v3 import limits as _limits
20from openstack.block_storage.v3 import resource_filter as _resource_filter
21from openstack.block_storage.v3 import snapshot as _snapshot
22from openstack.block_storage.v3 import stats as _stats
23from openstack.block_storage.v3 import type as _type
24from openstack.block_storage.v3 import volume as _volume
25from openstack import exceptions
26from openstack import resource
27
28
29class Proxy(_base_proxy.BaseBlockStorageProxy):
30
31    def get_snapshot(self, snapshot):
32        """Get a single snapshot
33
34        :param snapshot: The value can be the ID of a snapshot or a
35                         :class:`~openstack.volume.v3.snapshot.Snapshot`
36                         instance.
37
38        :returns: One :class:`~openstack.volume.v3.snapshot.Snapshot`
39        :raises: :class:`~openstack.exceptions.ResourceNotFound`
40                 when no resource can be found.
41        """
42        return self._get(_snapshot.Snapshot, snapshot)
43
44    def find_snapshot(self, name_or_id, ignore_missing=True, **attrs):
45        """Find a single snapshot
46
47        :param snapshot: The name or ID a snapshot
48        :param bool ignore_missing: When set to ``False``
49            :class:`~openstack.exceptions.ResourceNotFound` will be raised
50            when the snapshot does not exist.
51
52        :returns: One :class:`~openstack.volume.v3.snapshot.Snapshot`
53        :raises: :class:`~openstack.exceptions.ResourceNotFound`
54                 when no resource can be found.
55        """
56        return self._find(_snapshot.Snapshot, name_or_id,
57                          ignore_missing=ignore_missing)
58
59    def snapshots(self, details=True, **query):
60        """Retrieve a generator of snapshots
61
62        :param bool details: When set to ``False``
63            :class:`~openstack.block_storage.v3.snapshot.Snapshot`
64            objects will be returned. The default, ``True``, will cause
65            more attributes to be returned.
66        :param kwargs query: Optional query parameters to be sent to limit
67            the snapshots being returned.  Available parameters include:
68
69            * name: Name of the snapshot as a string.
70            * all_projects: Whether return the snapshots in all projects.
71            * project_id: Filter the snapshots by project.
72            * volume_id: volume id of a snapshot.
73            * status: Value of the status of the snapshot so that you can
74                      filter on "available" for example.
75
76        :returns: A generator of snapshot objects.
77        """
78        base_path = '/snapshots/detail' if details else None
79        return self._list(_snapshot.Snapshot, base_path=base_path, **query)
80
81    def create_snapshot(self, **attrs):
82        """Create a new snapshot from attributes
83
84        :param dict attrs: Keyword arguments which will be used to create
85                           a :class:`~openstack.volume.v3.snapshot.Snapshot`,
86                           comprised of the properties on the Snapshot class.
87
88        :returns: The results of snapshot creation
89        :rtype: :class:`~openstack.volume.v3.snapshot.Snapshot`
90        """
91        return self._create(_snapshot.Snapshot, **attrs)
92
93    def delete_snapshot(self, snapshot, ignore_missing=True):
94        """Delete a snapshot
95
96        :param snapshot: The value can be either the ID of a snapshot or a
97                         :class:`~openstack.volume.v3.snapshot.Snapshot`
98                         instance.
99        :param bool ignore_missing: When set to ``False``
100                    :class:`~openstack.exceptions.ResourceNotFound` will be
101                    raised when the snapshot does not exist.
102                    When set to ``True``, no exception will be set when
103                    attempting to delete a nonexistent snapshot.
104
105        :returns: ``None``
106        """
107        self._delete(_snapshot.Snapshot, snapshot,
108                     ignore_missing=ignore_missing)
109
110    def get_type(self, type):
111        """Get a single type
112
113        :param type: The value can be the ID of a type or a
114                     :class:`~openstack.volume.v3.type.Type` instance.
115
116        :returns: One :class:`~openstack.volume.v3.type.Type`
117        :raises: :class:`~openstack.exceptions.ResourceNotFound`
118                 when no resource can be found.
119        """
120        return self._get(_type.Type, type)
121
122    def find_type(self, name_or_id, ignore_missing=True, **attrs):
123        """Find a single volume type
124
125        :param snapshot: The name or ID a volume type
126        :param bool ignore_missing: When set to ``False``
127            :class:`~openstack.exceptions.ResourceNotFound` will be raised
128            when the type does not exist.
129
130        :returns: One :class:`~openstack.volume.v3.type.Type`
131        :raises: :class:`~openstack.exceptions.ResourceNotFound`
132                 when no resource can be found.
133        """
134        return self._find(_type.Type, name_or_id,
135                          ignore_missing=ignore_missing)
136
137    def types(self, **query):
138        """Retrieve a generator of volume types
139
140        :returns: A generator of volume type objects.
141        """
142        return self._list(_type.Type, **query)
143
144    def create_type(self, **attrs):
145        """Create a new type from attributes
146
147        :param dict attrs: Keyword arguments which will be used to create
148                           a :class:`~openstack.volume.v3.type.Type`,
149                           comprised of the properties on the Type class.
150
151        :returns: The results of type creation
152        :rtype: :class:`~openstack.volume.v3.type.Type`
153        """
154        return self._create(_type.Type, **attrs)
155
156    def delete_type(self, type, ignore_missing=True):
157        """Delete a type
158
159        :param type: The value can be either the ID of a type or a
160                     :class:`~openstack.volume.v3.type.Type` instance.
161        :param bool ignore_missing: When set to ``False``
162                    :class:`~openstack.exceptions.ResourceNotFound` will be
163                    raised when the type does not exist.
164                    When set to ``True``, no exception will be set when
165                    attempting to delete a nonexistent type.
166
167        :returns: ``None``
168        """
169        self._delete(_type.Type, type, ignore_missing=ignore_missing)
170
171    def update_type(self, type, **attrs):
172        """Update a type
173
174        :param type: The value can be either the ID of a type or a
175                     :class:`~openstack.volume.v3.type.Type` instance.
176        :param dict attrs: The attributes to update on the type
177                           represented by ``value``.
178
179        :returns: The updated type
180        :rtype: :class:`~openstack.volume.v3.type.Type`
181        """
182        return self._update(_type.Type, type, **attrs)
183
184    def update_type_extra_specs(self, type, **attrs):
185        """Update the extra_specs for a type
186
187        :param type: The value can be either the ID of a type or a
188                     :class:`~openstack.volume.v3.type.Type` instance.
189        :param dict attrs: The extra_spec attributes to update on the
190                           type represented by ``value``.
191
192        :returns: A dict containing updated extra_specs
193
194        """
195        res = self._get_resource(_type.Type, type)
196        extra_specs = res.set_extra_specs(self, **attrs)
197        result = _type.Type.existing(id=res.id, extra_specs=extra_specs)
198        return result
199
200    def delete_type_extra_specs(self, type, keys):
201        """Delete the extra_specs for a type
202
203        Note: This method will do a HTTP DELETE request for every key in keys.
204
205        :param type: The value can be either the ID of a type or a
206                     :class:`~openstack.volume.v3.type.Type` instance.
207        :param keys: The keys to delete
208
209        :returns: ``None``
210        """
211        res = self._get_resource(_type.Type, type)
212        return res.delete_extra_specs(self, keys)
213
214    def get_type_encryption(self, volume_type_id):
215        """Get the encryption details of a volume type
216
217        :param volume_type_id: The value can be the ID of a type or a
218                               :class:`~openstack.volume.v3.type.Type`
219                               instance.
220
221        :returns: One :class:`~openstack.volume.v3.type.TypeEncryption`
222        :raises: :class:`~openstack.exceptions.ResourceNotFound`
223                 when no resource can be found.
224        """
225        volume_type = self._get_resource(_type.Type, volume_type_id)
226
227        return self._get(_type.TypeEncryption,
228                         volume_type_id=volume_type.id,
229                         requires_id=False)
230
231    def create_type_encryption(self, volume_type, **attrs):
232        """Create new type encryption from attributes
233
234        :param volume_type: The value can be the ID of a type or a
235                            :class:`~openstack.volume.v3.type.Type`
236                            instance.
237
238        :param dict attrs: Keyword arguments which will be used to create
239                           a :class:`~openstack.volume.v3.type.TypeEncryption`,
240                           comprised of the properties on the TypeEncryption
241                           class.
242
243        :returns: The results of type encryption creation
244        :rtype: :class:`~openstack.volume.v3.type.TypeEncryption`
245        """
246        volume_type = self._get_resource(_type.Type, volume_type)
247
248        return self._create(_type.TypeEncryption,
249                            volume_type_id=volume_type.id, **attrs)
250
251    def delete_type_encryption(self, encryption=None,
252                               volume_type=None, ignore_missing=True):
253        """Delete type encryption attributes
254
255        :param encryption: The value can be None or a
256                           :class:`~openstack.volume.v3.type.TypeEncryption`
257                           instance.  If encryption_id is None then
258                           volume_type_id must be specified.
259
260        :param volume_type: The value can be the ID of a type or a
261                            :class:`~openstack.volume.v3.type.Type`
262                            instance.  Required if encryption_id is None.
263
264        :param bool ignore_missing: When set to ``False``
265                    :class:`~openstack.exceptions.ResourceNotFound` will be
266                    raised when the type does not exist.
267                    When set to ``True``, no exception will be set when
268                    attempting to delete a nonexistent type.
269
270        :returns: ``None``
271        """
272
273        if volume_type:
274            volume_type = self._get_resource(_type.Type, volume_type)
275            encryption = self._get(_type.TypeEncryption,
276                                   volume_type=volume_type.id,
277                                   requires_id=False)
278
279        self._delete(_type.TypeEncryption, encryption,
280                     ignore_missing=ignore_missing)
281
282    def update_type_encryption(self, encryption=None,
283                               volume_type=None, **attrs):
284        """Update a type
285        :param encryption: The value can be None or a
286                           :class:`~openstack.volume.v3.type.TypeEncryption`
287                           instance.  If encryption_id is None then
288                           volume_type_id must be specified.
289
290        :param volume_type: The value can be the ID of a type or a
291                            :class:`~openstack.volume.v3.type.Type`
292                            instance.  Required if encryption_id is None.
293        :param dict attrs: The attributes to update on the type encryption.
294
295        :returns: The updated type encryption
296        :rtype: :class:`~openstack.volume.v3.type.TypeEncryption`
297        """
298
299        if volume_type:
300            volume_type = self._get_resource(_type.Type, volume_type)
301            encryption = self._get(_type.TypeEncryption,
302                                   volume_type=volume_type.id,
303                                   requires_id=False)
304
305        return self._update(_type.TypeEncryption, encryption, **attrs)
306
307    def get_volume(self, volume):
308        """Get a single volume
309
310        :param volume: The value can be the ID of a volume or a
311                       :class:`~openstack.volume.v3.volume.Volume` instance.
312
313        :returns: One :class:`~openstack.volume.v3.volume.Volume`
314        :raises: :class:`~openstack.exceptions.ResourceNotFound`
315                 when no resource can be found.
316        """
317        return self._get(_volume.Volume, volume)
318
319    def find_volume(self, name_or_id, ignore_missing=True, **attrs):
320        """Find a single volume
321
322        :param snapshot: The name or ID a volume
323        :param bool ignore_missing: When set to ``False``
324            :class:`~openstack.exceptions.ResourceNotFound` will be raised
325            when the volume does not exist.
326
327        :returns: One :class:`~openstack.volume.v3.volume.Volume`
328        :raises: :class:`~openstack.exceptions.ResourceNotFound`
329                 when no resource can be found.
330        """
331        return self._find(_volume.Volume, name_or_id,
332                          ignore_missing=ignore_missing)
333
334    def volumes(self, details=True, **query):
335        """Retrieve a generator of volumes
336
337        :param bool details: When set to ``False`` no extended attributes
338            will be returned. The default, ``True``, will cause objects with
339            additional attributes to be returned.
340        :param kwargs query: Optional query parameters to be sent to limit
341            the volumes being returned.  Available parameters include:
342
343            * name: Name of the volume as a string.
344            * all_projects: Whether return the volumes in all projects
345            * status: Value of the status of the volume so that you can filter
346                    on "available" for example.
347
348        :returns: A generator of volume objects.
349        """
350        base_path = '/volumes/detail' if details else None
351        return self._list(_volume.Volume, base_path=base_path, **query)
352
353    def create_volume(self, **attrs):
354        """Create a new volume from attributes
355
356        :param dict attrs: Keyword arguments which will be used to create
357                           a :class:`~openstack.volume.v3.volume.Volume`,
358                           comprised of the properties on the Volume class.
359
360        :returns: The results of volume creation
361        :rtype: :class:`~openstack.volume.v3.volume.Volume`
362        """
363        return self._create(_volume.Volume, **attrs)
364
365    def delete_volume(self, volume, ignore_missing=True):
366        """Delete a volume
367
368        :param volume: The value can be either the ID of a volume or a
369                       :class:`~openstack.volume.v3.volume.Volume` instance.
370        :param bool ignore_missing: When set to ``False``
371                    :class:`~openstack.exceptions.ResourceNotFound` will be
372                    raised when the volume does not exist.
373                    When set to ``True``, no exception will be set when
374                    attempting to delete a nonexistent volume.
375
376        :returns: ``None``
377        """
378        self._delete(_volume.Volume, volume, ignore_missing=ignore_missing)
379
380    def extend_volume(self, volume, size):
381        """Extend a volume
382
383        :param volume: The value can be either the ID of a volume or a
384                       :class:`~openstack.volume.v3.volume.Volume` instance.
385        :param size: New volume size
386
387        :returns: None
388        """
389        volume = self._get_resource(_volume.Volume, volume)
390        volume.extend(self, size)
391
392    def set_volume_readonly(self, volume, readonly=True):
393        """Set a volume's read-only flag.
394
395        :param name_or_id: Name, unique ID of the volume or a volume dict.
396        :param bool readonly: Whether the volume should be a read-only volume
397            or not
398
399        :raises: OpenStackCloudTimeout if wait time exceeded.
400        :raises: OpenStackCloudException on operation error.
401        """
402        volume = self._get_resource(_volume.Volume, volume)
403        volume.set_readonly(self, readonly)
404
405    def retype_volume(self, volume, new_type, migration_policy="never"):
406        """Retype the volume.
407
408        :param name_or_id: Name, unique ID of the volume or a volume dict.
409        :param new_type: The new volume type that volume is changed with.
410        :param migration_policy: Specify if the volume should be migrated when
411                                 it is re-typed. Possible values are on-demand
412                                 or never. Default: never.
413
414        :raises: OpenStackCloudTimeout if wait time exceeded.
415        :raises: OpenStackCloudException on operation error.
416        """
417        volume = self._get_resource(_volume.Volume, volume)
418        volume.retype(self, new_type, migration_policy)
419
420    def backend_pools(self, **query):
421        """Returns a generator of cinder Back-end storage pools
422
423        :param kwargs query: Optional query parameters to be sent to limit
424            the resources being returned.
425
426        :returns A generator of cinder Back-end storage pools objects
427        """
428        return self._list(_stats.Pools, **query)
429
430    def backups(self, details=True, **query):
431        """Retrieve a generator of backups
432
433        :param bool details: When set to ``False``
434            no additional details will be returned. The default, ``True``,
435            will cause objects with additional attributes to be returned.
436        :param dict query: Optional query parameters to be sent to limit the
437            resources being returned:
438
439            * offset: pagination marker
440            * limit: pagination limit
441            * sort_key: Sorts by an attribute. A valid value is
442                name, status, container_format, disk_format, size, id,
443                created_at, or updated_at. Default is created_at.
444                The API uses the natural sorting direction of the
445                sort_key attribute value.
446            * sort_dir: Sorts by one or more sets of attribute and sort
447                direction combinations. If you omit the sort direction
448                in a set, default is desc.
449            * project_id: Project ID to query backups for.
450
451        :returns: A generator of backup objects.
452        """
453        base_path = '/backups/detail' if details else None
454        return self._list(_backup.Backup, base_path=base_path, **query)
455
456    def get_backup(self, backup):
457        """Get a backup
458
459        :param backup: The value can be the ID of a backup
460            or a :class:`~openstack.block_storage.v3.backup.Backup`
461            instance.
462
463        :returns: Backup instance
464        :rtype: :class:`~openstack.block_storage.v3.backup.Backup`
465        """
466        return self._get(_backup.Backup, backup)
467
468    def find_backup(self, name_or_id, ignore_missing=True, **attrs):
469        """Find a single backup
470
471        :param snapshot: The name or ID a backup
472        :param bool ignore_missing: When set to ``False``
473            :class:`~openstack.exceptions.ResourceNotFound` will be raised
474            when the backup does not exist.
475
476        :returns: One :class:`~openstack.volume.v3.backup.Backup`
477        :raises: :class:`~openstack.exceptions.ResourceNotFound`
478                 when no resource can be found.
479        """
480        return self._find(_backup.Backup, name_or_id,
481                          ignore_missing=ignore_missing)
482
483    def create_backup(self, **attrs):
484        """Create a new Backup from attributes with native API
485
486        :param dict attrs: Keyword arguments which will be used to create
487            a :class:`~openstack.block_storage.v3.backup.Backup`
488            comprised of the properties on the Backup class.
489
490        :returns: The results of Backup creation
491        :rtype: :class:`~openstack.block_storage.v3.backup.Backup`
492        """
493        return self._create(_backup.Backup, **attrs)
494
495    def delete_backup(self, backup, ignore_missing=True):
496        """Delete a CloudBackup
497
498        :param backup: The value can be the ID of a backup or a
499            :class:`~openstack.block_storage.v3.backup.Backup` instance
500        :param bool ignore_missing: When set to ``False``
501            :class:`~openstack.exceptions.ResourceNotFound` will be raised when
502            the zone does not exist.
503            When set to ``True``, no exception will be set when attempting to
504            delete a nonexistent zone.
505
506        :returns: ``None``
507        """
508        self._delete(_backup.Backup, backup,
509                     ignore_missing=ignore_missing)
510
511    def restore_backup(self, backup, volume_id=None, name=None):
512        """Restore a Backup to volume
513
514        :param backup: The value can be the ID of a backup or a
515            :class:`~openstack.block_storage.v3.backup.Backup` instance
516        :param volume_id: The ID of the volume to restore the backup to.
517        :param name: The name for new volume creation to restore.
518
519        :returns: Updated backup instance
520        :rtype: :class:`~openstack.block_storage.v3.backup.Backup`
521        """
522        backup = self._get_resource(_backup.Backup, backup)
523        return backup.restore(self, volume_id=volume_id, name=name)
524
525    def get_limits(self):
526        """Retrieves limits
527
528        :returns: A Limit object, including both
529            :class:`~openstack.block_storage.v3.limits.AbsoluteLimit` and
530            :class:`~openstack.block_storage.v3.limits.RateLimit`
531        :rtype: :class:`~openstack.block_storage.v3.limits.Limit`
532        """
533        return self._get(_limits.Limit, requires_id=False)
534
535    def get_capabilities(self, host):
536        """Get a backend's capabilites
537
538        :param host: Specified backend to obtain volume stats and properties.
539
540        :returns: One :class:
541            `~openstack.block_storage.v3.capabilites.Capabilities` instance.
542        :raises: :class:`~openstack.exceptions.ResourceNotFound` when no
543            resource can be found.
544        """
545        return self._get(_capabilities.Capabilities, host)
546
547    def availability_zones(self):
548        """Return a generator of availability zones
549
550        :returns: A generator of availability zone
551        :rtype: :class:`~openstack.block_storage.v3.availability_zone.\
552                        AvailabilityZone`
553        """
554
555        return self._list(availability_zone.AvailabilityZone)
556
557    def get_group_type(self, group_type):
558        """Get a specific group type
559
560        :param group_type: The value can be the ID of a group type
561            or a :class:`~openstack.block_storage.v3.group_type.GroupType`
562            instance.
563
564        :returns: One :class:
565            `~openstack.block_storage.v3.group_type.GroupType` instance.
566        :raises: :class:`~openstack.exceptions.ResourceNotFound` when no
567            resource can be found.
568        """
569        return self._get(_group_type.GroupType, group_type)
570
571    def find_group_type(self, name_or_id, ignore_missing=True):
572        """Find a single group type
573
574        :param name_or_id: The name or ID of a group type.
575        :param bool ignore_missing: When set to ``False``
576            :class:`~openstack.exceptions.ResourceNotFound` will be raised
577            when the group type does not exist.
578
579        :returns: One :class:`~openstack.block_storage.v3.group_type
580            .GroupType'
581        :raises: :class:`~openstack.exceptions.ResourceNotFound`
582            when no resource can be found.
583        """
584        return self._find(
585            _group_type.GroupType, name_or_id, ignore_missing=ignore_missing)
586
587    def group_types(self, **query):
588        """Retrive a generator of group types
589
590        :param dict query: Optional query parameters to be sent to limit the
591            resources being returned:
592
593            * sort: Comma-separated list of sort keys and optional sort
594                directions in the form of <key> [:<direction>]. A valid
595                direction is asc (ascending) or desc (descending).
596            * limit: Requests a page size of items. Returns a number of items
597                up to a limit value. Use the limit parameter to make an
598                initial limited request and use the ID of the last-seen item
599                from the response as the marker parameter value in a
600                subsequent limited request.
601            * offset: Used in conjunction with limit to return a slice of
602                items. Is where to start in the list.
603            * marker: The ID of the last-seen item.
604
605        :returns: A generator of group type objects.
606        """
607        return self._list(_group_type.GroupType, **query)
608
609    def create_group_type(self, **attrs):
610        """Create a group type
611
612        :param dict attrs: Keyword arguments which will be used to create
613            a :class:`~openstack.block_storage.v3.group_type.GroupType'
614            comprised of the properties on the GroupType class.
615
616        :returns: The results of group type creation.
617        :rtype: :class:`~openstack.block_storage.v3.group_type.GroupTye'.
618        """
619        return self._create(_group_type.GroupType, **attrs)
620
621    def delete_group_type(self, group_type, ignore_missing=True):
622        """Delete a group type
623
624        :param group_type: The value can be the ID of a group type
625            or a :class:`~openstack.block_storage.v3.group_type.GroupType`
626            instance.
627        :param bool ignore_missing: When set to ``False``
628            :class:`~openstack.exceptions.ResourceNotFound` will be raised when
629            the zone does not exist.
630            When set to ``True``, no exception will be set when attempting to
631            delete a nonexistent zone.
632
633        :returns: ''None''
634        """
635        self._delete(
636            _group_type.GroupType, group_type, ignore_missing=ignore_missing)
637
638    def update_group_type(self, group_type, **attrs):
639        """Update a group_type
640
641        :param group_type: The value can be the ID of a group type or a
642            :class:`~openstack.block_storage.v3.group_type.GroupType`
643            instance.
644        :param dict attrs: The attributes to update on the group type.
645
646        :returns: The updated group type.
647        :rtype: :class:`~openstack.block_storage.v3.group_type.GroupType`
648        """
649        return self._update(
650            _group_type.GroupType, group_type, **attrs)
651
652    def wait_for_status(self, res, status='ACTIVE', failures=None,
653                        interval=2, wait=120):
654        """Wait for a resource to be in a particular status.
655
656        :param res: The resource to wait on to reach the specified status.
657                    The resource must have a ``status`` attribute.
658        :type resource: A :class:`~openstack.resource.Resource` object.
659        :param status: Desired status.
660        :param failures: Statuses that would be interpreted as failures.
661        :type failures: :py:class:`list`
662        :param interval: Number of seconds to wait before to consecutive
663                         checks. Default to 2.
664        :param wait: Maximum number of seconds to wait before the change.
665                     Default to 120.
666        :returns: The resource is returned on success.
667        :raises: :class:`~openstack.exceptions.ResourceTimeout` if transition
668                 to the desired status failed to occur in specified seconds.
669        :raises: :class:`~openstack.exceptions.ResourceFailure` if the resource
670                 has transited to one of the failure statuses.
671        :raises: :class:`~AttributeError` if the resource does not have a
672                ``status`` attribute.
673        """
674        failures = ['Error'] if failures is None else failures
675        return resource.wait_for_status(
676            self, res, status, failures, interval, wait)
677
678    def wait_for_delete(self, res, interval=2, wait=120):
679        """Wait for a resource to be deleted.
680
681        :param res: The resource to wait on to be deleted.
682        :type resource: A :class:`~openstack.resource.Resource` object.
683        :param interval: Number of seconds to wait before to consecutive
684                         checks. Default to 2.
685        :param wait: Maximum number of seconds to wait before the change.
686                     Default to 120.
687        :returns: The resource is returned on success.
688        :raises: :class:`~openstack.exceptions.ResourceTimeout` if transition
689                 to delete failed to occur in the specified seconds.
690        """
691        return resource.wait_for_delete(self, res, interval, wait)
692
693    def resource_filters(self, **query):
694        """Retrieve a generator of resource filters
695
696        :returns: A generator of resource filters.
697        """
698        return self._list(_resource_filter.ResourceFilter, **query)
699
700    def extensions(self):
701        """Return a generator of extensions
702
703        :returns: A generator of extension
704        :rtype: :class:`~openstack.block_storage.v3.extension.\
705                        Extension`
706        """
707        return self._list(_extension.Extension)
708
709    def _get_cleanup_dependencies(self):
710        return {
711            'block_storage': {
712                'before': []
713            }
714        }
715
716    def _service_cleanup(self, dry_run=True, client_status_queue=None,
717                         identified_resources=None,
718                         filters=None, resource_evaluation_fn=None):
719        backups = []
720        for obj in self.backups(details=False):
721            need_delete = self._service_cleanup_del_res(
722                self.delete_backup,
723                obj,
724                dry_run=dry_run,
725                client_status_queue=client_status_queue,
726                identified_resources=identified_resources,
727                filters=filters,
728                resource_evaluation_fn=resource_evaluation_fn)
729            if not dry_run and need_delete:
730                backups.append(obj)
731
732        # Before deleting snapshots need to wait for backups to be deleted
733        for obj in backups:
734            try:
735                self.wait_for_delete(obj)
736            except exceptions.SDKException:
737                # Well, did our best, still try further
738                pass
739
740        snapshots = []
741        for obj in self.snapshots(details=False):
742            need_delete = self._service_cleanup_del_res(
743                self.delete_snapshot,
744                obj,
745                dry_run=dry_run,
746                client_status_queue=client_status_queue,
747                identified_resources=identified_resources,
748                filters=filters,
749                resource_evaluation_fn=resource_evaluation_fn)
750            if not dry_run and need_delete:
751                snapshots.append(obj)
752
753        # Before deleting volumes need to wait for snapshots to be deleted
754        for obj in snapshots:
755            try:
756                self.wait_for_delete(obj)
757            except exceptions.SDKException:
758                # Well, did our best, still try further
759                pass
760
761        for obj in self.volumes(details=True):
762            self._service_cleanup_del_res(
763                self.delete_volume,
764                obj,
765                dry_run=dry_run,
766                client_status_queue=client_status_queue,
767                identified_resources=identified_resources,
768                filters=filters,
769                resource_evaluation_fn=resource_evaluation_fn)
770