Statistics
| Branch: | Tag: | Revision:

root / logic / backend.py @ adee02b8

History | View | Annotate | Download (8.4 kB)

1
# Copyright 2011 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 django.conf import settings
35
from django.db import transaction
36

    
37
from synnefo.db.models import (VirtualMachine, Network, NetworkInterface,
38
                                NetworkLink)
39
from synnefo.logic import utils
40
from synnefo.util.rapi import GanetiRapiClient
41

    
42

    
43
rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
44

    
45

    
46
def process_op_status(vm, jobid, opcode, status, logmsg):
47
    """Process a job progress notification from the backend
48

49
    Process an incoming message from the backend (currently Ganeti).
50
    Job notifications with a terminating status (sucess, error, or canceled),
51
    also update the operating state of the VM.
52

53
    """
54
    if (opcode not in [x[0] for x in VirtualMachine.BACKEND_OPCODES] or
55
       status not in [x[0] for x in VirtualMachine.BACKEND_STATUSES]):
56
        raise VirtualMachine.InvalidBackendMsgError(opcode, status)
57

    
58
    vm.backendjobid = jobid
59
    vm.backendjobstatus = status
60
    vm.backendopcode = opcode
61
    vm.backendlogmsg = logmsg
62

    
63
    # Notifications of success change the operating state
64
    if status == 'success' and VirtualMachine.OPER_STATE_FROM_OPCODE[opcode] is not None:
65
        utils.update_state(vm, VirtualMachine.OPER_STATE_FROM_OPCODE[opcode])
66
        # Set the deleted flag explicitly, to cater for admin-initiated removals
67
        if opcode == 'OP_INSTANCE_REMOVE':
68
            vm.deleted = True
69

    
70
    # Special case: if OP_INSTANCE_CREATE fails --> ERROR
71
    if status in ('canceled', 'error') and opcode == 'OP_INSTANCE_CREATE':
72
        utils.update_state(vm, 'ERROR')
73
    # Any other notification of failure leaves the operating state unchanged
74

    
75
    vm.save()
76

    
77

    
78
@transaction.commit_on_success
79
def process_net_status(vm, nics):
80
    """Process a net status notification from the backend
81

82
    Process an incoming message from the Ganeti backend,
83
    detailing the NIC configuration of a VM instance.
84

85
    Update the state of the VM in the DB accordingly.
86
    """
87
    
88
    vm.nics.all().delete()
89
    for i, nic in enumerate(nics):
90
        if i == 0:
91
            net = Network.objects.get(public=True)
92
        else:
93
            try:
94
                link = NetworkLink.objects.get(name=nic['link'])
95
            except NetworkLink.DoesNotExist:
96
                # Cannot find an instance of NetworkLink for
97
                # the link attribute specified in the notification
98
                raise NetworkLink.DoesNotExist("Cannot find a NetworkLink "
99
                    "object for link='%s'" % nic['link'])
100
            net = link.network
101
            if net is None:
102
                raise Network.DoesNotExist("NetworkLink for link='%s' not "
103
                    "associated with an existing Network instance." %
104
                    nic['link'])
105

    
106
        vm.nics.create(
107
            network=net,
108
            index=i,
109
            mac=nic.get('mac', ''),
110
            ipv4=nic.get('ip', ''))
111
    vm.save()
112

    
113

    
114
def start_action(vm, action):
115
    """Update the state of a VM when a new action is initiated."""
116
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
117
        raise VirtualMachine.InvalidActionError(action)
118

    
119
    # No actions to deleted and no actions beside destroy to suspended VMs
120
    if vm.deleted:
121
        raise VirtualMachine.DeletedError
122
   
123
    # No actions to machines being built. They may be destroyed, however.
124
    if vm.operstate == 'BUILD' and action != 'DESTROY':
125
        raise VirtualMachine.BuildingError
126
    
127
    vm.action = action
128
    vm.backendjobid = None
129
    vm.backendopcode = None
130
    vm.backendjobstatus = None
131
    vm.backendlogmsg = None
132

    
133
    # Update the relevant flags if the VM is being suspended or destroyed
134
    if action == "DESTROY":
135
        vm.deleted = True
136
    elif action == "SUSPEND":
137
        vm.suspended = True
138
    elif action == "START":
139
        vm.suspended = False
140
    vm.save()
141

    
142

    
143
def create_instance(vm, flavor, password):
144
    # FIXME: `password` must be passed to the Ganeti OS provider via CreateInstance()
145
    
146
    nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK}
147
    
148
    return rapi.CreateInstance(
149
        mode='create',
150
        name=vm.backend_id,
151
        disk_template='plain',
152
        disks=[{"size": 2000}],         #FIXME: Always ask for a 2GB disk for now
153
        nics=[nic],
154
        os='debootstrap+default',       #TODO: select OS from imageRef
155
        ip_check=False,
156
        name_check=False,
157
        pnode=rapi.GetNodes()[0],       #TODO: verify if this is necessary
158
        dry_run=settings.TEST,
159
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram))
160

    
161
def delete_instance(vm):
162
    start_action(vm, 'DESTROY')
163
    rapi.DeleteInstance(vm.backend_id, dry_run=settings.TEST)
164
    vm.nics.all().delete()
165

    
166

    
167
def reboot_instance(vm, reboot_type):
168
    assert reboot_type in ('soft', 'hard')
169
    rapi.RebootInstance(vm.backend_id, reboot_type, dry_run=settings.TEST)
170

    
171

    
172
def startup_instance(vm):
173
    start_action(vm, 'START')
174
    rapi.StartupInstance(vm.backend_id, dry_run=settings.TEST)
175

    
176

    
177
def shutdown_instance(vm):
178
    start_action(vm, 'STOP')
179
    rapi.ShutdownInstance(vm.backend_id, dry_run=settings.TEST)
180

    
181

    
182
def get_instance_console(vm):
183
    return rapi.GetInstanceConsole(vm.backend_id)
184

    
185

    
186
def create_network_link():
187
    try:
188
        last = NetworkLink.objects.order_by('-index')[0]
189
        index = last.index + 1
190
    except IndexError:
191
        index = 1
192
    
193
    if index <= settings.GANETI_MAX_LINK_NUMBER:
194
        name = '%s%d' % (settings.GANETI_LINK_PREFIX, index)
195
        return NetworkLink.objects.create(index=index, name=name, available=True)
196
    return None     # All link slots are filled
197

    
198
@transaction.commit_on_success
199
def create_network(name, owner):
200
    try:
201
        link = NetworkLink.objects.filter(available=True)[0]
202
    except IndexError:
203
        link = create_network_link()
204
        if not link:
205
            return None
206
    
207
    network = Network.objects.create(
208
        name=name,
209
        owner=owner,
210
        state='ACTIVE',
211
        link=link)
212
    
213
    link.network = network
214
    link.available = False
215
    link.save()
216
    
217
    return network
218

    
219
@transaction.commit_on_success
220
def delete_network(net):
221
    link = net.link
222
    if link.name != settings.GANETI_NULL_LINK:
223
        link.available = True
224
        link.network = None
225
        link.save()
226
    
227
    for vm in net.machines.all():
228
        disconnect_from_network(vm, net)
229
        vm.save()
230
    net.state = 'DELETED'
231
    net.save()
232

    
233
def connect_to_network(vm, net):
234
    nic = {'mode': 'bridged', 'link': net.link.name}
235
    rapi.ModifyInstance(vm.backend_id,
236
        nics=[('add', nic)],
237
        dry_run=settings.TEST)
238

    
239
def disconnect_from_network(vm, net):
240
    nics = vm.nics.filter(network__public=False).order_by('index')
241
    new_nics = [nic for nic in nics if nic.network != net]
242
    if new_nics == nics:
243
        return      # Nothing to remove
244
    ops = [('remove', {})]
245
    for i, nic in enumerate(new_nics):
246
        ops.append((i + 1, {
247
            'mode': 'bridged',
248
            'link': nic.network.link.name}))
249
    rapi.ModifyInstance(vm.backend_id, nics=ops, dry_run=settings.TEST)