Statistics
| Branch: | Tag: | Revision:

root / logic / backend.py @ 26563957

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
    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, image, password):
144
    
145
    nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK}
146
    
147
    return rapi.CreateInstance(
148
        mode='create',
149
        name=vm.backend_id,
150
        disk_template='plain',
151
        disks=[{"size": 4000}],     #FIXME: Always ask for a 4GB disk for now
152
        nics=[nic],
153
        os='image+default',
154
        ip_check=False,
155
        name_check=False,
156
        pnode=rapi.GetNodes()[0],   #TODO: use a Ganeti iallocator instead
157
        dry_run=settings.TEST,
158
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram),
159
        osparams=dict(img_id=image.backend_id, img_passwd=password,
160
                      img_format=image.format))
161

    
162

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

    
168

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

    
173

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

    
178

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

    
183

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

    
187

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

    
191

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

    
195

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

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

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

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

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

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

    
265

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

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