Statistics
| Branch: | Tag: | Revision:

root / logic / backend.py @ 4d713480

History | View | Annotate | Download (11.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
_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 = nic.get('firewall', '')
114
        firewall_profile = _reverse_tags.get(firewall, '')
115
        if not firewall_profile and net.public:
116
            firewall_profile = settings.DEFAULT_FIREWALL_PROFILE
117
    
118
        vm.nics.create(
119
            network=net,
120
            index=i,
121
            mac=nic.get('mac', ''),
122
            ipv4=nic.get('ip', ''),
123
            ipv6=nic.get('ipv6',''),
124
            firewall_profile=firewall_profile)
125
    vm.save()
126

    
127

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

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

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

    
141
    vm.action = action
142
    vm.backendjobid = None
143
    vm.backendopcode = None
144
    vm.backendjobstatus = None
145
    vm.backendlogmsg = None
146

    
147
    # Update the relevant flags if the VM is being suspended or destroyed.
148
    # Do not set the deleted flag here, see ticket #721.
149
    #
150
    # The deleted flag is set asynchronously, when an OP_INSTANCE_REMOVE
151
    # completes successfully. Hence, a server may be visible for some time
152
    # after a DELETE /servers/id returns HTTP 204.
153
    #
154
    if action == "DESTROY":
155
        # vm.deleted = True
156
        pass
157
    elif action == "SUSPEND":
158
        vm.suspended = True
159
    elif action == "START":
160
        vm.suspended = False
161
    vm.save()
162

    
163

    
164
def create_instance(vm, flavor, image, password):
165

    
166
    nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK}
167

    
168
    if settings.IGNORE_FLAVOR_DISK_SIZES:
169
        if image.backend_id.find("windows") >= 0:
170
            sz = 14000
171
        else:
172
            sz = 4000
173
    else:
174
        sz = flavor.disk * 1024
175

    
176
    return rapi.CreateInstance(
177
        mode='create',
178
        name=vm.backend_id,
179
        disk_template=settings.GANETI_DISK_TEMPLATE,
180
        disks=[{"size": sz}],     #FIXME: Always ask for a 4GB disk for now
181
        nics=[nic],
182
        os=settings.GANETI_OS_PROVIDER,
183
        ip_check=False,
184
        name_check=False,
185
        # Do not specific a node explicitly, have
186
        # Ganeti use an iallocator instead
187
        #
188
        # pnode=rapi.GetNodes()[0],
189
        dry_run=settings.TEST,
190
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram),
191
        osparams=dict(img_id=image.backend_id, img_passwd=password,
192
                      img_format=image.format),
193
        # Be explicit about setting serial_console = False for Synnefo-based
194
        # instances regardless of the cluster-wide setting, see #785
195
        hvparams=dict(serial_console=False))
196

    
197

    
198
def delete_instance(vm):
199
    start_action(vm, 'DESTROY')
200
    rapi.DeleteInstance(vm.backend_id, dry_run=settings.TEST)
201
    vm.nics.all().delete()
202

    
203

    
204
def reboot_instance(vm, reboot_type):
205
    assert reboot_type in ('soft', 'hard')
206
    rapi.RebootInstance(vm.backend_id, reboot_type, dry_run=settings.TEST)
207

    
208

    
209
def startup_instance(vm):
210
    start_action(vm, 'START')
211
    rapi.StartupInstance(vm.backend_id, dry_run=settings.TEST)
212

    
213

    
214
def shutdown_instance(vm):
215
    start_action(vm, 'STOP')
216
    rapi.ShutdownInstance(vm.backend_id, dry_run=settings.TEST)
217

    
218

    
219
def get_instance_console(vm):
220
    # RAPI GetInstanceConsole() returns endpoints to the vnc_bind_address,
221
    # which is a cluster-wide setting, either 0.0.0.0 or 127.0.0.1, and pretty
222
    # useless (see #783).
223
    #
224
    # Until this is fixed on the Ganeti side, construct a console info reply
225
    # directly.
226
    # 
227
    # WARNING: This assumes that VNC runs on port network_port on
228
    #          the instance's primary node, and is probably
229
    #          hypervisor-specific.
230
    #
231
    console = {}
232
    console['kind'] = 'vnc'
233
    i = rapi.GetInstance(vm.backend_id)
234
    if i['hvparams']['serial_console']:
235
        raise Exception("hv parameter serial_console cannot be true")
236
    console['host'] = i['pnode']
237
    console['port'] = i['network_port']
238
    
239
    return console
240
    # return rapi.GetInstanceConsole(vm.backend_id)
241

    
242
def request_status_update(vm):
243
    return rapi.GetInstanceInfo(vm.backend_id)
244

    
245

    
246
def get_job_status(jobid):
247
    return rapi.GetJobStatus(jobid)
248

    
249

    
250
def update_status(vm, status):
251
    utils.update_state(vm, status)
252

    
253
def create_network_link():
254
    try:
255
        last = NetworkLink.objects.order_by('-index')[0]
256
        index = last.index + 1
257
    except IndexError:
258
        index = 1
259

    
260
    if index <= settings.GANETI_MAX_LINK_NUMBER:
261
        name = '%s%d' % (settings.GANETI_LINK_PREFIX, index)
262
        return NetworkLink.objects.create(index=index, name=name,
263
                                            available=True)
264
    return None     # All link slots are filled
265

    
266
@transaction.commit_on_success
267
def create_network(name, owner):
268
    try:
269
        link = NetworkLink.objects.filter(available=True)[0]
270
    except IndexError:
271
        link = create_network_link()
272
        if not link:
273
            return None
274

    
275
    network = Network.objects.create(
276
        name=name,
277
        owner=owner,
278
        state='ACTIVE',
279
        link=link)
280

    
281
    link.network = network
282
    link.available = False
283
    link.save()
284

    
285
    return network
286

    
287
@transaction.commit_on_success
288
def delete_network(net):
289
    link = net.link
290
    if link.name != settings.GANETI_NULL_LINK:
291
        link.available = True
292
        link.network = None
293
        link.save()
294

    
295
    for vm in net.machines.all():
296
        disconnect_from_network(vm, net)
297
        vm.save()
298
    net.state = 'DELETED'
299
    net.save()
300

    
301
def connect_to_network(vm, net):
302
    nic = {'mode': 'bridged', 'link': net.link.name}
303
    rapi.ModifyInstance(vm.backend_id,
304
        nics=[('add', nic)],
305
        dry_run=settings.TEST)
306

    
307
def disconnect_from_network(vm, net):
308
    nics = vm.nics.filter(network__public=False).order_by('index')
309
    new_nics = [nic for nic in nics if nic.network != net]
310
    if new_nics == nics:
311
        return      # Nothing to remove
312
    ops = [('remove', {})]
313
    for i, nic in enumerate(new_nics):
314
        ops.append((i + 1, {
315
            'mode': 'bridged',
316
            'link': nic.network.link.name}))
317
    rapi.ModifyInstance(vm.backend_id, nics=ops, dry_run=settings.TEST)
318

    
319
def set_firewall_profile(vm, profile):
320
    try:
321
        tag = _firewall_tags[profile]
322
    except KeyError:
323
        raise ValueError("Unsopported Firewall Profile: %s" % profile)
324

    
325
    # Delete all firewall tags
326
    for t in _firewall_tags.values():
327
        rapi.DeleteInstanceTags(vm.backend_id, [t], dry_run=settings.TEST)
328

    
329
    rapi.AddInstanceTags(vm.backend_id, [tag], dry_run=settings.TEST)
330
    
331
    # XXX NOP ModifyInstance call to force process_net_status to run
332
    # on the dispatcher
333
    rapi.ModifyInstance(vm.backend_id, os_name=settings.GANETI_OS_PROVIDER)