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.
12from openstack import exceptions
13from openstack import resource
14from openstack import utils
15
16
17class Backup(resource.Resource):
18    """Volume Backup"""
19    resource_key = "backup"
20    resources_key = "backups"
21    base_path = "/backups"
22
23    # TODO(gtema): Starting from ~3.31(3.45) Cinder seems to support also fuzzy
24    # search (name~, status~, volume_id~). But this is not documented
25    # officially and seem to require microversion be set
26    _query_mapping = resource.QueryParameters(
27        'all_tenants', 'limit', 'marker', 'project_id',
28        'name', 'status', 'volume_id',
29        'sort_key', 'sort_dir')
30
31    # capabilities
32    allow_fetch = True
33    allow_create = True
34    allow_delete = True
35    allow_list = True
36    allow_get = True
37
38    #: Properties
39    #: backup availability zone
40    availability_zone = resource.Body("availability_zone")
41    #: The container backup in
42    container = resource.Body("container")
43    #: The date and time when the resource was created.
44    created_at = resource.Body("created_at")
45    #: data timestamp
46    #: The time when the data on the volume was first saved.
47    #: If it is a backup from volume, it will be the same as created_at
48    #: for a backup. If it is a backup from a snapshot,
49    #: it will be the same as created_at for the snapshot.
50    data_timestamp = resource.Body('data_timestamp')
51    #: backup description
52    description = resource.Body("description")
53    #: Backup fail reason
54    fail_reason = resource.Body("fail_reason")
55    #: Force backup
56    force = resource.Body("force", type=bool)
57    #: has_dependent_backups
58    #: If this value is true, there are other backups depending on this backup.
59    has_dependent_backups = resource.Body('has_dependent_backups', type=bool)
60    #: Indicates whether the backup mode is incremental.
61    #: If this value is true, the backup mode is incremental.
62    #: If this value is false, the backup mode is full.
63    is_incremental = resource.Body("is_incremental", type=bool)
64    #: A list of links associated with this volume. *Type: list*
65    links = resource.Body("links", type=list)
66    #: The backup metadata. New in version 3.43
67    metadata = resource.Body('metadata', type=dict)
68    #: backup name
69    name = resource.Body("name")
70    #: backup object count
71    object_count = resource.Body("object_count", type=int)
72    #: The UUID of the owning project.
73    #: New in version 3.18
74    project_id = resource.Body('os-backup-project-attr:project_id')
75    #: The size of the volume, in gibibytes (GiB).
76    size = resource.Body("size", type=int)
77    #: The UUID of the source volume snapshot.
78    snapshot_id = resource.Body("snapshot_id")
79    #: backup status
80    #: values: creating, available, deleting, error, restoring, error_restoring
81    status = resource.Body("status")
82    #: The date and time when the resource was updated.
83    updated_at = resource.Body("updated_at")
84    #: The UUID of the project owner. New in 3.56
85    user_id = resource.Body('user_id')
86    #: The UUID of the volume.
87    volume_id = resource.Body("volume_id")
88
89    def create(self, session, prepend_key=True, base_path=None, **params):
90        """Create a remote resource based on this instance.
91
92        :param session: The session to use for making this request.
93        :type session: :class:`~keystoneauth1.adapter.Adapter`
94        :param prepend_key: A boolean indicating whether the resource_key
95                            should be prepended in a resource creation
96                            request. Default to True.
97        :param str base_path: Base part of the URI for creating resources, if
98                              different from
99                              :data:`~openstack.resource.Resource.base_path`.
100        :param dict params: Additional params to pass.
101        :return: This :class:`Resource` instance.
102        :raises: :exc:`~openstack.exceptions.MethodNotSupported` if
103                 :data:`Resource.allow_create` is not set to ``True``.
104        """
105        if not self.allow_create:
106            raise exceptions.MethodNotSupported(self, "create")
107
108        session = self._get_session(session)
109        microversion = self._get_microversion_for(session, 'create')
110        requires_id = (self.create_requires_id
111                       if self.create_requires_id is not None
112                       else self.create_method == 'PUT')
113
114        if self.create_exclude_id_from_body:
115            self._body._dirty.discard("id")
116
117        if self.create_method == 'POST':
118            request = self._prepare_request(requires_id=requires_id,
119                                            prepend_key=prepend_key,
120                                            base_path=base_path)
121            # NOTE(gtema) this is a funny example of when attribute
122            # is called "incremental" on create, "is_incremental" on get
123            # and use of "alias" or "aka" is not working for such conflict,
124            # since our preferred attr name is exactly "is_incremental"
125            body = request.body
126            if 'is_incremental' in body['backup']:
127                body['backup']['incremental'] = \
128                    body['backup'].pop('is_incremental')
129            response = session.post(request.url,
130                                    json=request.body, headers=request.headers,
131                                    microversion=microversion, params=params)
132        else:
133            # Just for safety of the implementation (since PUT removed)
134            raise exceptions.ResourceFailure(
135                msg="Invalid create method: %s" % self.create_method)
136
137        has_body = (self.has_body if self.create_returns_body is None
138                    else self.create_returns_body)
139        self.microversion = microversion
140        self._translate_response(response, has_body=has_body)
141        # direct comparision to False since we need to rule out None
142        if self.has_body and self.create_returns_body is False:
143            # fetch the body if it's required but not returned by create
144            return self.fetch(session)
145        return self
146
147    def restore(self, session, volume_id=None, name=None):
148        """Restore current backup to volume
149
150        :param session: openstack session
151        :param volume_id: The ID of the volume to restore the backup to.
152        :param name: The name for new volume creation to restore.
153        :return: Updated backup instance
154        """
155        url = utils.urljoin(self.base_path, self.id, "restore")
156        body = {'restore': {}}
157        if volume_id:
158            body['restore']['volume_id'] = volume_id
159        if name:
160            body['restore']['name'] = name
161        if not (volume_id or name):
162            raise exceptions.SDKException('Either of `name` or `volume_id`'
163                                          ' must be specified.')
164        response = session.post(url,
165                                json=body)
166        self._translate_response(response, has_body=False)
167        return self
168
169
170BackupDetail = Backup
171