Statistics
| Branch: | Tag: | Revision:

root / logic / backend.py @ f533f224

History | View | Annotate | Download (7.2 kB)

1
#
2
# Business Logic for communication with the Ganeti backend
3
#
4
# Copyright 2010 Greek Research and Technology Network
5
#
6

    
7
from django.conf import settings
8
from django.db import transaction
9

    
10
from synnefo.db.models import VirtualMachine, Network, NetworkLink
11
from synnefo.logic import utils
12
from synnefo.util.rapi import GanetiRapiClient
13

    
14

    
15
rapi = GanetiRapiClient(*settings.GANETI_CLUSTER_INFO)
16

    
17

    
18
def process_op_status(vm, jobid, opcode, status, logmsg):
19
    """Process a job progress notification from the backend
20

21
    Process an incoming message from the backend (currently Ganeti).
22
    Job notifications with a terminating status (sucess, error, or canceled),
23
    also update the operating state of the VM.
24

25
    """
26
    if (opcode not in [x[0] for x in VirtualMachine.BACKEND_OPCODES] or
27
       status not in [x[0] for x in VirtualMachine.BACKEND_STATUSES]):
28
        raise VirtualMachine.InvalidBackendMsgError(opcode, status)
29

    
30
    vm.backendjobid = jobid
31
    vm.backendjobstatus = status
32
    vm.backendopcode = opcode
33
    vm.backendlogmsg = logmsg
34

    
35
    # Notifications of success change the operating state
36
    if status == 'success' and VirtualMachine.OPER_STATE_FROM_OPCODE[opcode] is not None:
37
        utils.update_state(vm, VirtualMachine.OPER_STATE_FROM_OPCODE[opcode])
38
        # Set the deleted flag explicitly, to cater for admin-initiated removals
39
        if opcode == 'OP_INSTANCE_REMOVE':
40
            vm.deleted = True
41

    
42
    # Special case: if OP_INSTANCE_CREATE fails --> ERROR
43
    if status in ('canceled', 'error') and opcode == 'OP_INSTANCE_CREATE':
44
        utils.update_state(vm, 'ERROR')
45
    # Any other notification of failure leaves the operating state unchanged
46

    
47
    vm.save()
48

    
49

    
50
@transaction.commit_on_success
51
def process_net_status(vm, nics):
52
    """Process a net status notification from the backend
53

54
    Process an incoming message from the Ganeti backend,
55
    detailing the NIC configuration of a VM instance.
56

57
    Update the state of the VM in the DB accordingly.
58
    """
59
    
60
    vm.nics.all().delete()
61
    for i, nic in enumerate(nics):
62
        if i == 0:
63
            net = Network.objects.get(public=True)
64
        else:
65
            try:
66
                link = NetworkLink.objects.get(name=nic['link'])
67
            except NetworkLink.DoesNotExist:
68
                # Cannot find an instance of NetworkLink for
69
                # the link attribute specified in the notification
70
                raise NetworkLink.DoesNotExist("Cannot find a NetworkLink "
71
                    "object for link='%s'" % nic['link'])
72
            net = link.network
73
            if net is None:
74
                raise Network.DoesNotExist("NetworkLink for link='%s' not "
75
                    "associated with an existing Network instance." %
76
                    nic['link'])
77

    
78
        vm.nics.create(
79
            network=net,
80
            index=i,
81
            mac=nic.get('mac', ''),
82
            ipv4=nic.get('ip', ''))
83
    vm.save()
84

    
85

    
86
def start_action(vm, action):
87
    """Update the state of a VM when a new action is initiated."""
88
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
89
        raise VirtualMachine.InvalidActionError(action)
90

    
91
    # No actions to deleted and no actions beside destroy to suspended VMs
92
    if vm.deleted:
93
        raise VirtualMachine.DeletedError
94
   
95
    # No actions to machines being built. They may be destroyed, however.
96
    if vm.operstate == 'BUILD' and action != 'DESTROY':
97
        raise VirtualMachine.BuildingError
98
    
99
    vm.action = action
100
    vm.backendjobid = None
101
    vm.backendopcode = None
102
    vm.backendjobstatus = None
103
    vm.backendlogmsg = None
104

    
105
    # Update the relevant flags if the VM is being suspended or destroyed
106
    if action == "DESTROY":
107
        vm.deleted = True
108
    elif action == "SUSPEND":
109
        vm.suspended = True
110
    elif action == "START":
111
        vm.suspended = False
112
    vm.save()
113

    
114

    
115
def create_instance(vm, flavor, image, password):
116
    
117
    nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK}
118
    
119
    return rapi.CreateInstance(
120
        mode='create',
121
        name=vm.backend_id,
122
        disk_template='plain',
123
        disks=[{"size": 4000}],     #FIXME: Always ask for a 4GB disk for now
124
        nics=[nic],
125
        os='image+default',
126
        ip_check=False,
127
        name_check=False,
128
        pnode=rapi.GetNodes()[0],   #TODO: use a Ganeti iallocator instead
129
        dry_run=settings.TEST,
130
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram),
131
        osparams=dict(img_id=image.backend_id, img_passwd=password,
132
                      img_format=image.format))
133

    
134

    
135
def delete_instance(vm):
136
    start_action(vm, 'DESTROY')
137
    rapi.DeleteInstance(vm.backend_id, dry_run=settings.TEST)
138
    vm.nics.all().delete()
139

    
140

    
141
def reboot_instance(vm, reboot_type):
142
    assert reboot_type in ('soft', 'hard')
143
    rapi.RebootInstance(vm.backend_id, reboot_type, dry_run=settings.TEST)
144

    
145

    
146
def startup_instance(vm):
147
    start_action(vm, 'START')
148
    rapi.StartupInstance(vm.backend_id, dry_run=settings.TEST)
149

    
150

    
151
def shutdown_instance(vm):
152
    start_action(vm, 'STOP')
153
    rapi.ShutdownInstance(vm.backend_id, dry_run=settings.TEST)
154

    
155

    
156
def get_instance_console(vm):
157
    return rapi.GetInstanceConsole(vm.backend_id)
158

    
159

    
160
def request_status_update(vm):
161
    return rapi.GetInstanceInfo(vm.backend_id)
162

    
163

    
164
def get_job_status(jobid):
165
    return rapi.GetJobStatus(jobid)
166

    
167

    
168
def update_status(vm, status):
169
    utils.update_state(vm, status)
170

    
171
def create_network_link():
172
    try:
173
        last = NetworkLink.objects.order_by('-index')[0]
174
        index = last.index + 1
175
    except IndexError:
176
        index = 1
177
    
178
    if index <= settings.GANETI_MAX_LINK_NUMBER:
179
        name = '%s%d' % (settings.GANETI_LINK_PREFIX, index)
180
        return NetworkLink.objects.create(index=index, name=name,
181
                                          available=True)
182
    # FIXME: Shouldn't something at least be logged here?
183
    return None     # All link slots are filled
184

    
185

    
186
@transaction.commit_on_success
187
def create_network(name, owner):
188
    try:
189
        link = NetworkLink.objects.filter(available=True)[0]
190
    except IndexError:
191
        link = create_network_link()
192
        if not link:
193
            return None
194
    
195
    network = Network.objects.create(
196
        name=name,
197
        owner=owner,
198
        state='ACTIVE',
199
        link=link)
200
    
201
    link.network = network
202
    link.available = False
203
    link.save()
204
    
205
    return network
206

    
207

    
208
@transaction.commit_on_success
209
def delete_network(net):
210
    link = net.link
211
    if link.name != settings.GANETI_NULL_LINK:
212
        link.available = True
213
        link.network = None
214
        link.save()
215
    
216
    for vm in net.machines.all():
217
        disconnect_from_network(vm, net)
218
        vm.save()
219
    net.state = 'DELETED'
220
    net.save()
221

    
222

    
223
def connect_to_network(vm, net):
224
    nic = {'mode': 'bridged', 'link': net.link.name}
225
    rapi.ModifyInstance(vm.backend_id,
226
        nics=[('add', nic)],
227
        dry_run=settings.TEST)
228

    
229

    
230
def disconnect_from_network(vm, net):
231
    nics = vm.nics.filter(network__public=False).order_by('index')
232
    new_nics = [nic for nic in nics if nic.network != net]
233
    if new_nics == nics:
234
        return      # Nothing to remove
235
    ops = [('remove', {})]
236
    for i, nic in enumerate(new_nics):
237
        ops.append((i + 1, {
238
            'mode': 'bridged',
239
            'link': nic.network.link.name}))
240
    rapi.ModifyInstance(vm.backend_id, nics=ops, dry_run=settings.TEST)
241