Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / volume / snapshots.py @ 18ca395d

History | View | Annotate | Download (5 kB)

1
import logging
2
from django.utils import simplejson as json
3
from django.db import transaction
4
from snf_django.lib.api import faults
5
from synnefo.plankton.utils import image_backend
6
from synnefo.logic import backend
7
from synnefo.volume import util
8

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

    
12
log = logging.getLogger(__name__)
13

    
14
SNAPSHOTS_CONTAINER = "snapshots"
15
SNAPSHOTS_DOMAIN = "plankton"
16
SNAPSHOTS_PREFIX = "plankton:"
17
SNAPSHOTS_TYPE = "application/octet-stream"
18
SNAPSHOTS_MAPFILE_PREFIX = "archip:"
19

    
20

    
21
@transaction.commit_on_success
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.
32

33
    """
34

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

    
39
    flavor = volume.machine.flavor
40
    if not flavor.disk_template.startswith("ext_"):
41
        msg = ("Snapshots are supported only for volumes of ext_*"
42
               " disk template")
43
        raise faults.BadRequest(msg)
44

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

    
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
68
    snapshot_metadata[SNAPSHOTS_PREFIX + "is_snapshot"] = True
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
74
    image_properties = {"EXCLUDE_ALL_TASKS": "yes",
75
                        "description": description}
76
    vm_metadata = dict(volume.machine.metadata
77
                                     .values_list("meta_key", "meta_value"))
78
    for key in ["OS", "users"]:
79
        val = vm_metadata.get(key)
80
        if val is not None:
81
            image_properties[key] = val
82
    snapshot_metadata[SNAPSHOTS_PREFIX + "properties"] = \
83
        json.dumps(image_properties)
84

    
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
89

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

    
93
    with image_backend(user_id) as pithos_backend:
94
        # move this to plankton backend
95
        snapshot_uuid = pithos_backend.backend.register_object_map(
96
            user=user_id,
97
            account=user_id,
98
            container=SNAPSHOTS_CONTAINER,
99
            name=snapshot_pithos_name,
100
            size=size,
101
            domain=SNAPSHOTS_DOMAIN,
102
            type=SNAPSHOTS_TYPE,
103
            mapfile=mapfile,
104
            meta=snapshot_metadata,
105
            replace_meta=True,
106
            permissions=None)
107
            #checksum=None,
108

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

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

    
114
    return snapshot
115

    
116

    
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)
122

    
123

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

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

130
    """
131
    user_id = snapshot["owner"]
132
    log.info("Deleting snapshot '%s'", snapshot["location"])
133
    with image_backend(user_id) as pithos_backend:
134
        pithos_backend.delete_snapshot(snapshot["uuid"])
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!")