Statistics
| Branch: | Tag: | Revision:

root / logic / backend.py @ efff6193

History | View | Annotate | Download (9.6 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
_firewall_tags = {
46
    'ENABLED': settings.GANETI_FIREWALL_ENABLED_TAG,
47
    'DISABLED': settings.GANETI_FIREWALL_DISABLED_TAG,
48
    'PROTECTED': settings.GANETI_FIREWALL_PROTECTED_TAG}
49

    
50
_reverse_tags = dict((v.split(':')[3], k) for k, v in _firewall_tags.items())
51

    
52

    
53
def process_op_status(vm, jobid, opcode, status, logmsg):
54
    """Process a job progress notification from the backend
55

56
    Process an incoming message from the backend (currently Ganeti).
57
    Job notifications with a terminating status (sucess, error, or canceled),
58
    also update the operating state of the VM.
59

60
    """
61
    if (opcode not in [x[0] for x in VirtualMachine.BACKEND_OPCODES] or
62
       status not in [x[0] for x in VirtualMachine.BACKEND_STATUSES]):
63
        raise VirtualMachine.InvalidBackendMsgError(opcode, status)
64

    
65
    vm.backendjobid = jobid
66
    vm.backendjobstatus = status
67
    vm.backendopcode = opcode
68
    vm.backendlogmsg = logmsg
69

    
70
    # Notifications of success change the operating state
71
    if status == 'success' and VirtualMachine.OPER_STATE_FROM_OPCODE[opcode] is not None:
72
        utils.update_state(vm, VirtualMachine.OPER_STATE_FROM_OPCODE[opcode])
73
        # Set the deleted flag explicitly, to cater for admin-initiated removals
74
        if opcode == 'OP_INSTANCE_REMOVE':
75
            vm.deleted = True
76

    
77
    # Special case: if OP_INSTANCE_CREATE fails --> ERROR
78
    if status in ('canceled', 'error') and opcode == 'OP_INSTANCE_CREATE':
79
        utils.update_state(vm, 'ERROR')
80
    # Any other notification of failure leaves the operating state unchanged
81

    
82
    vm.save()
83

    
84

    
85
@transaction.commit_on_success
86
def process_net_status(vm, nics):
87
    """Process a net status notification from the backend
88

89
    Process an incoming message from the Ganeti backend,
90
    detailing the NIC configuration of a VM instance.
91

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

    
113
        firewall_profile=''
114
        if 'firewall' in nic:
115
            firewall_profile = _reverse_tags.get(nic['firewall'], '')
116
        
117
        vm.nics.create(
118
            network=net,
119
            index=i,
120
            mac=nic.get('mac', ''),
121
            ipv4=nic.get('ip', ''),
122
            ipv6=nic.get('ipv6',''),
123
            firewall_profile=firewall_profile)
124
    vm.save()
125

    
126

    
127
def start_action(vm, action):
128
    """Update the state of a VM when a new action is initiated."""
129
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
130
        raise VirtualMachine.InvalidActionError(action)
131

    
132
    # No actions to deleted and no actions beside destroy to suspended VMs
133
    if vm.deleted:
134
        raise VirtualMachine.DeletedError
135
   
136
    # No actions to machines being built. They may be destroyed, however.
137
    if vm.operstate == 'BUILD' and action != 'DESTROY':
138
        raise VirtualMachine.BuildingError
139
    
140
    vm.action = action
141
    vm.backendjobid = None
142
    vm.backendopcode = None
143
    vm.backendjobstatus = None
144
    vm.backendlogmsg = None
145

    
146
    # Update the relevant flags if the VM is being suspended or destroyed
147
    if action == "DESTROY":
148
        vm.deleted = True
149
    elif action == "SUSPEND":
150
        vm.suspended = True
151
    elif action == "START":
152
        vm.suspended = False
153
    vm.save()
154

    
155

    
156
def create_instance(vm, flavor, image, password):
157
    
158
    nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK}
159
    
160
    return rapi.CreateInstance(
161
        mode='create',
162
        name=vm.backend_id,
163
        disk_template='plain',
164
        disks=[{"size": 4000}],     #FIXME: Always ask for a 4GB disk for now
165
        nics=[nic],
166
        os='image+default',
167
        ip_check=False,
168
        name_check=False,
169
        # Do not specific a node explicitly, have
170
        # Ganeti use an iallocator instead
171
        #
172
        # pnode=rapi.GetNodes()[0]
173
        dry_run=settings.TEST,
174
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram),
175
        osparams=dict(img_id=image.backend_id, img_passwd=password,
176
                      img_format=image.format))
177

    
178

    
179
def delete_instance(vm):
180
    start_action(vm, 'DESTROY')
181
    rapi.DeleteInstance(vm.backend_id, dry_run=settings.TEST)
182
    vm.nics.all().delete()
183

    
184

    
185
def reboot_instance(vm, reboot_type):
186
    assert reboot_type in ('soft', 'hard')
187
    rapi.RebootInstance(vm.backend_id, reboot_type, dry_run=settings.TEST)
188

    
189

    
190
def startup_instance(vm):
191
    start_action(vm, 'START')
192
    rapi.StartupInstance(vm.backend_id, dry_run=settings.TEST)
193

    
194

    
195
def shutdown_instance(vm):
196
    start_action(vm, 'STOP')
197
    rapi.ShutdownInstance(vm.backend_id, dry_run=settings.TEST)
198

    
199

    
200
def get_instance_console(vm):
201
    return rapi.GetInstanceConsole(vm.backend_id)
202

    
203

    
204
def request_status_update(vm):
205
    return rapi.GetInstanceInfo(vm.backend_id)
206

    
207

    
208
def get_job_status(jobid):
209
    return rapi.GetJobStatus(jobid)
210

    
211

    
212
def update_status(vm, status):
213
    utils.update_state(vm, status)
214

    
215
def create_network_link():
216
    try:
217
        last = NetworkLink.objects.order_by('-index')[0]
218
        index = last.index + 1
219
    except IndexError:
220
        index = 1
221
    
222
    if index <= settings.GANETI_MAX_LINK_NUMBER:
223
        name = '%s%d' % (settings.GANETI_LINK_PREFIX, index)
224
        return NetworkLink.objects.create(index=index, name=name,
225
                                            available=True)
226
    return None     # All link slots are filled
227

    
228
@transaction.commit_on_success
229
def create_network(name, owner):
230
    try:
231
        link = NetworkLink.objects.filter(available=True)[0]
232
    except IndexError:
233
        link = create_network_link()
234
        if not link:
235
            return None
236
    
237
    network = Network.objects.create(
238
        name=name,
239
        owner=owner,
240
        state='ACTIVE',
241
        link=link)
242
    
243
    link.network = network
244
    link.available = False
245
    link.save()
246
    
247
    return network
248

    
249
@transaction.commit_on_success
250
def delete_network(net):
251
    link = net.link
252
    if link.name != settings.GANETI_NULL_LINK:
253
        link.available = True
254
        link.network = None
255
        link.save()
256
    
257
    for vm in net.machines.all():
258
        disconnect_from_network(vm, net)
259
        vm.save()
260
    net.state = 'DELETED'
261
    net.save()
262

    
263
def connect_to_network(vm, net):
264
    nic = {'mode': 'bridged', 'link': net.link.name}
265
    rapi.ModifyInstance(vm.backend_id,
266
        nics=[('add', nic)],
267
        dry_run=settings.TEST)
268

    
269
def disconnect_from_network(vm, net):
270
    nics = vm.nics.filter(network__public=False).order_by('index')
271
    new_nics = [nic for nic in nics if nic.network != net]
272
    if new_nics == nics:
273
        return      # Nothing to remove
274
    ops = [('remove', {})]
275
    for i, nic in enumerate(new_nics):
276
        ops.append((i + 1, {
277
            'mode': 'bridged',
278
            'link': nic.network.link.name}))
279
    rapi.ModifyInstance(vm.backend_id, nics=ops, dry_run=settings.TEST)
280

    
281
def set_firewall_profile(vm, profile):
282
    try:
283
        tag = _firewall_tags[profile]
284
    except KeyError:
285
        raise ValueError("Unsopported Firewall Profile: %s" % profile)
286
    
287
    # Delete all firewall tags
288
    for t in _firewall_tags.values():
289
        rapi.DeleteInstanceTags(vm.backend_id, [t], dry_run=settings.TEST)
290
    
291
    rapi.AddInstanceTags(vm.backend_id, [tag], dry_run=settings.TEST)