Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / volume / volumes.py @ 8c3c855f

History | View | Annotate | Download (7.4 kB)

1
import logging
2

    
3
from django.db import transaction
4
from django.conf import settings
5
from snf_django.lib.api import faults
6
from synnefo.db.models import Volume, VolumeMetadata
7
from synnefo.volume import util
8
from synnefo.logic import server_attachments, utils
9

    
10
log = logging.getLogger(__name__)
11

    
12

    
13
@transaction.commit_on_success
14
def create(user_id, size, server_id, name=None, description=None,
15
           source_volume_id=None, source_snapshot_id=None,
16
           source_image_id=None, metadata=None):
17

    
18
    # Currently we cannot create volumes without being attached to a server
19
    if server_id is None:
20
        raise faults.BadRequest("Volume must be attached to server")
21
    server = util.get_server(user_id, server_id, for_update=True,
22
                             exception=faults.BadRequest)
23

    
24
    # Assert that not more than one source are used
25
    sources = filter(lambda x: x is not None,
26
                     [source_volume_id, source_snapshot_id, source_image_id])
27
    if len(sources) > 1:
28
        raise faults.BadRequest("Volume can not have more than one source!")
29

    
30
    if source_volume_id is not None:
31
        source_type = "volume"
32
        source_uuid = source_volume_id
33
    elif source_snapshot_id is not None:
34
        source_type = "snapshot"
35
        source_uuid = source_snapshot_id
36
    elif source_image_id is not None:
37
        source_type = "image"
38
        source_uuid = source_image_id
39
    else:
40
        source_type = "blank"
41
        source_uuid = None
42

    
43
    volume = _create_volume(server, user_id, size, source_type, source_uuid,
44
                            name, description, index=None)
45

    
46
    if metadata is not None:
47
        for meta_key, meta_val in metadata.items():
48
            utils.check_name_length(meta_key, VolumeMetadata.KEY_LENGTH,
49
                                    "Metadata key is too long")
50
            utils.check_name_length(meta_val, VolumeMetadata.VALUE_LENGTH,
51
                                    "Metadata key is too long")
52
            volume.metadata.create(key=meta_key, value=meta_val)
53

    
54
    server_attachments.attach_volume(server, volume)
55

    
56
    return volume
57

    
58

    
59
def _create_volume(server, user_id, size, source_type, source_uuid,
60
                   name=None, description=None, index=None,
61
                   delete_on_termination=True):
62

    
63
    utils.check_name_length(name, Volume.NAME_LENGTH,
64
                            "Volume name is too long")
65
    utils.check_name_length(description, Volume.DESCRIPTION_LENGTH,
66
                            "Volume name is too long")
67
    # Only ext_ disk template supports cloning from another source. Otherwise
68
    # is must be the root volume so that 'snf-image' fill the volume
69
    disk_template = server.flavor.disk_template
70
    teplate, provider = util.get_disk_template_provider(disk_template)
71
    can_have_source = (index == 0 or
72
                       provider in settings.GANETI_CLONE_PROVIDERS)
73
    if not can_have_source and source_type != "blank":
74
        msg = ("Volumes of '%s' disk template cannot have a source" %
75
               disk_template)
76
        raise faults.BadRequest(msg)
77

    
78
    # TODO: Check Volume/Snapshot Status
79
    if source_type == "volume":
80
        source_volume = util.get_volume(user_id, source_uuid,
81
                                        for_update=True,
82
                                        exception=faults.BadRequest)
83
        if source_volume.status != "IN_USE":
84
            raise faults.BadRequest("Cannot clone volume while it is in '%s'"
85
                                    " status" % source_volume.status)
86
        # If no size is specified, use the size of the volume
87
        if size is None:
88
            size = source_volume.size
89
        elif size < source_volume.size:
90
            raise faults.BadRequest("Volume size cannot be smaller than the"
91
                                    " source volume")
92
        source = Volume.prefix_source(source_uuid, source_type="volume")
93
        origin = source_volume.backend_volume_uuid
94
    elif source_type == "snapshot":
95
        source_snapshot = util.get_snapshot(user_id, source_uuid,
96
                                            exception=faults.BadRequest)
97
        snap_status = source_snapshot.get("status", "").upper()
98
        if snap_status != "AVAILABLE":
99
            raise faults.BadRequest("Cannot create volume from snapshot, while"
100
                                    " snapshot is in '%s' status" %
101
                                    snap_status)
102
        source = Volume.prefix_source(source_uuid,
103
                                      source_type="snapshot")
104
        if size is None:
105
            raise faults.BadRequest("Volume size is required")
106
        elif (size << 30) < int(source_snapshot["size"]):
107
            raise faults.BadRequest("Volume size '%s' is smaller than"
108
                                    " snapshot's size '%s'"
109
                                    % (size << 30, source_snapshot["size"]))
110
        origin = source_snapshot["mapfile"]
111
    elif source_type == "image":
112
        source_image = util.get_image(user_id, source_uuid,
113
                                      exception=faults.BadRequest)
114
        img_status = source_image.get("status", "").upper()
115
        if img_status != "AVAILABLE":
116
            raise faults.BadRequest("Cannot create volume from image, while"
117
                                    " image is in '%s' status" % img_status)
118
        if size is None:
119
            raise faults.BadRequest("Volume size is required")
120
        elif (size << 30) < int(source_image["size"]):
121
            raise faults.BadRequest("Volume size '%s' is smaller than"
122
                                    " image's size '%s'"
123
                                    % (size << 30, source_image["size"]))
124
        source = Volume.prefix_source(source_uuid, source_type="image")
125
        origin = source_image["mapfile"]
126
    elif source_type == "blank":
127
        if size is None:
128
            raise faults.BadRequest("Volume size is required")
129
        source = origin = None
130
    else:
131
        raise faults.BadRequest("Unknwon source type")
132

    
133
    volume = Volume.objects.create(userid=user_id,
134
                                   size=size,
135
                                   disk_template=disk_template,
136
                                   name=name,
137
                                   machine=server,
138
                                   description=description,
139
                                   delete_on_termination=delete_on_termination,
140
                                   source=source,
141
                                   origin=origin,
142
                                   #volume_type=volume_type,
143
                                   status="CREATING")
144
    return volume
145

    
146

    
147
@transaction.commit_on_success
148
def delete(volume):
149
    """Delete a Volume"""
150
    # A volume is deleted by detaching it from the server that is attached.
151
    # Deleting a detached volume is not implemented.
152
    if volume.machine_id is not None:
153
        server_attachments.detach_volume(volume.machine, volume)
154
        log.info("Detach volume '%s' from server '%s', job: %s",
155
                 volume.id, volume.machine_id, volume.backendjobid)
156
    else:
157
        raise faults.BadRequest("Cannot delete a detached volume")
158

    
159
    return volume
160

    
161

    
162
@transaction.commit_on_success
163
def update(volume, name=None, description=None, delete_on_termination=None):
164
    if name is not None:
165
        volume.name = name
166
    if description is not None:
167
        volume.description = description
168
    if delete_on_termination is not None:
169
        volume.delete_on_termination = delete_on_termination
170

    
171
    volume.save()
172
    return volume