Statistics
| Branch: | Tag: | Revision:

root / logic / backend.py @ 207b70d5

History | View | Annotate | Download (6.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.filter(public=True)[0]
64
        else:
65
            link = NetworkLink.objects.get(name=nic['link'])
66
            net = link.network
67
        
68
        vm.nics.create(
69
            network=net,
70
            index=i,
71
            mac=nic.get('mac', ''),
72
            ipv4=nic.get('ip', ''))
73
    vm.save()
74

    
75

    
76
def start_action(vm, action):
77
    """Update the state of a VM when a new action is initiated."""
78
    if not action in [x[0] for x in VirtualMachine.ACTIONS]:
79
        raise VirtualMachine.InvalidActionError(action)
80

    
81
    # No actions to deleted and no actions beside destroy to suspended VMs
82
    if vm.deleted:
83
        raise VirtualMachine.DeletedError
84
   
85
    # No actions to machines being built. They may be destroyed, however.
86
    if vm.operstate == 'BUILD' and action != 'DESTROY':
87
        raise VirtualMachine.BuildingError
88
    
89
    vm.action = action
90
    vm.backendjobid = None
91
    vm.backendopcode = None
92
    vm.backendjobstatus = None
93
    vm.backendlogmsg = None
94

    
95
    # Update the relevant flags if the VM is being suspended or destroyed
96
    if action == "DESTROY":
97
        vm.deleted = True
98
    elif action == "SUSPEND":
99
        vm.suspended = True
100
    elif action == "START":
101
        vm.suspended = False
102
    vm.save()
103

    
104

    
105
def create_instance(vm, flavor, password):
106
    # FIXME: `password` must be passed to the Ganeti OS provider via CreateInstance()
107
    
108
    nic = {'ip': 'pool', 'mode': 'routed', 'link': settings.GANETI_PUBLIC_LINK}
109
    
110
    return rapi.CreateInstance(
111
        mode='create',
112
        name=vm.backend_id,
113
        disk_template='plain',
114
        disks=[{"size": 2000}],         #FIXME: Always ask for a 2GB disk for now
115
        nics=[nic],
116
        os='debootstrap+default',       #TODO: select OS from imageRef
117
        ip_check=False,
118
        name_check=False,
119
        pnode=rapi.GetNodes()[0],       #TODO: verify if this is necessary
120
        dry_run=settings.TEST,
121
        beparams=dict(auto_balance=True, vcpus=flavor.cpu, memory=flavor.ram))
122

    
123
def delete_instance(vm):
124
    start_action(vm, 'DESTROY')
125
    rapi.DeleteInstance(vm.backend_id, dry_run=settings.TEST)
126
    vm.nics.all().delete()
127

    
128

    
129
def reboot_instance(vm, reboot_type):
130
    assert reboot_type in ('soft', 'hard')
131
    rapi.RebootInstance(vm.backend_id, reboot_type, dry_run=settings.TEST)
132

    
133

    
134
def startup_instance(vm):
135
    start_action(vm, 'START')
136
    rapi.StartupInstance(vm.backend_id, dry_run=settings.TEST)
137

    
138

    
139
def shutdown_instance(vm):
140
    start_action(vm, 'STOP')
141
    rapi.ShutdownInstance(vm.backend_id, dry_run=settings.TEST)
142

    
143

    
144
def get_instance_console(vm):
145
    return rapi.GetInstanceConsole(vm.backend_id)
146

    
147

    
148
def create_network_link():
149
    try:
150
        last = NetworkLink.objects.order_by('-index')[0]
151
        index = last.index + 1
152
    except IndexError:
153
        index = 1
154
    
155
    if index <= settings.GANETI_MAX_LINK_NUMBER:
156
        name = '%s%d' % (settings.GANETI_LINK_PREFIX, index)
157
        return NetworkLink.objects.create(index=index, name=name, available=True)
158
    return None     # All link slots are filled
159

    
160
@transaction.commit_on_success
161
def create_network(net):
162
    try:
163
        link = NetworkLink.objects.filter(available=True)[0]
164
    except IndexError:
165
        link = create_network_link()
166
        if not link:
167
            return False
168
    link.network = net
169
    link.available = False
170
    link.save()
171
    return True
172

    
173
@transaction.commit_on_success
174
def delete_network(net):
175
    link = net.link
176
    link.available = True
177
    link.netowrk = False
178
    link.save()
179
    
180
    for vm in net.machines.all():
181
        disconnect_from_network(vm, net)
182
        vm.save()
183
    net.state = 'DELETED'
184
    net.save()
185

    
186
def connect_to_network(vm, net):
187
    nic = {'mode': 'bridged', 'link': net.link.name}
188
    rapi.ModifyInstance(vm.backend_id, nics=[('add', nic)], dry_run=settings.TEST)
189

    
190
def disconnect_from_network(vm, net):
191
    nics = vm.nics.order_by('index')[1:]    # Skip the public network
192
    ops = [('remove', {})] * len(nics)
193
    for nic in nics:
194
        if nic.network == net:
195
            continue
196
        ops.append(('add', {
197
            'mode': 'bridged',
198
            'link': nic.network.link.name,
199
            'mac': nic.mac}))
200
    for op in ops:
201
        rapi.ModifyInstance(vm.backend_id, nics=[op], dry_run=settings.TEST)