Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / server_attachments.py @ a1623327

History | View | Annotate | Download (5.2 kB)

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

    
30
import logging
31

    
32
from snf_django.lib.api import faults
33
from django.conf import settings
34
from synnefo.logic import backend, commands
35

    
36
log = logging.getLogger(__name__)
37

    
38

    
39
def attach_volume(vm, volume):
40
    """Attach a volume to a server.
41

42
    The volume must be in 'AVAILABLE' status in order to be attached. Also,
43
    number of the volumes that are attached to the server must remain less
44
    than 'GANETI_MAX_DISKS_PER_INSTANCE' setting. This function will send
45
    the corresponding job to Ganeti backend and update the status of the
46
    volume to 'ATTACHING'.
47

48
    """
49
    # Check volume state
50
    if volume.status not in ["AVAILABLE", "CREATING"]:
51
        raise faults.BadRequest("Cannot attach volume while volume is in"
52
                                " '%s' status." % volume.status)
53

    
54
    # Check that disk templates are the same
55
    if volume.disk_template != vm.flavor.disk_template:
56
        msg = ("Volume and server must have the same disk template. Volume has"
57
               " disk template '%s' while server has '%s'"
58
               % (volume.disk_template, vm.flavor.disk_template))
59
        raise faults.BadRequest(msg)
60

    
61
    # Check maximum disk per instance hard limit
62
    vm_volumes_num = vm.volumes.filter(deleted=False).count()
63
    if vm_volumes_num == settings.GANETI_MAX_DISKS_PER_INSTANCE:
64
        raise faults.BadRequest("Maximum volumes per server limit reached")
65

    
66
    if volume.status == "CREATING":
67
        action_fields = {"disks": [("add", volume, {})]}
68
    else:
69
        action_fields = {}
70
    comm = commands.server_command("ATTACH_VOLUME",
71
                                   action_fields=action_fields)
72
    return comm(_attach_volume)(vm, volume)
73

    
74

    
75
def _attach_volume(vm, volume):
76
    """Attach a Volume to a VM and update the Volume's status."""
77
    jobid = backend.attach_volume(vm, volume)
78
    log.info("Attached volume '%s' to server '%s'. JobID: '%s'", volume.id,
79
             volume.machine_id, jobid)
80
    volume.backendjobid = jobid
81
    volume.machine = vm
82
    if volume.status == "AVAILALBE":
83
        volume.status = "ATTACHING"
84
    else:
85
        volume.status = "CREATING"
86
    volume.save()
87
    return jobid
88

    
89

    
90
def detach_volume(vm, volume):
91
    """Detach a Volume from a VM
92

93
    The volume must be in 'IN_USE' status in order to be detached. Also,
94
    the root volume of the instance (index=0) can not be detached. This
95
    function will send the corresponding job to Ganeti backend and update the
96
    status of the volume to 'DETACHING'.
97

98
    """
99

    
100
    _check_attachment(vm, volume)
101
    if volume.status not in ["IN_USE", "ERROR"]:
102
        raise faults.BadRequest("Cannot detach volume while volume is in"
103
                                " '%s' status." % volume.status)
104
    if volume.index == 0:
105
        raise faults.BadRequest("Cannot detach the root volume of a server")
106

    
107
    action_fields = {"disks": [("remove", volume, {})]}
108
    comm = commands.server_command("DETACH_VOLUME",
109
                                   action_fields=action_fields)
110
    return comm(_detach_volume)(vm, volume)
111

    
112

    
113
def _detach_volume(vm, volume):
114
    """Detach a Volume from a VM and update the Volume's status"""
115
    jobid = backend.detach_volume(vm, volume)
116
    log.info("Detached volume '%s' from server '%s'. JobID: '%s'", volume.id,
117
             volume.machine_id, jobid)
118
    volume.backendjobid = jobid
119
    if volume.delete_on_termination:
120
        volume.status = "DELETING"
121
    else:
122
        volume.status = "DETACHING"
123
    volume.save()
124
    return jobid
125

    
126

    
127
def _check_attachment(vm, volume):
128
    """Check that the Volume is attached to the VM"""
129
    if volume.machine_id != vm.id:
130
        raise faults.BadRequest("Volume '%s' is not attached to server '%s'"
131
                                % volume.id, vm.id)