Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / volume / views.py @ 0a83201b

History | View | Annotate | Download (12.1 kB)

1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from itertools import ifilter
35
from logging import getLogger
36
from django.http import HttpResponse
37
from django.utils import simplejson as json
38

    
39
import datetime
40
from dateutil.parser import parse as date_parse
41

    
42
from snf_django.lib import api
43
from snf_django.lib.api import faults, utils
44

    
45
from synnefo.volume import volumes, snapshots, util
46
from synnefo.db.models import Volume
47
from synnefo.plankton.utils import image_backend
48
log = getLogger('synnefo.volume')
49

    
50

    
51
def display_null_field(field):
52
    if field is None:
53
        return None
54
    else:
55
        str(field)
56

    
57

    
58
def volume_to_dict(volume, detail=True):
59
    data = {
60
        "id": str(volume.id),
61
        "name": display_null_field(volume.name),
62
        # TODO: Links!
63
        "links": "",
64
    }
65
    if detail:
66
        details = {
67
            "status": volume.status.lower(),
68
            "size": volume.size,
69
            "description": volume.description,
70
            "created_at": utils.isoformat(volume.created),
71
            "metadata": dict((m.key, m.value) for m in volume.metadata.all()),
72
            "snapshot_id": display_null_field(volume.source_snapshot_id),
73
            "source_volid": display_null_field(volume.source_volume_id),
74
            "image_id": display_null_field(volume.source_image_id),
75
            "attachments": get_volume_attachments(volume),
76
            # TODO:
77
            "volume_type": None,
78
            #"availabilit_zone": None,
79
            #"bootable": None,
80
            #"os-vol-tenant-attr:tenant_id": None,
81
            #"os-vol-host-attr:host": None,
82
            #"os-vol-mig-status-attr:name_id": None,
83
            #"os-vol-mig-status-attr:migstat": None,
84
        }
85
        data.update(details)
86
    return data
87

    
88

    
89
def get_volume_attachments(volume):
90
    if volume.machine_id is None:
91
        return []
92
    else:
93
        return [{"server_id": volume.machine_id,
94
                 "volume_id": volume.id,
95
                 "device_index": volume.index}]
96

    
97

    
98
@api.api_method(http_method="POST", user_required=True, logger=log)
99
def create_volume(request):
100
    """Create a new Volume."""
101

    
102
    req = utils.get_request_dict(request)
103
    log.debug("create_volume %s", req)
104

    
105
    user_id = request.user_uniq
106
    # Get and validate 'name' parameter
107
    # TODO: auto generate name
108
    name = req.get("name", None)
109
    if name is None:
110
        raise faults.BadRequest("Volume 'name' is needed.")
111
    # Get and validate 'size' parameter
112
    size = req.get("size")
113
    if size is None:
114
        raise faults.BadRequest("Volume 'size' is needed.")
115
    try:
116
        size = int(size)
117
        if size <= 0:
118
            raise ValueError
119
    except ValueError:
120
        raise faults.BadRequest("Volume 'size' needs to be a positive integer"
121
                                " value. '%s' cannot be accepted." % size)
122

    
123
    # TODO: Fix volume type, validate, etc..
124
    volume_type = req.get("volume_type", None)
125

    
126
    # Optional parameters
127
    description = req.get("description", "")
128
    metadata = req.get("metadata", {})
129
    if not isinstance(metadata, dict):
130
        msg = "Volume 'metadata' needs to be a dictionary of key-value pairs."\
131
              " '%s' can not be accepted." % metadata
132
        raise faults.BadRequest(msg)
133

    
134
    # Id of the volume to clone from
135
    source_volume_id = req.get("source_volid")
136
    # Id of the snapshot to create the volume from
137
    source_snapshot_id = req.get("snapshot_id")
138
    # Reference to an Image stored in Glance
139
    source_image_id = req.get("imageRef")
140
    # TODO: Check that not all of them are used
141

    
142
    server_id = req.get("server_id")
143
    if server_id is None:
144
        raise faults.BadRequest("Attribute 'server_id' is mandatory")
145

    
146
    # Create the volume
147
    volume = volumes.create(user_id=user_id, size=size, name=name,
148
                            source_volume_id=source_volume_id,
149
                            source_snapshot_id=source_snapshot_id,
150
                            source_image_id=source_image_id,
151
                            volume_type=volume_type, description=description,
152
                            metadata=metadata, server_id=server_id)
153

    
154
    # Render response
155
    data = json.dumps(dict(volume=volume_to_dict(volume, detail=False)))
156
    return HttpResponse(data, status=200)
157

    
158

    
159
@api.api_method(http_method="GET", user_required=True, logger=log)
160
def list_volumes(request, detail=False):
161
    log.debug('list_volumes detail=%s', detail)
162
    volumes = Volume.objects.filter(userid=request.user_uniq)
163

    
164
    since = utils.isoparse(request.GET.get('changes-since'))
165
    if since:
166
        volumes = volumes.filter(updated__gte=since)
167
        if not volumes:
168
            return HttpResponse(status=304)
169
    else:
170
        volumes = volumes.filter(deleted=False)
171

    
172
    volumes = [volume_to_dict(v, detail) for v in volumes.order_by("id")]
173

    
174
    data = json.dumps({'volumes': volumes})
175
    return HttpResponse(data, content_type="application/json", status=200)
176

    
177

    
178
@api.api_method(http_method="DELETE", user_required=True, logger=log)
179
def delete_volume(request, volume_id):
180
    log.debug("delete_volume volume_id: %s", volume_id)
181

    
182
    volume = util.get.volume(request.user_uniq, volume_id, for_update=True)
183
    volumes.delete(volume)
184

    
185
    return HttpResponse(status=202)
186

    
187

    
188
@api.api_method(http_method="GET", user_required=True, logger=log)
189
def get_volume(request, volume_id):
190
    log.debug('get_volume volume_id: %s', volume_id)
191

    
192
    volume = util.get.volume(request.user_uniq, volume_id)
193

    
194
    data = json.dumps({'volume': volume_to_dict(volume, detail=True)})
195
    return HttpResponse(data, content_type="application/json", status=200)
196

    
197

    
198
@api.api_method(http_method="PUT", user_required=True, logger=log)
199
def update_volume(request, volume_id):
200
    req = utils.get_request_dict(request)
201
    log.debug('update_volume volume_id: %s, request: %s', volume_id, req)
202

    
203
    volume = util.get.volume(request.user_uniq, volume_id, for_update=True)
204

    
205
    new_name = req.get("name")
206
    description = req.get("description")
207

    
208
    if new_name is None and description is None:
209
        raise faults.BadRequest("Nothing to update.")
210

    
211
    if new_name is not None:
212
        volume = volumes.rename(volume, new_name)
213
    if description is not None:
214
        volume = volumes.update_description(volume, description)
215
    data = json.dumps({'volume': volume_to_dict(volume, detail=True)})
216
    return HttpResponse(data, content_type="application/json", status=200)
217

    
218

    
219
def snapshot_to_dict(snapshot, detail=True):
220
    owner = snapshot["owner"]
221
    status = snapshot["status"]
222
    progress = snapshot["progress"]
223
    data = {
224
        "id": snapshot["uuid"],
225
        "size": int(snapshot["size"]) >> 30,  # gigabytes
226
        "name": snapshot["name"],
227
        "description": snapshot["description"],
228
        "status": status,
229
        "user_id": owner,
230
        "tenant_id": owner,
231
        "os-extended-snapshot-attribute:progress": progress,
232
        #"os-extended-snapshot-attribute:project_id": project,
233
        "created_at": utils.isoformat(date_parse(snapshot["created_at"])),
234
        "metadata": snapshot.get("metadata", {}),
235
        "volume_id": snapshot.get("volume_id"),
236
        "links": "",  # TODO fix links
237
    }
238
    return data
239

    
240

    
241
@api.api_method(http_method="POST", user_required=True, logger=log)
242
def create_snapshot(request):
243
    """Create a new Snapshot."""
244

    
245
    req = utils.get_request_dict(request)
246
    log.debug("create_snapshot %s", req)
247

    
248
    user_id = request.user_uniq
249
    # Get and validate 'name' parameter
250
    # TODO: auto generate name
251
    metadata = req.get("metadata", {})
252
    if not isinstance(metadata, dict):
253
        msg = "Snapshot 'metadata' needs to be a dictionary of key-value"\
254
              " pairs. '%s' can not be accepted." % metadata
255
        raise faults.BadRequest(msg)
256

    
257
    volume_id = req.get("volume_id", None)
258
    if volume_id is None:
259
        raise faults.BadRequest("'volume_id' attribute is missing.")
260
    volume = util.get_volume(user_id, volume_id, for_update=True,
261
                             exception=faults.BadRequest)
262

    
263
    name = req.get("name", None)
264
    if name is None:
265
        name = "snapshot_volume_%s_%s" %\
266
            (volume.id, str(datetime.datetime.now()))
267
    description = req.get("description", "")
268

    
269
    # TODO: What to do with force ?
270
    force = req.get("force", False)
271
    if not isinstance(force, bool):
272
        raise faults.BadRequest("Invalid value for 'force' attribute.")
273

    
274
    snapshot = snapshots.create(user_id=user_id, volume=volume, name=name,
275
                                description=description, metadata=metadata,
276
                                force=force)
277

    
278
    # Render response
279
    data = json.dumps(dict(snapshot=snapshot_to_dict(snapshot, detail=False)))
280
    return HttpResponse(data, status=200)  # TOO: Maybe 202 ?
281

    
282

    
283
@api.api_method(http_method="GET", user_required=True, logger=log)
284
def list_snapshots(request, detail=False):
285
    log.debug('list_snapshots detail=%s', detail)
286
    since = utils.isoparse(request.GET.get('changes-since'))
287
    with image_backend(request.user_uniq) as backend:
288
        snapshots = backend.list_snapshots()
289
        if since:
290
            updated_since = lambda snap:\
291
                date_parse(snap["updated_at"]) >= since
292
            snapshots = ifilter(updated_since, snapshots)
293
            if not snapshots:
294
                return HttpResponse(status=304)
295

    
296
    snapshots = sorted(snapshots, key=lambda x: x['id'])
297
    snapshots_dict = [snapshot_to_dict(snapshot, detail)
298
                      for snapshot in snapshots]
299

    
300
    data = json.dumps(dict(snapshots=snapshots_dict))
301

    
302
    return HttpResponse(data, status=200)
303

    
304

    
305
@api.api_method(http_method="DELETE", user_required=True, logger=log)
306
def delete_snapshot(request, snapshot_id):
307
    log.debug("delete_snapshot snapshot_id: %s", snapshot_id)
308

    
309
    snapshot = util.get_snapshot(request.user_uniq, snapshot_id)
310
    snapshots.delete(snapshot)
311

    
312
    return HttpResponse(status=202)
313

    
314

    
315
@api.api_method(http_method="GET", user_required=True, logger=log)
316
def get_snapshot(request, snapshot_id):
317
    log.debug('get_snapshot snapshot_id: %s', snapshot_id)
318

    
319
    snapshot = util.get_snapshot(request.user_uniq, snapshot_id)
320
    data = json.dumps({'snapshot': snapshot_to_dict(snapshot, detail=True)})
321
    return HttpResponse(data, content_type="application/json", status=200)
322

    
323

    
324
@api.api_method(http_method="PUT", user_required=True, logger=log)
325
def update_snapshot(request, snapshot_id):
326
    req = utils.get_request_dict(request)
327
    log.debug('update_snapshot snapshot_id: %s, request: %s', snapshot_id, req)
328
    snapshot = util.get_snapshot(request.user_uniq, snapshot_id)
329
    # TODO
330
    #snapshot.name = req.get("name", snapshot.name)
331
    #snapshot.description = req.get("description", snapshot.description)
332
    #snapshot.save()
333
    data = json.dumps({'snapshot': snapshot_to_dict(snapshot, detail=True)})
334
    return HttpResponse(data, content_type="application/json", status=200)