Statistics
| Branch: | Tag: | Revision:

root / logic / backend.py @ ace4bd5d

History | View | Annotate | Download (9.6 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2

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

    
31
# Business Logic for communication with the Ganeti backend
32

    
33
from django.conf import settings
34
from django.db import transaction
35

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

    
41

    
42
rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
43

    
44
_firewall_tags = {
45
    'ENABLED': settings.GANETI_FIREWALL_ENABLED_TAG,
46
    'DISABLED': settings.GANETI_FIREWALL_DISABLED_TAG,
47
    'PROTECTED': settings.GANETI_FIREWALL_PROTECTED_TAG}
48

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

    
51

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

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

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

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

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

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

    
81
    vm.save()
82

    
83

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

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

91
    Update the state of the VM in the DB accordingly.
92
    """
93

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

    
112
        firewall_profile=''
113
        if 'firewall' in nic:
114
            firewall_profile = _reverse_tags.get(nic['firewall'], '')
115

    
116
        vm.nics.create(
117
            network=net,
118
            index=i,
119
            mac=nic.get('mac', ''),
120
            ipv4=nic.get('ip', ''),
121
            ipv6=nic.get('ipv6',''),
122
            firewall_profile=firewall_profile)
123
    vm.save()
124

    
125

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

    
131
    # No actions to deleted and no actions beside destroy to suspended VMs
132
    if vm.deleted:
133
        raise VirtualMachine.DeletedError
134

    
135
    # No actions to machines being built. They may be destroyed, however.
136
    if vm.operstate == 'BUILD' and action != 'DESTROY':
137
        raise VirtualMachine.BuildingError
138

    
139
    vm.action = action
140
    vm.backendjobid = None
141
    vm.backendopcode = None
142
    vm.backendjobstatus = None
143
    vm.backendlogmsg = None
144

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

    
154

    
155
def create_instance(vm, flavor, image, password):
156

    
157
    nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK}
158

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

    
177

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

    
183

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

    
188

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

    
193

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

    
198

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

    
202

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

    
206

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

    
210

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

    
214
def create_network_link():
215
    try:
216
        last = NetworkLink.objects.order_by('-index')[0]
217
        index = last.index + 1
218
    except IndexError:
219
        index = 1
220

    
221
    if index <= settings.GANETI_MAX_LINK_NUMBER:
222
        name = '%s%d' % (settings.GANETI_LINK_PREFIX, index)
223
        return NetworkLink.objects.create(index=index, name=name,
224
                                            available=True)
225
    return None     # All link slots are filled
226

    
227
@transaction.commit_on_success
228
def create_network(name, owner):
229
    try:
230
        link = NetworkLink.objects.filter(available=True)[0]
231
    except IndexError:
232
        link = create_network_link()
233
        if not link:
234
            return None
235

    
236
    network = Network.objects.create(
237
        name=name,
238
        owner=owner,
239
        state='ACTIVE',
240
        link=link)
241

    
242
    link.network = network
243
    link.available = False
244
    link.save()
245

    
246
    return network
247

    
248
@transaction.commit_on_success
249
def delete_network(net):
250
    link = net.link
251
    if link.name != settings.GANETI_NULL_LINK:
252
        link.available = True
253
        link.network = None
254
        link.save()
255

    
256
    for vm in net.machines.all():
257
        disconnect_from_network(vm, net)
258
        vm.save()
259
    net.state = 'DELETED'
260
    net.save()
261

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

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

    
280
def set_firewall_profile(vm, profile):
281
    try:
282
        tag = _firewall_tags[profile]
283
    except KeyError:
284
        raise ValueError("Unsopported Firewall Profile: %s" % profile)
285

    
286
    # Delete all firewall tags
287
    for t in _firewall_tags.values():
288
        rapi.DeleteInstanceTags(vm.backend_id, [t], dry_run=settings.TEST)
289

    
290
    rapi.AddInstanceTags(vm.backend_id, [tag], dry_run=settings.TEST)