Statistics
| Branch: | Tag: | Revision:

root / logic / backend.py @ 746c6bf4

History | View | Annotate | Download (9.2 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
            ipv6=nic.get('ipv6',''))
112
    vm.save()
113

    
114

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

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

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

    
143

    
144
def create_instance(vm, flavor, image, password):
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": 4000}],     #FIXME: Always ask for a 4GB disk for now
153
        nics=[nic],
154
        os='image+default',
155
        ip_check=False,
156
        name_check=False,
157
        pnode=rapi.GetNodes()[0],   #TODO: use a Ganeti iallocator instead
158
        dry_run=settings.TEST,
159
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram),
160
        osparams=dict(img_id=image.backend_id, img_passwd=password,
161
                      img_format=image.format))
162

    
163

    
164
def delete_instance(vm):
165
    start_action(vm, 'DESTROY')
166
    rapi.DeleteInstance(vm.backend_id, dry_run=settings.TEST)
167
    vm.nics.all().delete()
168

    
169

    
170
def reboot_instance(vm, reboot_type):
171
    assert reboot_type in ('soft', 'hard')
172
    rapi.RebootInstance(vm.backend_id, reboot_type, dry_run=settings.TEST)
173

    
174

    
175
def startup_instance(vm):
176
    start_action(vm, 'START')
177
    rapi.StartupInstance(vm.backend_id, dry_run=settings.TEST)
178

    
179

    
180
def shutdown_instance(vm):
181
    start_action(vm, 'STOP')
182
    rapi.ShutdownInstance(vm.backend_id, dry_run=settings.TEST)
183

    
184

    
185
def get_instance_console(vm):
186
    return rapi.GetInstanceConsole(vm.backend_id)
187

    
188

    
189
def request_status_update(vm):
190
    return rapi.GetInstanceInfo(vm.backend_id)
191

    
192

    
193
def get_job_status(jobid):
194
    return rapi.GetJobStatus(jobid)
195

    
196

    
197
def update_status(vm, status):
198
    utils.update_state(vm, status)
199

    
200
def create_network_link():
201
    try:
202
        last = NetworkLink.objects.order_by('-index')[0]
203
        index = last.index + 1
204
    except IndexError:
205
        index = 1
206
    
207
    if index <= settings.GANETI_MAX_LINK_NUMBER:
208
        name = '%s%d' % (settings.GANETI_LINK_PREFIX, index)
209
        return NetworkLink.objects.create(index=index, name=name,
210
                                            available=True)
211
    return None     # All link slots are filled
212

    
213
@transaction.commit_on_success
214
def create_network(name, owner):
215
    try:
216
        link = NetworkLink.objects.filter(available=True)[0]
217
    except IndexError:
218
        link = create_network_link()
219
        if not link:
220
            return None
221
    
222
    network = Network.objects.create(
223
        name=name,
224
        owner=owner,
225
        state='ACTIVE',
226
        link=link)
227
    
228
    link.network = network
229
    link.available = False
230
    link.save()
231
    
232
    return network
233

    
234
@transaction.commit_on_success
235
def delete_network(net):
236
    link = net.link
237
    if link.name != settings.GANETI_NULL_LINK:
238
        link.available = True
239
        link.network = None
240
        link.save()
241
    
242
    for vm in net.machines.all():
243
        disconnect_from_network(vm, net)
244
        vm.save()
245
    net.state = 'DELETED'
246
    net.save()
247

    
248
def connect_to_network(vm, net):
249
    nic = {'mode': 'bridged', 'link': net.link.name}
250
    rapi.ModifyInstance(vm.backend_id,
251
        nics=[('add', nic)],
252
        dry_run=settings.TEST)
253

    
254
def disconnect_from_network(vm, net):
255
    nics = vm.nics.filter(network__public=False).order_by('index')
256
    new_nics = [nic for nic in nics if nic.network != net]
257
    if new_nics == nics:
258
        return      # Nothing to remove
259
    ops = [('remove', {})]
260
    for i, nic in enumerate(new_nics):
261
        ops.append((i + 1, {
262
            'mode': 'bridged',
263
            'link': nic.network.link.name}))
264
    rapi.ModifyInstance(vm.backend_id, nics=ops, dry_run=settings.TEST)
265

    
266

    
267
_firewall_tags = {
268
    'ENABLED': settings.GANETI_FIREWALL_ENABLED_TAG,
269
    'DISABLED': settings.GANETI_FIREWALL_DISABLED_TAG,
270
    'PROTECTED': settings.GANETI_FIREWALL_PROTECTED_TAG}
271

    
272
def set_firewall_profile(vm, profile):
273
    try:
274
        tag = _firewall_tags[profile]
275
    except KeyError:
276
        raise ValueError("Unsopported Firewall Profile: %s" % profile)
277
    
278
    # Delete all firewall tags
279
    rapi.DeleteInstanceTags(vm.backend_id, _firewall_tags.values(),
280
                            dry_run=settings.TEST)
281
    
282
    rapi.AddInstanceTags(vm.backend_id, [tag], dry_run=settings.TEST)