Revision 18ca395d

b/snf-cyclades-app/synnefo/volume/snapshots.py
1
import datetime
1
import logging
2 2
from django.utils import simplejson as json
3 3
from django.db import transaction
4 4
from snf_django.lib.api import faults
5
from snf_django.lib.api.utils import isoformat
6 5
from synnefo.plankton.utils import image_backend
7 6
from synnefo.logic import backend
8 7
from synnefo.volume import util
9 8

  
9
#import datetime
10
#from snf_django.lib.api.utils import isoformat
11

  
12
log = logging.getLogger(__name__)
13

  
10 14
SNAPSHOTS_CONTAINER = "snapshots"
11 15
SNAPSHOTS_DOMAIN = "plankton"
12 16
SNAPSHOTS_PREFIX = "plankton:"
......
16 20

  
17 21
@transaction.commit_on_success
18 22
def create(user_id, volume, name, description, metadata, force=False):
23
    """Create a snapshot from a given volume
24

  
25
    Create a snapshot from a given volume. The snapshot is first created as
26
    a file in Pithos, with specified metadata to indicate that it is a
27
    snapshot. Then a job is sent to Ganeti backend to create the actual
28
    snapshot of the volume.
29

  
30
    Snapshots are only supported for volumes of ext_ disk template. Also,
31
    the volume must be attached to some server.
19 32

  
33
    """
34

  
35
    # Check that taking a snapshot is feasible
20 36
    if volume.machine is None:
21
        raise faults.BadRequest("Can not snapshot detached volume!")
37
        raise faults.BadRequest("Cannot snapshot a detached volume!")
22 38

  
23 39
    flavor = volume.machine.flavor
24 40
    if not flavor.disk_template.startswith("ext_"):
......
26 42
               " disk template")
27 43
        raise faults.BadRequest(msg)
28 44

  
45
    # Increase the snapshot counter of the volume that is used in order to
46
    # generate unique snapshot names
29 47
    volume.snapshot_counter += 1
30 48
    volume.save()
31 49
    transaction.commit()
32 50

  
33
    snapshot_metadata = {}
34
    snapshot_metadata[SNAPSHOTS_PREFIX + "name"] = name
35
    snapshot_metadata[SNAPSHOTS_PREFIX + "description"] = description
36
    snapshot_metadata[SNAPSHOTS_PREFIX + "metadata"] = json.dumps(metadata)
37
    snapshot_metadata[SNAPSHOTS_PREFIX + "volume_id"] = volume.id
38
    snapshot_metadata[SNAPSHOTS_PREFIX + "status"] = "CREATING"
39
    #XXX: just to work
51
    # Snapshot information are stored as metadata on the Pithos file
52
    snapshot_metadata = {
53
        SNAPSHOTS_PREFIX + "name": name,
54
        SNAPSHOTS_PREFIX + "description": description,
55
        SNAPSHOTS_PREFIX + "volume_id": volume.id,
56
        SNAPSHOTS_PREFIX + "status": "CREATING",
57
    }
58

  
59
    # TODO: The following are used in order plankton to work with snapshots
60
    # exactly as with iamges
61
    snapshot_metadata.update({
62
        SNAPSHOTS_PREFIX + "store": "pithos",
63
        SNAPSHOTS_PREFIX + "disk_format": "diskdump",
64
        SNAPSHOTS_PREFIX + "default_container_format": "bare",
65
        SNAPSHOTS_PREFIX + "metadata": json.dumps(metadata)})
66

  
67
    # Set a special attribute to distinquish snapshots from the images
40 68
    snapshot_metadata[SNAPSHOTS_PREFIX + "is_snapshot"] = True
41
    #XXX: for images
42
    snapshot_metadata[SNAPSHOTS_PREFIX + "store"] = "pithos"
43
    snapshot_metadata[SNAPSHOTS_PREFIX + "disk_format"] = "diskdump"
44
    snapshot_metadata[SNAPSHOTS_PREFIX + "default_container_format"] = "bare"
45
    # XXX: Hack-ish way to clone the metadata
69

  
70
    # Snapshots are used as images. We set the most important properties
71
    # that are being used for images. We set 'EXCLUDE_ALL_TASKS' to bypass
72
    # image customization. Also, we get some basic metadata for the volume from
73
    # the server that the volume is attached
46 74
    image_properties = {"EXCLUDE_ALL_TASKS": "yes",
47 75
                        "description": description}
48
    vm_metadata = dict(volume.machine.metadata.values_list("meta_key", "meta_value"))
76
    vm_metadata = dict(volume.machine.metadata
77
                                     .values_list("meta_key", "meta_value"))
49 78
    for key in ["OS", "users"]:
50 79
        val = vm_metadata.get(key)
51 80
        if val is not None:
52 81
            image_properties[key] = val
53
    snapshot_metadata[SNAPSHOTS_PREFIX + "properties"] = json.dumps(image_properties)
82
    snapshot_metadata[SNAPSHOTS_PREFIX + "properties"] = \
83
        json.dumps(image_properties)
54 84

  
55
    snapshot_name = generate_snapshot_name(volume)
56
    mapfile = SNAPSHOTS_MAPFILE_PREFIX + snapshot_name
85
    # Generate a name for the Pithos file. Also, generate a name for the
86
    # Archipelago mapfile.
87
    snapshot_pithos_name = generate_snapshot_pithos_name(volume)
88
    mapfile = SNAPSHOTS_MAPFILE_PREFIX + snapshot_pithos_name
57 89

  
90
    # Convert size from Gbytes to bytes
58 91
    size = volume.size << 30
92

  
59 93
    with image_backend(user_id) as pithos_backend:
60 94
        # move this to plankton backend
61 95
        snapshot_uuid = pithos_backend.backend.register_object_map(
62 96
            user=user_id,
63 97
            account=user_id,
64 98
            container=SNAPSHOTS_CONTAINER,
65
            name=snapshot_name,
99
            name=snapshot_pithos_name,
66 100
            size=size,
67 101
            domain=SNAPSHOTS_DOMAIN,
68 102
            type=SNAPSHOTS_TYPE,
......
72 106
            permissions=None)
73 107
            #checksum=None,
74 108

  
75
    backend.snapshot_instance(volume.machine, snapshot_name=snapshot_name)
109
    backend.snapshot_instance(volume.machine,
110
                              snapshot_name=snapshot_pithos_name)
76 111

  
77 112
    snapshot = util.get_snapshot(user_id, snapshot_uuid)
78 113

  
79 114
    return snapshot
80 115

  
81 116

  
82
def generate_snapshot_name(volume):
83
    time = isoformat(datetime.datetime.now())
84
    return "snf-snapshot-of-volume-%s-%s" % (volume.id,
85
                                                volume.snapshot_counter)
117
def generate_snapshot_pithos_name(volume):
118
    """Helper function to generate a name for the Pithos file."""
119
    # time = isoformat(datetime.datetime.now())
120
    return "snapshot-of-volume-%s-%s" % (volume.id,
121
                                         volume.snapshot_counter)
86 122

  
87 123

  
88 124
@transaction.commit_on_success
89 125
def delete(snapshot):
126
    """Delete a snapshot.
127

  
128
    Delete a snapshot by deleting the corresponding file from Pithos.
129

  
130
    """
90 131
    user_id = snapshot["owner"]
132
    log.info("Deleting snapshot '%s'", snapshot["location"])
91 133
    with image_backend(user_id) as pithos_backend:
92 134
        pithos_backend.delete_snapshot(snapshot["uuid"])
93 135
    return snapshot
136

  
137

  
138
def rename(snapshot, new_name):
139
    # user_id = snapshot["owner"]
140
    raise NotImplemented("Renaming a snapshot is not implemented!")
141

  
142

  
143
def update_description(snapshot, new_description):
144
    # user_id = snapshot["owner"]
145
    raise NotImplemented("Updating snapshot's description is not implemented!")
b/snf-cyclades-app/synnefo/volume/views.py
222 222
    data = {
223 223
        "id": snapshot["uuid"],
224 224
        "size": int(snapshot["size"]) >> 30,  # gigabytes
225
        "name": snapshot["name"],
226
        "description": snapshot["description"],
225
        "display_name": snapshot["name"],
226
        "display_description": snapshot["description"],
227 227
        "status": status,
228 228
        "user_id": owner,
229 229
        "tenant_id": owner,
......
232 232
        "created_at": utils.isoformat(date_parse(snapshot["created_at"])),
233 233
        "metadata": snapshot.get("metadata", {}),
234 234
        "volume_id": snapshot.get("volume_id"),
235
        "links": "",  # TODO fix links
235
        "links": util.snapshot_to_links(snapshot["uuid"])
236 236
    }
237 237
    return data
238 238

  
......
263 263
    volume = util.get_volume(user_id, volume_id, for_update=True,
264 264
                             exception=faults.BadRequest)
265 265

  
266
    name = new_snapshot.get("name", None)
266
    name = new_snapshot.get("display_name", None)
267 267
    if name is None:
268 268
        name = "snapshot_volume_%s_%s" %\
269 269
            (volume.id, str(datetime.datetime.now()))
270
    description = new_snapshot.get("description", "")
270
    description = new_snapshot.get("display_description", "")
271 271

  
272 272
    # TODO: What to do with force ?
273 273
    force = new_snapshot.get("force", False)
......
329 329
    req = utils.get_request_dict(request)
330 330
    log.debug('update_snapshot snapshot_id: %s, request: %s', snapshot_id, req)
331 331
    snapshot = util.get_snapshot(request.user_uniq, snapshot_id)
332
    # TODO
333
    #snapshot.name = req.get("name", snapshot.name)
334
    #snapshot.description = req.get("description", snapshot.description)
335
    #snapshot.save()
332

  
333
    new_name = req.get("display_name")
334
    new_description = req.get("display_description")
335
    if new_name is None and new_description is None:
336
        raise faults.BadRequest("Nothing to update.")
337

  
338
    if new_name is not None:
339
        snapshot = snapshots.rename(snapshot, new_name)
340
    if new_description is not None:
341
        snapshot = snapshots.update_description(snapshot, new_description)
342

  
336 343
    data = json.dumps({'snapshot': snapshot_to_dict(snapshot, detail=True)})
337 344
    return HttpResponse(data, content_type="application/json", status=200)

Also available in: Unified diff