Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / backend.py @ bda47e03

History | View | Annotate | Download (41.1 kB)

1 41a7fae7 Christos Stavrakakis
# Copyright 2011-2013 GRNET S.A. All rights reserved.
2 37ca953f Christodoulos Psaltis
#
3 adee02b8 Giorgos Verigakis
# Redistribution and use in source and binary forms, with or
4 adee02b8 Giorgos Verigakis
# without modification, are permitted provided that the following
5 adee02b8 Giorgos Verigakis
# conditions are met:
6 37ca953f Christodoulos Psaltis
#
7 adee02b8 Giorgos Verigakis
#   1. Redistributions of source code must retain the above
8 adee02b8 Giorgos Verigakis
#      copyright notice, this list of conditions and the following
9 adee02b8 Giorgos Verigakis
#      disclaimer.
10 37ca953f Christodoulos Psaltis
#
11 adee02b8 Giorgos Verigakis
#   2. Redistributions in binary form must reproduce the above
12 adee02b8 Giorgos Verigakis
#      copyright notice, this list of conditions and the following
13 adee02b8 Giorgos Verigakis
#      disclaimer in the documentation and/or other materials
14 adee02b8 Giorgos Verigakis
#      provided with the distribution.
15 37ca953f Christodoulos Psaltis
#
16 adee02b8 Giorgos Verigakis
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 adee02b8 Giorgos Verigakis
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 adee02b8 Giorgos Verigakis
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 adee02b8 Giorgos Verigakis
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 adee02b8 Giorgos Verigakis
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 adee02b8 Giorgos Verigakis
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 adee02b8 Giorgos Verigakis
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 adee02b8 Giorgos Verigakis
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 adee02b8 Giorgos Verigakis
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 adee02b8 Giorgos Verigakis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 adee02b8 Giorgos Verigakis
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 adee02b8 Giorgos Verigakis
# POSSIBILITY OF SUCH DAMAGE.
28 37ca953f Christodoulos Psaltis
#
29 adee02b8 Giorgos Verigakis
# The views and conclusions contained in the software and
30 adee02b8 Giorgos Verigakis
# documentation are those of the authors and should not be
31 adee02b8 Giorgos Verigakis
# interpreted as representing official policies, either expressed
32 adee02b8 Giorgos Verigakis
# or implied, of GRNET S.A.
33 529178b1 Giorgos Verigakis
from django.conf import settings
34 207b70d5 Giorgos Verigakis
from django.db import transaction
35 a1baa42b Christos Stavrakakis
from datetime import datetime, timedelta
36 207b70d5 Giorgos Verigakis
37 22ee6892 Christos Stavrakakis
from synnefo.db.models import (Backend, VirtualMachine, Network,
38 3524241a Christos Stavrakakis
                               BackendNetwork, BACKEND_STATUSES,
39 ca4d59e3 Christos Stavrakakis
                               pooled_rapi_client, VirtualMachineDiagnostic,
40 bfb3f9c2 Christos Stavrakakis
                               Flavor, IPAddress, IPAddressLog)
41 0292883e Christos Stavrakakis
from synnefo.logic import utils, ips
42 cb4eee84 Christos Stavrakakis
from synnefo import quotas
43 b7d38981 Dimitris Aragiorgis
from synnefo.api.util import release_resource
44 fd95834e Christos Stavrakakis
from synnefo.util.mac2eui64 import mac2eui64
45 d2036274 Christos Stavrakakis
from synnefo.logic import rapi
46 529178b1 Giorgos Verigakis
47 3524241a Christos Stavrakakis
from logging import getLogger
48 3524241a Christos Stavrakakis
log = getLogger(__name__)
49 9e98ba3c Giorgos Verigakis
50 529178b1 Giorgos Verigakis
51 efff6193 Giorgos Verigakis
_firewall_tags = {
52 efff6193 Giorgos Verigakis
    'ENABLED': settings.GANETI_FIREWALL_ENABLED_TAG,
53 efff6193 Giorgos Verigakis
    'DISABLED': settings.GANETI_FIREWALL_DISABLED_TAG,
54 efff6193 Giorgos Verigakis
    'PROTECTED': settings.GANETI_FIREWALL_PROTECTED_TAG}
55 efff6193 Giorgos Verigakis
56 efff6193 Giorgos Verigakis
_reverse_tags = dict((v.split(':')[3], k) for k, v in _firewall_tags.items())
57 efff6193 Giorgos Verigakis
58 d7ff7f5a Christos Stavrakakis
# Timeout in seconds for building NICs. After this period the NICs considered
59 d7ff7f5a Christos Stavrakakis
# stale and removed from DB.
60 0d069390 Christos Stavrakakis
BUILDING_NIC_TIMEOUT = timedelta(seconds=180)
61 02feca11 Vassilios Karakoidas
62 3278725f Christos Stavrakakis
SIMPLE_NIC_FIELDS = ["state", "mac", "network", "firewall_profile", "index"]
63 3278725f Christos Stavrakakis
COMPLEX_NIC_FIELDS = ["ipv4_address", "ipv6_address"]
64 3278725f Christos Stavrakakis
NIC_FIELDS = SIMPLE_NIC_FIELDS + COMPLEX_NIC_FIELDS
65 0d069390 Christos Stavrakakis
UNKNOWN_NIC_PREFIX = "unknown-"
66 a1baa42b Christos Stavrakakis
67 02feca11 Vassilios Karakoidas
68 41a7fae7 Christos Stavrakakis
def handle_vm_quotas(vm, job_id, job_opcode, job_status, job_fields):
69 41a7fae7 Christos Stavrakakis
    """Handle quotas for updated VirtualMachine.
70 41a7fae7 Christos Stavrakakis

71 41a7fae7 Christos Stavrakakis
    Update quotas for the updated VirtualMachine based on the job that run on
72 41a7fae7 Christos Stavrakakis
    the Ganeti backend. If a commission has been already issued for this job,
73 41a7fae7 Christos Stavrakakis
    then this commission is just accepted or rejected based on the job status.
74 41a7fae7 Christos Stavrakakis
    Otherwise, a new commission for the given change is issued, that is also in
75 41a7fae7 Christos Stavrakakis
    force and auto-accept mode. In this case, previous commissions are
76 41a7fae7 Christos Stavrakakis
    rejected, since they reflect a previous state of the VM.
77 41a7fae7 Christos Stavrakakis

78 41a7fae7 Christos Stavrakakis
    """
79 d2036274 Christos Stavrakakis
    if job_status not in rapi.JOB_STATUS_FINALIZED:
80 88fd91af Christos Stavrakakis
        return vm
81 41a7fae7 Christos Stavrakakis
82 41a7fae7 Christos Stavrakakis
    # Check successful completion of a job will trigger any quotable change in
83 41a7fae7 Christos Stavrakakis
    # the VM state.
84 41a7fae7 Christos Stavrakakis
    action = utils.get_action_from_opcode(job_opcode, job_fields)
85 88fd91af Christos Stavrakakis
    if action == "BUILD":
86 88fd91af Christos Stavrakakis
        # Quotas for new VMs are automatically accepted by the API
87 88fd91af Christos Stavrakakis
        return vm
88 41a7fae7 Christos Stavrakakis
89 41a7fae7 Christos Stavrakakis
    if vm.task_job_id == job_id and vm.serial is not None:
90 41a7fae7 Christos Stavrakakis
        # Commission for this change has already been issued. So just
91 562bf712 Christos Stavrakakis
        # accept/reject it. Special case is OP_INSTANCE_CREATE, which even
92 562bf712 Christos Stavrakakis
        # if fails, must be accepted, as the user must manually remove the
93 562bf712 Christos Stavrakakis
        # failed server
94 41a7fae7 Christos Stavrakakis
        serial = vm.serial
95 d2036274 Christos Stavrakakis
        if job_status == rapi.JOB_STATUS_SUCCESS:
96 41a7fae7 Christos Stavrakakis
            quotas.accept_serial(serial)
97 d2036274 Christos Stavrakakis
        elif job_status in [rapi.JOB_STATUS_ERROR, rapi.JOB_STATUS_CANCELED]:
98 41a7fae7 Christos Stavrakakis
            log.debug("Job %s failed. Rejecting related serial %s", job_id,
99 41a7fae7 Christos Stavrakakis
                      serial)
100 41a7fae7 Christos Stavrakakis
            quotas.reject_serial(serial)
101 41a7fae7 Christos Stavrakakis
        vm.serial = None
102 16b959ce Giorgos Korfiatis
    elif job_status == rapi.JOB_STATUS_SUCCESS:
103 16b959ce Giorgos Korfiatis
        commission_info = quotas.get_commission_info(resource=vm,
104 16b959ce Giorgos Korfiatis
                                                     action=action,
105 16b959ce Giorgos Korfiatis
                                                     action_fields=job_fields)
106 16b959ce Giorgos Korfiatis
        if commission_info is not None:
107 16b959ce Giorgos Korfiatis
            # Commission for this change has not been issued, or the issued
108 16b959ce Giorgos Korfiatis
            # commission was unaware of the current change. Reject all previous
109 16b959ce Giorgos Korfiatis
            # commissions and create a new one in forced mode!
110 16b959ce Giorgos Korfiatis
            log.debug("Expected job was %s. Processing job %s.",
111 16b959ce Giorgos Korfiatis
                      vm.task_job_id, job_id)
112 16b959ce Giorgos Korfiatis
            reason = ("client: dispatcher, resource: %s, ganeti_job: %s"
113 16b959ce Giorgos Korfiatis
                      % (vm, job_id))
114 16b959ce Giorgos Korfiatis
            quotas.handle_resource_commission(vm, action,
115 16b959ce Giorgos Korfiatis
                                              action_fields=job_fields,
116 16b959ce Giorgos Korfiatis
                                              commission_name=reason,
117 16b959ce Giorgos Korfiatis
                                              force=True,
118 16b959ce Giorgos Korfiatis
                                              auto_accept=True)
119 16b959ce Giorgos Korfiatis
            log.debug("Issued new commission: %s", vm.serial)
120 41a7fae7 Christos Stavrakakis
121 41a7fae7 Christos Stavrakakis
    return vm
122 41a7fae7 Christos Stavrakakis
123 41a7fae7 Christos Stavrakakis
124 093f9c53 Vangelis Koukis
@transaction.commit_on_success
125 ca4d59e3 Christos Stavrakakis
def process_op_status(vm, etime, jobid, opcode, status, logmsg, nics=None,
126 e6fbada1 Christos Stavrakakis
                      job_fields=None):
127 ad2d6807 Vangelis Koukis
    """Process a job progress notification from the backend
128 02feca11 Vassilios Karakoidas

129 02feca11 Vassilios Karakoidas
    Process an incoming message from the backend (currently Ganeti).
130 02feca11 Vassilios Karakoidas
    Job notifications with a terminating status (sucess, error, or canceled),
131 02feca11 Vassilios Karakoidas
    also update the operating state of the VM.
132 02feca11 Vassilios Karakoidas

133 02feca11 Vassilios Karakoidas
    """
134 41303ed0 Vangelis Koukis
    # See #1492, #1031, #1111 why this line has been removed
135 41303ed0 Vangelis Koukis
    #if (opcode not in [x[0] for x in VirtualMachine.BACKEND_OPCODES] or
136 fd65ab41 Christos Stavrakakis
    if status not in [x[0] for x in BACKEND_STATUSES]:
137 02feca11 Vassilios Karakoidas
        raise VirtualMachine.InvalidBackendMsgError(opcode, status)
138 02feca11 Vassilios Karakoidas
139 dfd19c2d Vassilios Karakoidas
    vm.backendjobid = jobid
140 dfd19c2d Vassilios Karakoidas
    vm.backendjobstatus = status
141 dfd19c2d Vassilios Karakoidas
    vm.backendopcode = opcode
142 dfd19c2d Vassilios Karakoidas
    vm.backendlogmsg = logmsg
143 02feca11 Vassilios Karakoidas
144 d2036274 Christos Stavrakakis
    if status not in rapi.JOB_STATUS_FINALIZED:
145 41a7fae7 Christos Stavrakakis
        vm.save()
146 41a7fae7 Christos Stavrakakis
        return
147 41a7fae7 Christos Stavrakakis
148 e6fbada1 Christos Stavrakakis
    if job_fields is None:
149 e6fbada1 Christos Stavrakakis
        job_fields = {}
150 1fdd8d69 Christos Stavrakakis
151 1fdd8d69 Christos Stavrakakis
    new_operstate = None
152 41a7fae7 Christos Stavrakakis
    state_for_success = VirtualMachine.OPER_STATE_FROM_OPCODE.get(opcode)
153 32a0b855 Giorgos Korfiatis
154 d2036274 Christos Stavrakakis
    if status == rapi.JOB_STATUS_SUCCESS:
155 1fdd8d69 Christos Stavrakakis
        # If job succeeds, change operating state if needed
156 41a7fae7 Christos Stavrakakis
        if state_for_success is not None:
157 1fdd8d69 Christos Stavrakakis
            new_operstate = state_for_success
158 1fdd8d69 Christos Stavrakakis
159 e6fbada1 Christos Stavrakakis
        beparams = job_fields.get("beparams", None)
160 41a7fae7 Christos Stavrakakis
        if beparams:
161 41a7fae7 Christos Stavrakakis
            # Change the flavor of the VM
162 ca4d59e3 Christos Stavrakakis
            _process_resize(vm, beparams)
163 1fdd8d69 Christos Stavrakakis
164 41a7fae7 Christos Stavrakakis
        # Update backendtime only for jobs that have been successfully
165 41a7fae7 Christos Stavrakakis
        # completed, since only these jobs update the state of the VM. Else a
166 41a7fae7 Christos Stavrakakis
        # "race condition" may occur when a successful job (e.g.
167 41a7fae7 Christos Stavrakakis
        # OP_INSTANCE_REMOVE) completes before an error job and messages arrive
168 41a7fae7 Christos Stavrakakis
        # in reversed order.
169 41a7fae7 Christos Stavrakakis
        vm.backendtime = etime
170 ca4d59e3 Christos Stavrakakis
171 d2036274 Christos Stavrakakis
    if status in rapi.JOB_STATUS_FINALIZED and nics is not None:
172 90858bda Christos Stavrakakis
        # Update the NICs of the VM
173 90858bda Christos Stavrakakis
        _process_net_status(vm, etime, nics)
174 90858bda Christos Stavrakakis
175 91954b45 Christos Stavrakakis
    # Special case: if OP_INSTANCE_CREATE fails --> ERROR
176 d2036274 Christos Stavrakakis
    if opcode == 'OP_INSTANCE_CREATE' and status in (rapi.JOB_STATUS_CANCELED,
177 d2036274 Christos Stavrakakis
                                                     rapi.JOB_STATUS_ERROR):
178 1fdd8d69 Christos Stavrakakis
        new_operstate = "ERROR"
179 c4ce868e Christos Stavrakakis
        vm.backendtime = etime
180 96feddae Christos Stavrakakis
        # Update state of associated NICs
181 96feddae Christos Stavrakakis
        vm.nics.all().update(state="ERROR")
182 cb4eee84 Christos Stavrakakis
    elif opcode == 'OP_INSTANCE_REMOVE':
183 e97288bc Christos Stavrakakis
        # Special case: OP_INSTANCE_REMOVE fails for machines in ERROR,
184 e97288bc Christos Stavrakakis
        # when no instance exists at the Ganeti backend.
185 e97288bc Christos Stavrakakis
        # See ticket #799 for all the details.
186 d2036274 Christos Stavrakakis
        if (status == rapi.JOB_STATUS_SUCCESS or
187 d2036274 Christos Stavrakakis
           (status == rapi.JOB_STATUS_ERROR and not vm_exists_in_backend(vm))):
188 a1baa42b Christos Stavrakakis
            # VM has been deleted
189 a1baa42b Christos Stavrakakis
            for nic in vm.nics.all():
190 a1baa42b Christos Stavrakakis
                # Release the IP
191 3278725f Christos Stavrakakis
                remove_nic_ips(nic)
192 a1baa42b Christos Stavrakakis
                # And delete the NIC.
193 a1baa42b Christos Stavrakakis
                nic.delete()
194 e97288bc Christos Stavrakakis
            vm.deleted = True
195 1fdd8d69 Christos Stavrakakis
            new_operstate = state_for_success
196 e97288bc Christos Stavrakakis
            vm.backendtime = etime
197 d2036274 Christos Stavrakakis
            status = rapi.JOB_STATUS_SUCCESS
198 41a7fae7 Christos Stavrakakis
199 d2036274 Christos Stavrakakis
    if status in rapi.JOB_STATUS_FINALIZED:
200 41a7fae7 Christos Stavrakakis
        # Job is finalized: Handle quotas/commissioning
201 41a7fae7 Christos Stavrakakis
        vm = handle_vm_quotas(vm, job_id=jobid, job_opcode=opcode,
202 41a7fae7 Christos Stavrakakis
                              job_status=status, job_fields=job_fields)
203 41a7fae7 Christos Stavrakakis
        # and clear task fields
204 41a7fae7 Christos Stavrakakis
        if vm.task_job_id == jobid:
205 41a7fae7 Christos Stavrakakis
            vm.task = None
206 41a7fae7 Christos Stavrakakis
            vm.task_job_id = None
207 02feca11 Vassilios Karakoidas
208 1fdd8d69 Christos Stavrakakis
    if new_operstate is not None:
209 1fdd8d69 Christos Stavrakakis
        vm.operstate = new_operstate
210 1fdd8d69 Christos Stavrakakis
211 02feca11 Vassilios Karakoidas
    vm.save()
212 22e52ede Vassilios Karakoidas
213 ad2d6807 Vangelis Koukis
214 ca4d59e3 Christos Stavrakakis
def _process_resize(vm, beparams):
215 ca4d59e3 Christos Stavrakakis
    """Change flavor of a VirtualMachine based on new beparams."""
216 ca4d59e3 Christos Stavrakakis
    old_flavor = vm.flavor
217 41a7fae7 Christos Stavrakakis
    vcpus = beparams.get("vcpus", old_flavor.cpu)
218 41a7fae7 Christos Stavrakakis
    ram = beparams.get("maxmem", old_flavor.ram)
219 41a7fae7 Christos Stavrakakis
    if vcpus == old_flavor.cpu and ram == old_flavor.ram:
220 ca4d59e3 Christos Stavrakakis
        return
221 ca4d59e3 Christos Stavrakakis
    try:
222 ca4d59e3 Christos Stavrakakis
        new_flavor = Flavor.objects.get(cpu=vcpus, ram=ram,
223 ca4d59e3 Christos Stavrakakis
                                        disk=old_flavor.disk,
224 ca4d59e3 Christos Stavrakakis
                                        disk_template=old_flavor.disk_template)
225 ca4d59e3 Christos Stavrakakis
    except Flavor.DoesNotExist:
226 8d5795b4 Christos Stavrakakis
        raise Exception("Cannot find flavor for VM")
227 ca4d59e3 Christos Stavrakakis
    vm.flavor = new_flavor
228 ca4d59e3 Christos Stavrakakis
    vm.save()
229 ca4d59e3 Christos Stavrakakis
230 ca4d59e3 Christos Stavrakakis
231 207b70d5 Giorgos Verigakis
@transaction.commit_on_success
232 c4e55622 Christos Stavrakakis
def process_net_status(vm, etime, nics):
233 fd95834e Christos Stavrakakis
    """Wrap _process_net_status inside transaction."""
234 fd95834e Christos Stavrakakis
    _process_net_status(vm, etime, nics)
235 fd95834e Christos Stavrakakis
236 fd95834e Christos Stavrakakis
237 fd95834e Christos Stavrakakis
def _process_net_status(vm, etime, nics):
238 ad2d6807 Vangelis Koukis
    """Process a net status notification from the backend
239 ad2d6807 Vangelis Koukis

240 ad2d6807 Vangelis Koukis
    Process an incoming message from the Ganeti backend,
241 ad2d6807 Vangelis Koukis
    detailing the NIC configuration of a VM instance.
242 ad2d6807 Vangelis Koukis

243 ad2d6807 Vangelis Koukis
    Update the state of the VM in the DB accordingly.
244 37ca953f Christodoulos Psaltis

245 a1baa42b Christos Stavrakakis
    """
246 b578d9e7 Christos Stavrakakis
    ganeti_nics = process_ganeti_nics(nics)
247 3278725f Christos Stavrakakis
    db_nics = dict([(nic.id, nic)
248 3278725f Christos Stavrakakis
                    for nic in vm.nics.prefetch_related("ips__subnet")])
249 40d53b77 Christos Stavrakakis
250 40d53b77 Christos Stavrakakis
    # Get X-Lock on backend before getting X-Lock on network IP pools, to
251 40d53b77 Christos Stavrakakis
    # guarantee that no deadlock will occur with Backend allocator.
252 40d53b77 Christos Stavrakakis
    Backend.objects.select_for_update().get(id=vm.backend_id)
253 b578d9e7 Christos Stavrakakis
254 a1baa42b Christos Stavrakakis
    for nic_name in set(db_nics.keys()) | set(ganeti_nics.keys()):
255 a1baa42b Christos Stavrakakis
        db_nic = db_nics.get(nic_name)
256 a1baa42b Christos Stavrakakis
        ganeti_nic = ganeti_nics.get(nic_name)
257 a1baa42b Christos Stavrakakis
        if ganeti_nic is None:
258 a1baa42b Christos Stavrakakis
            # NIC exists in DB but not in Ganeti. If the NIC is in 'building'
259 a1baa42b Christos Stavrakakis
            # state for more than 5 minutes, then we remove the NIC.
260 a1baa42b Christos Stavrakakis
            # TODO: This is dangerous as the job may be stack in the queue, and
261 a1baa42b Christos Stavrakakis
            # releasing the IP may lead to duplicate IP use.
262 3a6be177 Christos Stavrakakis
            if db_nic.state != "BUILD" or\
263 3a6be177 Christos Stavrakakis
                (db_nic.state == "BUILD" and
264 0d069390 Christos Stavrakakis
                 etime > db_nic.created + BUILDING_NIC_TIMEOUT):
265 3278725f Christos Stavrakakis
                remove_nic_ips(db_nic)
266 0d069390 Christos Stavrakakis
                db_nic.delete()
267 a1baa42b Christos Stavrakakis
            else:
268 0d069390 Christos Stavrakakis
                log.warning("Ignoring recent building NIC: %s", db_nic)
269 a1baa42b Christos Stavrakakis
        elif db_nic is None:
270 3278725f Christos Stavrakakis
            msg = ("NIC/%s of VM %s does not exist in DB! Cannot automatically"
271 3278725f Christos Stavrakakis
                   " fix this issue!" % (nic_name, vm))
272 3278725f Christos Stavrakakis
            log.error(msg)
273 3278725f Christos Stavrakakis
            continue
274 a1baa42b Christos Stavrakakis
        elif not nics_are_equal(db_nic, ganeti_nic):
275 3278725f Christos Stavrakakis
            for f in SIMPLE_NIC_FIELDS:
276 3278725f Christos Stavrakakis
                # Update the NIC in DB with the values from Ganeti NIC
277 3278725f Christos Stavrakakis
                setattr(db_nic, f, ganeti_nic[f])
278 3278725f Christos Stavrakakis
                db_nic.save()
279 bfb3f9c2 Christos Stavrakakis
280 a1baa42b Christos Stavrakakis
            # Special case where the IPv4 address has changed, because you
281 a1baa42b Christos Stavrakakis
            # need to release the old IPv4 address and reserve the new one
282 8764d304 Christos Stavrakakis
            ipv4_address = ganeti_nic["ipv4_address"]
283 8764d304 Christos Stavrakakis
            if db_nic.ipv4_address != ipv4_address:
284 bfb3f9c2 Christos Stavrakakis
                change_address_of_port(db_nic, vm.userid,
285 bfb3f9c2 Christos Stavrakakis
                                       old_address=db_nic.ipv4_address,
286 bfb3f9c2 Christos Stavrakakis
                                       new_address=ipv4_address,
287 bfb3f9c2 Christos Stavrakakis
                                       version=4)
288 bfb3f9c2 Christos Stavrakakis
289 bfb3f9c2 Christos Stavrakakis
            ipv6_address = ganeti_nic["ipv6_address"]
290 bfb3f9c2 Christos Stavrakakis
            if db_nic.ipv6_address != ipv6_address:
291 bfb3f9c2 Christos Stavrakakis
                change_address_of_port(db_nic, vm.userid,
292 bfb3f9c2 Christos Stavrakakis
                                       old_address=db_nic.ipv6_address,
293 bfb3f9c2 Christos Stavrakakis
                                       new_address=ipv6_address,
294 bfb3f9c2 Christos Stavrakakis
                                       version=6)
295 b578d9e7 Christos Stavrakakis
296 b578d9e7 Christos Stavrakakis
    vm.backendtime = etime
297 b578d9e7 Christos Stavrakakis
    vm.save()
298 b578d9e7 Christos Stavrakakis
299 b578d9e7 Christos Stavrakakis
300 bfb3f9c2 Christos Stavrakakis
def change_address_of_port(port, userid, old_address, new_address, version):
301 bfb3f9c2 Christos Stavrakakis
    """Change."""
302 bfb3f9c2 Christos Stavrakakis
    if old_address is not None:
303 bfb3f9c2 Christos Stavrakakis
        msg = ("IPv%s Address of server '%s' changed from '%s' to '%s'"
304 bfb3f9c2 Christos Stavrakakis
               % (version, port.machine_id, old_address, new_address))
305 bfb3f9c2 Christos Stavrakakis
        log.critical(msg)
306 bfb3f9c2 Christos Stavrakakis
307 bfb3f9c2 Christos Stavrakakis
    # Remove the old IP address
308 bfb3f9c2 Christos Stavrakakis
    remove_nic_ips(port, version=version)
309 bfb3f9c2 Christos Stavrakakis
310 bfb3f9c2 Christos Stavrakakis
    if version == 4:
311 bfb3f9c2 Christos Stavrakakis
        ipaddress = ips.allocate_ip(port.network, userid, address=new_address)
312 bfb3f9c2 Christos Stavrakakis
        ipaddress.nic = port
313 bfb3f9c2 Christos Stavrakakis
        ipaddress.save()
314 bfb3f9c2 Christos Stavrakakis
    elif version == 6:
315 bfb3f9c2 Christos Stavrakakis
        subnet6 = port.network.subnet6
316 bfb3f9c2 Christos Stavrakakis
        ipaddress = IPAddress.objects.create(userid=userid,
317 bfb3f9c2 Christos Stavrakakis
                                             network=port.network,
318 bfb3f9c2 Christos Stavrakakis
                                             subnet=subnet6,
319 bfb3f9c2 Christos Stavrakakis
                                             nic=port,
320 bfb3f9c2 Christos Stavrakakis
                                             address=new_address)
321 bfb3f9c2 Christos Stavrakakis
    else:
322 bfb3f9c2 Christos Stavrakakis
        raise ValueError("Unknown version: %s" % version)
323 bfb3f9c2 Christos Stavrakakis
324 bfb3f9c2 Christos Stavrakakis
    # New address log
325 bfb3f9c2 Christos Stavrakakis
    ip_log = IPAddressLog.objects.create(server_id=port.machine_id,
326 bfb3f9c2 Christos Stavrakakis
                                         network_id=port.network_id,
327 bfb3f9c2 Christos Stavrakakis
                                         address=new_address,
328 bfb3f9c2 Christos Stavrakakis
                                         active=True)
329 bfb3f9c2 Christos Stavrakakis
    log.info("Created IP log entry '%s' for address '%s' to server '%s'",
330 bfb3f9c2 Christos Stavrakakis
             ip_log.id, new_address, port.machine_id)
331 bfb3f9c2 Christos Stavrakakis
332 bfb3f9c2 Christos Stavrakakis
    return ipaddress
333 bfb3f9c2 Christos Stavrakakis
334 bfb3f9c2 Christos Stavrakakis
335 a1baa42b Christos Stavrakakis
def nics_are_equal(db_nic, gnt_nic):
336 a1baa42b Christos Stavrakakis
    for field in NIC_FIELDS:
337 a1baa42b Christos Stavrakakis
        if getattr(db_nic, field) != gnt_nic[field]:
338 a1baa42b Christos Stavrakakis
            return False
339 a1baa42b Christos Stavrakakis
    return True
340 a1baa42b Christos Stavrakakis
341 a1baa42b Christos Stavrakakis
342 b578d9e7 Christos Stavrakakis
def process_ganeti_nics(ganeti_nics):
343 a1baa42b Christos Stavrakakis
    """Process NIC dict from ganeti"""
344 b578d9e7 Christos Stavrakakis
    new_nics = []
345 a1baa42b Christos Stavrakakis
    for index, gnic in enumerate(ganeti_nics):
346 a1baa42b Christos Stavrakakis
        nic_name = gnic.get("name", None)
347 a1baa42b Christos Stavrakakis
        if nic_name is not None:
348 a1baa42b Christos Stavrakakis
            nic_id = utils.id_from_nic_name(nic_name)
349 a1baa42b Christos Stavrakakis
        else:
350 a1baa42b Christos Stavrakakis
            # Put as default value the index. If it is an unknown NIC to
351 a1baa42b Christos Stavrakakis
            # synnefo it will be created automaticaly.
352 0d069390 Christos Stavrakakis
            nic_id = UNKNOWN_NIC_PREFIX + str(index)
353 a1baa42b Christos Stavrakakis
        network_name = gnic.get('network', '')
354 a1baa42b Christos Stavrakakis
        network_id = utils.id_from_network_name(network_name)
355 a1baa42b Christos Stavrakakis
        network = Network.objects.get(id=network_id)
356 77f0fa63 Christos Stavrakakis
357 77f0fa63 Christos Stavrakakis
        # Get the new nic info
358 a1baa42b Christos Stavrakakis
        mac = gnic.get('mac')
359 a1baa42b Christos Stavrakakis
        ipv4 = gnic.get('ip')
360 8764d304 Christos Stavrakakis
        subnet6 = network.subnet6
361 bfb3f9c2 Christos Stavrakakis
        ipv6 = mac2eui64(mac, subnet6.cidr) if subnet6 else None
362 8d325d4b Christos Stavrakakis
363 a1baa42b Christos Stavrakakis
        firewall = gnic.get('firewall')
364 8d325d4b Christos Stavrakakis
        firewall_profile = _reverse_tags.get(firewall)
365 a1baa42b Christos Stavrakakis
        if not firewall_profile and network.public:
366 658a825a Giorgos Verigakis
            firewall_profile = settings.DEFAULT_FIREWALL_PROFILE
367 9afeb669 Kostas Papadimitriou
368 a1baa42b Christos Stavrakakis
        nic_info = {
369 a1baa42b Christos Stavrakakis
            'index': index,
370 a1baa42b Christos Stavrakakis
            'network': network,
371 cc92b70f Christos Stavrakakis
            'mac': mac,
372 8764d304 Christos Stavrakakis
            'ipv4_address': ipv4,
373 8764d304 Christos Stavrakakis
            'ipv6_address': ipv6,
374 939d71dd Christos Stavrakakis
            'firewall_profile': firewall_profile,
375 939d71dd Christos Stavrakakis
            'state': 'ACTIVE'}
376 b578d9e7 Christos Stavrakakis
377 a1baa42b Christos Stavrakakis
        new_nics.append((nic_id, nic_info))
378 a1baa42b Christos Stavrakakis
    return dict(new_nics)
379 b578d9e7 Christos Stavrakakis
380 b578d9e7 Christos Stavrakakis
381 bfb3f9c2 Christos Stavrakakis
def remove_nic_ips(nic, version=None):
382 3278725f Christos Stavrakakis
    """Remove IP addresses associated with a NetworkInterface.
383 a1baa42b Christos Stavrakakis

384 3278725f Christos Stavrakakis
    Remove all IP addresses that are associated with the NetworkInterface
385 3278725f Christos Stavrakakis
    object, by returning them to the pool and deleting the IPAddress object. If
386 3278725f Christos Stavrakakis
    the IP is a floating IP, then it is just disassociated from the NIC.
387 bfb3f9c2 Christos Stavrakakis
    If version is specified, then only IP addressses of that version will be
388 bfb3f9c2 Christos Stavrakakis
    removed.
389 a1baa42b Christos Stavrakakis

390 a1baa42b Christos Stavrakakis
    """
391 8764d304 Christos Stavrakakis
    for ip in nic.ips.all():
392 bfb3f9c2 Christos Stavrakakis
        if version and ip.ipversion != version:
393 bfb3f9c2 Christos Stavrakakis
            continue
394 bfb3f9c2 Christos Stavrakakis
395 ff863a80 Christos Stavrakakis
        # Update the DB table holding the logging of all IP addresses
396 bfb3f9c2 Christos Stavrakakis
        terminate_active_ipaddress_log(nic, ip)
397 ff863a80 Christos Stavrakakis
398 bfb3f9c2 Christos Stavrakakis
        if ip.floating_ip:
399 bfb3f9c2 Christos Stavrakakis
            ip.nic = None
400 bfb3f9c2 Christos Stavrakakis
            ip.save()
401 bfb3f9c2 Christos Stavrakakis
        else:
402 bfb3f9c2 Christos Stavrakakis
            # Release the IPv4 address
403 bfb3f9c2 Christos Stavrakakis
            ip.release_address()
404 8764d304 Christos Stavrakakis
            ip.delete()
405 bd392934 Christos Stavrakakis
406 bd392934 Christos Stavrakakis
407 bfb3f9c2 Christos Stavrakakis
def terminate_active_ipaddress_log(nic, ip):
408 ff863a80 Christos Stavrakakis
    """Update DB logging entry for this IP address."""
409 fae6e5f0 Christos Stavrakakis
    if not ip.network.public or nic.machine is None:
410 ff863a80 Christos Stavrakakis
        return
411 ff863a80 Christos Stavrakakis
    try:
412 ff863a80 Christos Stavrakakis
        ip_log, created = \
413 ff863a80 Christos Stavrakakis
            IPAddressLog.objects.get_or_create(server_id=nic.machine_id,
414 ff863a80 Christos Stavrakakis
                                               network_id=ip.network_id,
415 ff863a80 Christos Stavrakakis
                                               address=ip.address,
416 ff863a80 Christos Stavrakakis
                                               active=True)
417 ff863a80 Christos Stavrakakis
    except IPAddressLog.MultipleObjectsReturned:
418 ff863a80 Christos Stavrakakis
        logmsg = ("Multiple active log entries for IP %s, Network %s,"
419 8d5795b4 Christos Stavrakakis
                  "Server %s. Cannot proceed!"
420 ff863a80 Christos Stavrakakis
                  % (ip.address, ip.network, nic.machine))
421 ff863a80 Christos Stavrakakis
        log.error(logmsg)
422 ff863a80 Christos Stavrakakis
        raise
423 ff863a80 Christos Stavrakakis
424 ff863a80 Christos Stavrakakis
    if created:
425 ff863a80 Christos Stavrakakis
        logmsg = ("No log entry for IP %s, Network %s, Server %s. Created new"
426 ff863a80 Christos Stavrakakis
                  " but with wrong creation timestamp."
427 ff863a80 Christos Stavrakakis
                  % (ip.address, ip.network, nic.machine))
428 ff863a80 Christos Stavrakakis
        log.error(logmsg)
429 ff863a80 Christos Stavrakakis
    ip_log.released_at = datetime.now()
430 ff863a80 Christos Stavrakakis
    ip_log.active = False
431 ff863a80 Christos Stavrakakis
    ip_log.save()
432 bd392934 Christos Stavrakakis
433 bd392934 Christos Stavrakakis
434 22ee6892 Christos Stavrakakis
@transaction.commit_on_success
435 22ee6892 Christos Stavrakakis
def process_network_status(back_network, etime, jobid, opcode, status, logmsg):
436 22ee6892 Christos Stavrakakis
    if status not in [x[0] for x in BACKEND_STATUSES]:
437 fd2bdbb2 Christos Stavrakakis
        raise Network.InvalidBackendMsgError(opcode, status)
438 22ee6892 Christos Stavrakakis
439 22ee6892 Christos Stavrakakis
    back_network.backendjobid = jobid
440 22ee6892 Christos Stavrakakis
    back_network.backendjobstatus = status
441 22ee6892 Christos Stavrakakis
    back_network.backendopcode = opcode
442 22ee6892 Christos Stavrakakis
    back_network.backendlogmsg = logmsg
443 22ee6892 Christos Stavrakakis
444 c82f57ad Christos Stavrakakis
    # Note: Network is already locked!
445 05146623 Christos Stavrakakis
    network = back_network.network
446 05146623 Christos Stavrakakis
447 22ee6892 Christos Stavrakakis
    # Notifications of success change the operating state
448 22ee6892 Christos Stavrakakis
    state_for_success = BackendNetwork.OPER_STATE_FROM_OPCODE.get(opcode, None)
449 d2036274 Christos Stavrakakis
    if status == rapi.JOB_STATUS_SUCCESS and state_for_success is not None:
450 22ee6892 Christos Stavrakakis
        back_network.operstate = state_for_success
451 22ee6892 Christos Stavrakakis
452 d2036274 Christos Stavrakakis
    if (status in (rapi.JOB_STATUS_CANCELED, rapi.JOB_STATUS_ERROR)
453 d2036274 Christos Stavrakakis
       and opcode == 'OP_NETWORK_ADD'):
454 5480daec Christos Stavrakakis
        back_network.operstate = 'ERROR'
455 e97288bc Christos Stavrakakis
        back_network.backendtime = etime
456 22ee6892 Christos Stavrakakis
457 e97288bc Christos Stavrakakis
    if opcode == 'OP_NETWORK_REMOVE':
458 d2036274 Christos Stavrakakis
        network_is_deleted = (status == rapi.JOB_STATUS_SUCCESS)
459 d2036274 Christos Stavrakakis
        if network_is_deleted or (status == rapi.JOB_STATUS_ERROR and not
460 198d91c3 Christos Stavrakakis
                                  network_exists_in_backend(back_network)):
461 e97288bc Christos Stavrakakis
            back_network.operstate = state_for_success
462 e97288bc Christos Stavrakakis
            back_network.deleted = True
463 e97288bc Christos Stavrakakis
            back_network.backendtime = etime
464 22ee6892 Christos Stavrakakis
465 d2036274 Christos Stavrakakis
    if status == rapi.JOB_STATUS_SUCCESS:
466 e97288bc Christos Stavrakakis
        back_network.backendtime = etime
467 22ee6892 Christos Stavrakakis
    back_network.save()
468 4d5d0b9c Christos Stavrakakis
    # Also you must update the state of the Network!!
469 05146623 Christos Stavrakakis
    update_network_state(network)
470 fd2bdbb2 Christos Stavrakakis
471 fd2bdbb2 Christos Stavrakakis
472 2509ce17 Christos Stavrakakis
def update_network_state(network):
473 99af08a4 Christos Stavrakakis
    """Update the state of a Network based on BackendNetwork states.
474 cb4eee84 Christos Stavrakakis

475 99af08a4 Christos Stavrakakis
    Update the state of a Network based on the operstate of the networks in the
476 99af08a4 Christos Stavrakakis
    backends that network exists.
477 99af08a4 Christos Stavrakakis

478 99af08a4 Christos Stavrakakis
    The state of the network is:
479 99af08a4 Christos Stavrakakis
    * ACTIVE: If it is 'ACTIVE' in at least one backend.
480 99af08a4 Christos Stavrakakis
    * DELETED: If it is is 'DELETED' in all backends that have been created.
481 99af08a4 Christos Stavrakakis

482 99af08a4 Christos Stavrakakis
    This function also releases the resources (MAC prefix or Bridge) and the
483 99af08a4 Christos Stavrakakis
    quotas for the network.
484 99af08a4 Christos Stavrakakis

485 99af08a4 Christos Stavrakakis
    """
486 99af08a4 Christos Stavrakakis
    if network.deleted:
487 99af08a4 Christos Stavrakakis
        # Network has already been deleted. Just assert that state is also
488 99af08a4 Christos Stavrakakis
        # DELETED
489 99af08a4 Christos Stavrakakis
        if not network.state == "DELETED":
490 99af08a4 Christos Stavrakakis
            network.state = "DELETED"
491 99af08a4 Christos Stavrakakis
            network.save()
492 cb4eee84 Christos Stavrakakis
        return
493 cb4eee84 Christos Stavrakakis
494 99af08a4 Christos Stavrakakis
    backend_states = [s.operstate for s in network.backend_networks.all()]
495 27cda06b Christos Stavrakakis
    if not backend_states and network.action != "DESTROY":
496 99af08a4 Christos Stavrakakis
        if network.state != "ACTIVE":
497 99af08a4 Christos Stavrakakis
            network.state = "ACTIVE"
498 99af08a4 Christos Stavrakakis
            network.save()
499 99af08a4 Christos Stavrakakis
            return
500 99af08a4 Christos Stavrakakis
501 99af08a4 Christos Stavrakakis
    # Network is deleted when all BackendNetworks go to "DELETED" operstate
502 27cda06b Christos Stavrakakis
    deleted = reduce(lambda x, y: x == y and "DELETED", backend_states,
503 27cda06b Christos Stavrakakis
                     "DELETED")
504 99af08a4 Christos Stavrakakis
505 cb4eee84 Christos Stavrakakis
    # Release the resources on the deletion of the Network
506 99af08a4 Christos Stavrakakis
    if deleted:
507 c82f57ad Christos Stavrakakis
        if network.ips.filter(deleted=False, floating_ip=True).exists():
508 8d5795b4 Christos Stavrakakis
            msg = "Cannot delete network %s! Floating IPs still in use!"
509 c82f57ad Christos Stavrakakis
            log.error(msg % network)
510 c82f57ad Christos Stavrakakis
            raise Exception(msg % network)
511 cb4eee84 Christos Stavrakakis
        log.info("Network %r deleted. Releasing link %r mac_prefix %r",
512 cb4eee84 Christos Stavrakakis
                 network.id, network.mac_prefix, network.link)
513 cb4eee84 Christos Stavrakakis
        network.deleted = True
514 99af08a4 Christos Stavrakakis
        network.state = "DELETED"
515 b7d38981 Dimitris Aragiorgis
        if network.mac_prefix:
516 b7d38981 Dimitris Aragiorgis
            if network.FLAVORS[network.flavor]["mac_prefix"] == "pool":
517 b7d38981 Dimitris Aragiorgis
                release_resource(res_type="mac_prefix",
518 b7d38981 Dimitris Aragiorgis
                                 value=network.mac_prefix)
519 b7d38981 Dimitris Aragiorgis
        if network.link:
520 b7d38981 Dimitris Aragiorgis
            if network.FLAVORS[network.flavor]["link"] == "pool":
521 b7d38981 Dimitris Aragiorgis
                release_resource(res_type="bridge", value=network.link)
522 cb4eee84 Christos Stavrakakis
523 c82f57ad Christos Stavrakakis
        # Set all subnets as deleted
524 c82f57ad Christos Stavrakakis
        network.subnets.update(deleted=True)
525 c82f57ad Christos Stavrakakis
        # And delete the IP pools
526 c82f57ad Christos Stavrakakis
        for subnet in network.subnets.all():
527 c82f57ad Christos Stavrakakis
            if subnet.ipversion == 4:
528 c82f57ad Christos Stavrakakis
                subnet.ip_pools.all().delete()
529 e8234183 Christos Stavrakakis
        # And all the backend networks since there are useless
530 e8234183 Christos Stavrakakis
        network.backend_networks.all().delete()
531 c82f57ad Christos Stavrakakis
532 cb4eee84 Christos Stavrakakis
        # Issue commission
533 e18c1749 Christos Stavrakakis
        if network.userid:
534 368d879e Giorgos Korfiatis
            quotas.issue_and_accept_commission(network, action="DESTROY")
535 32a0b855 Giorgos Korfiatis
            # the above has already saved the object and committed;
536 32a0b855 Giorgos Korfiatis
            # a second save would override others' changes, since the
537 32a0b855 Giorgos Korfiatis
            # object is now unlocked
538 32a0b855 Giorgos Korfiatis
            return
539 e18c1749 Christos Stavrakakis
        elif not network.public:
540 e18c1749 Christos Stavrakakis
            log.warning("Network %s does not have an owner!", network.id)
541 cb4eee84 Christos Stavrakakis
    network.save()
542 cb4eee84 Christos Stavrakakis
543 cb4eee84 Christos Stavrakakis
544 fd2bdbb2 Christos Stavrakakis
@transaction.commit_on_success
545 fd2bdbb2 Christos Stavrakakis
def process_network_modify(back_network, etime, jobid, opcode, status,
546 e6fbada1 Christos Stavrakakis
                           job_fields):
547 fd2bdbb2 Christos Stavrakakis
    assert (opcode == "OP_NETWORK_SET_PARAMS")
548 fd2bdbb2 Christos Stavrakakis
    if status not in [x[0] for x in BACKEND_STATUSES]:
549 fd2bdbb2 Christos Stavrakakis
        raise Network.InvalidBackendMsgError(opcode, status)
550 fd2bdbb2 Christos Stavrakakis
551 fd2bdbb2 Christos Stavrakakis
    back_network.backendjobid = jobid
552 fd2bdbb2 Christos Stavrakakis
    back_network.backendjobstatus = status
553 fd2bdbb2 Christos Stavrakakis
    back_network.opcode = opcode
554 fd2bdbb2 Christos Stavrakakis
555 e6fbada1 Christos Stavrakakis
    add_reserved_ips = job_fields.get("add_reserved_ips")
556 fc56ae0f Christos Stavrakakis
    if add_reserved_ips:
557 3278725f Christos Stavrakakis
        network = back_network.network
558 3278725f Christos Stavrakakis
        for ip in add_reserved_ips:
559 3278725f Christos Stavrakakis
            network.reserve_address(ip, external=True)
560 fd2bdbb2 Christos Stavrakakis
561 d2036274 Christos Stavrakakis
    if status == rapi.JOB_STATUS_SUCCESS:
562 fd2bdbb2 Christos Stavrakakis
        back_network.backendtime = etime
563 fd2bdbb2 Christos Stavrakakis
    back_network.save()
564 ad2d6807 Vangelis Koukis
565 c25cc9ec Vangelis Koukis
566 9068cd85 Georgios Gousios
@transaction.commit_on_success
567 0827883e Nikos Skalkotos
def process_create_progress(vm, etime, progress):
568 9068cd85 Georgios Gousios
569 0827883e Nikos Skalkotos
    percentage = int(progress)
570 9068cd85 Georgios Gousios
571 af90d919 Vangelis Koukis
    # The percentage may exceed 100%, due to the way
572 0827883e Nikos Skalkotos
    # snf-image:copy-progress tracks bytes read by image handling processes
573 af90d919 Vangelis Koukis
    percentage = 100 if percentage > 100 else percentage
574 af90d919 Vangelis Koukis
    if percentage < 0:
575 af90d919 Vangelis Koukis
        raise ValueError("Percentage cannot be negative")
576 9068cd85 Georgios Gousios
577 7ec9558b Vangelis Koukis
    # FIXME: log a warning here, see #1033
578 7ec9558b Vangelis Koukis
#   if last_update > percentage:
579 7ec9558b Vangelis Koukis
#       raise ValueError("Build percentage should increase monotonically " \
580 7ec9558b Vangelis Koukis
#                        "(old = %d, new = %d)" % (last_update, percentage))
581 9068cd85 Georgios Gousios
582 c25cc9ec Vangelis Koukis
    # This assumes that no message of type 'ganeti-create-progress' is going to
583 c25cc9ec Vangelis Koukis
    # arrive once OP_INSTANCE_CREATE has succeeded for a Ganeti instance and
584 c25cc9ec Vangelis Koukis
    # the instance is STARTED.  What if the two messages are processed by two
585 c25cc9ec Vangelis Koukis
    # separate dispatcher threads, and the 'ganeti-op-status' message for
586 c25cc9ec Vangelis Koukis
    # successful creation gets processed before the 'ganeti-create-progress'
587 c25cc9ec Vangelis Koukis
    # message? [vkoukis]
588 c25cc9ec Vangelis Koukis
    #
589 c25cc9ec Vangelis Koukis
    #if not vm.operstate == 'BUILD':
590 c25cc9ec Vangelis Koukis
    #    raise VirtualMachine.IllegalState("VM is not in building state")
591 9068cd85 Georgios Gousios
592 c25cc9ec Vangelis Koukis
    vm.buildpercentage = percentage
593 c4e55622 Christos Stavrakakis
    vm.backendtime = etime
594 9068cd85 Georgios Gousios
    vm.save()
595 ad2d6807 Vangelis Koukis
596 c25cc9ec Vangelis Koukis
597 952b2a48 Christos Stavrakakis
@transaction.commit_on_success
598 7d43565f Kostas Papadimitriou
def create_instance_diagnostic(vm, message, source, level="DEBUG", etime=None,
599 cc92b70f Christos Stavrakakis
                               details=None):
600 7d43565f Kostas Papadimitriou
    """
601 7d43565f Kostas Papadimitriou
    Create virtual machine instance diagnostic entry.
602 7d43565f Kostas Papadimitriou

603 7d43565f Kostas Papadimitriou
    :param vm: VirtualMachine instance to create diagnostic for.
604 7d43565f Kostas Papadimitriou
    :param message: Diagnostic message.
605 7d43565f Kostas Papadimitriou
    :param source: Diagnostic source identifier (e.g. image-helper).
606 7d43565f Kostas Papadimitriou
    :param level: Diagnostic level (`DEBUG`, `INFO`, `WARNING`, `ERROR`).
607 7d43565f Kostas Papadimitriou
    :param etime: The time the message occured (if available).
608 7d43565f Kostas Papadimitriou
    :param details: Additional details or debug information.
609 7d43565f Kostas Papadimitriou
    """
610 7d43565f Kostas Papadimitriou
    VirtualMachineDiagnostic.objects.create_for_vm(vm, level, source=source,
611 cc92b70f Christos Stavrakakis
                                                   source_date=etime,
612 cc92b70f Christos Stavrakakis
                                                   message=message,
613 cc92b70f Christos Stavrakakis
                                                   details=details)
614 7d43565f Kostas Papadimitriou
615 7d43565f Kostas Papadimitriou
616 2c022086 Christos Stavrakakis
def create_instance(vm, nics, flavor, image):
617 3a9b3cde Giorgos Verigakis
    """`image` is a dictionary which should contain the keys:
618 3a9b3cde Giorgos Verigakis
            'backend_id', 'format' and 'metadata'
619 7f691719 Christos Stavrakakis

620 3a9b3cde Giorgos Verigakis
        metadata value should be a dictionary.
621 3a9b3cde Giorgos Verigakis
    """
622 864bed43 Christos Stavrakakis
623 1c382247 Vangelis Koukis
    # Handle arguments to CreateInstance() as a dictionary,
624 1c382247 Vangelis Koukis
    # initialize it based on a deployment-specific value.
625 1c382247 Vangelis Koukis
    # This enables the administrator to override deployment-specific
626 c25cc9ec Vangelis Koukis
    # arguments, such as the disk template to use, name of os provider
627 1c382247 Vangelis Koukis
    # and hypervisor-specific parameters at will (see Synnefo #785, #835).
628 1c382247 Vangelis Koukis
    #
629 bd87213f Christos Stavrakakis
    kw = vm.backend.get_create_params()
630 1c382247 Vangelis Koukis
    kw['mode'] = 'create'
631 924d8085 Christos Stavrakakis
    kw['name'] = vm.backend_vm_id
632 5949b704 Vangelis Koukis
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
633 296682fe Kostas Papadimitriou
634 2a599282 Christos Stavrakakis
    kw['disk_template'] = flavor.disk_template
635 d7841399 Christos Stavrakakis
    kw['disks'] = [{"size": flavor.disk * 1024}]
636 006c6249 Christos Stavrakakis
    provider = flavor.disk_provider
637 296682fe Kostas Papadimitriou
    if provider:
638 296682fe Kostas Papadimitriou
        kw['disks'][0]['provider'] = provider
639 b2222a7f Christos Stavrakakis
        kw['disks'][0]['origin'] = flavor.disk_origin
640 296682fe Kostas Papadimitriou
641 7c714455 Christos Stavrakakis
    kw['nics'] = [{"name": nic.backend_uuid,
642 7c714455 Christos Stavrakakis
                   "network": nic.network.backend_id,
643 92d2d1ce Christos Stavrakakis
                   "ip": nic.ipv4_address}
644 cb66110b Christos Stavrakakis
                  for nic in nics]
645 d2036274 Christos Stavrakakis
646 cb66110b Christos Stavrakakis
    backend = vm.backend
647 cb66110b Christos Stavrakakis
    depend_jobs = []
648 cb66110b Christos Stavrakakis
    for nic in nics:
649 d2036274 Christos Stavrakakis
        bnet, job_ids = ensure_network_is_active(backend, nic.network_id)
650 d2036274 Christos Stavrakakis
        depend_jobs.extend(job_ids)
651 d2036274 Christos Stavrakakis
652 d2036274 Christos Stavrakakis
    kw["depends"] = create_job_dependencies(depend_jobs)
653 cb66110b Christos Stavrakakis
654 5949b704 Vangelis Koukis
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
655 1c382247 Vangelis Koukis
    # kw['os'] = settings.GANETI_OS_PROVIDER
656 1c382247 Vangelis Koukis
    kw['ip_check'] = False
657 1c382247 Vangelis Koukis
    kw['name_check'] = False
658 79b7dbb7 Christos Stavrakakis
659 1c382247 Vangelis Koukis
    # Do not specific a node explicitly, have
660 1c382247 Vangelis Koukis
    # Ganeti use an iallocator instead
661 f8d1eaf4 Kostas Papadimitriou
    #kw['pnode'] = rapi.GetNodes()[0]
662 79b7dbb7 Christos Stavrakakis
663 1c382247 Vangelis Koukis
    kw['dry_run'] = settings.TEST
664 41303ed0 Vangelis Koukis
665 2b1db26f Giorgos Verigakis
    kw['beparams'] = {
666 cc92b70f Christos Stavrakakis
        'auto_balance': True,
667 cc92b70f Christos Stavrakakis
        'vcpus': flavor.cpu,
668 cc92b70f Christos Stavrakakis
        'memory': flavor.ram}
669 79b7dbb7 Christos Stavrakakis
670 e3b5be49 Giorgos Verigakis
    kw['osparams'] = {
671 79b7dbb7 Christos Stavrakakis
        'config_url': vm.config_url,
672 79b7dbb7 Christos Stavrakakis
        # Store image id and format to Ganeti
673 2a599282 Christos Stavrakakis
        'img_id': image['backend_id'],
674 3a9b3cde Giorgos Verigakis
        'img_format': image['format']}
675 d1eaa651 Christos Stavrakakis
676 e9d59f5e Christos Stavrakakis
    # Use opportunistic locking
677 727fb2f9 Christos Stavrakakis
    kw['opportunistic_locking'] = settings.GANETI_USE_OPPORTUNISTIC_LOCKING
678 e9d59f5e Christos Stavrakakis
679 5949b704 Vangelis Koukis
    # Defined in settings.GANETI_CREATEINSTANCE_KWARGS
680 1c382247 Vangelis Koukis
    # kw['hvparams'] = dict(serial_console=False)
681 79b7dbb7 Christos Stavrakakis
682 6afeb85d Christos Stavrakakis
    log.debug("Creating instance %s", utils.hide_pass(kw))
683 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
684 3524241a Christos Stavrakakis
        return client.CreateInstance(**kw)
685 f533f224 Vangelis Koukis
686 529178b1 Giorgos Verigakis
687 529178b1 Giorgos Verigakis
def delete_instance(vm):
688 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
689 c8914c67 Christos Stavrakakis
        return client.DeleteInstance(vm.backend_vm_id, dry_run=settings.TEST)
690 529178b1 Giorgos Verigakis
691 ad2d6807 Vangelis Koukis
692 529178b1 Giorgos Verigakis
def reboot_instance(vm, reboot_type):
693 529178b1 Giorgos Verigakis
    assert reboot_type in ('soft', 'hard')
694 bbae3e45 Christos Stavrakakis
    kwargs = {"instance": vm.backend_vm_id,
695 bbae3e45 Christos Stavrakakis
              "reboot_type": "hard"}
696 bbae3e45 Christos Stavrakakis
    # XXX: Currently shutdown_timeout parameter is not supported from the
697 bbae3e45 Christos Stavrakakis
    # Ganeti RAPI. Until supported, we will fallback for both reboot types
698 bbae3e45 Christos Stavrakakis
    # to the default shutdown timeout of Ganeti (120s). Note that reboot
699 bbae3e45 Christos Stavrakakis
    # type of Ganeti job must be always hard. The 'soft' and 'hard' type
700 bbae3e45 Christos Stavrakakis
    # of OS API is different from the one in Ganeti, and maps to
701 bbae3e45 Christos Stavrakakis
    # 'shutdown_timeout'.
702 bbae3e45 Christos Stavrakakis
    #if reboot_type == "hard":
703 bbae3e45 Christos Stavrakakis
    #    kwargs["shutdown_timeout"] = 0
704 bbae3e45 Christos Stavrakakis
    if settings.TEST:
705 bbae3e45 Christos Stavrakakis
        kwargs["dry_run"] = True
706 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
707 bbae3e45 Christos Stavrakakis
        return client.RebootInstance(**kwargs)
708 529178b1 Giorgos Verigakis
709 ad2d6807 Vangelis Koukis
710 529178b1 Giorgos Verigakis
def startup_instance(vm):
711 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
712 3524241a Christos Stavrakakis
        return client.StartupInstance(vm.backend_vm_id, dry_run=settings.TEST)
713 529178b1 Giorgos Verigakis
714 ad2d6807 Vangelis Koukis
715 529178b1 Giorgos Verigakis
def shutdown_instance(vm):
716 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
717 3524241a Christos Stavrakakis
        return client.ShutdownInstance(vm.backend_vm_id, dry_run=settings.TEST)
718 529178b1 Giorgos Verigakis
719 ad2d6807 Vangelis Koukis
720 2cd3f389 Christos Stavrakakis
def resize_instance(vm, vcpus, memory):
721 2cd3f389 Christos Stavrakakis
    beparams = {"vcpus": int(vcpus),
722 2cd3f389 Christos Stavrakakis
                "minmem": int(memory),
723 2cd3f389 Christos Stavrakakis
                "maxmem": int(memory)}
724 2cd3f389 Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
725 2cd3f389 Christos Stavrakakis
        return client.ModifyInstance(vm.backend_vm_id, beparams=beparams)
726 2cd3f389 Christos Stavrakakis
727 2cd3f389 Christos Stavrakakis
728 529178b1 Giorgos Verigakis
def get_instance_console(vm):
729 71099804 Vangelis Koukis
    # RAPI GetInstanceConsole() returns endpoints to the vnc_bind_address,
730 71099804 Vangelis Koukis
    # which is a cluster-wide setting, either 0.0.0.0 or 127.0.0.1, and pretty
731 71099804 Vangelis Koukis
    # useless (see #783).
732 71099804 Vangelis Koukis
    #
733 71099804 Vangelis Koukis
    # Until this is fixed on the Ganeti side, construct a console info reply
734 71099804 Vangelis Koukis
    # directly.
735 9afeb669 Kostas Papadimitriou
    #
736 71099804 Vangelis Koukis
    # WARNING: This assumes that VNC runs on port network_port on
737 71099804 Vangelis Koukis
    #          the instance's primary node, and is probably
738 71099804 Vangelis Koukis
    #          hypervisor-specific.
739 71099804 Vangelis Koukis
    #
740 bf5c82dc Christos Stavrakakis
    log.debug("Getting console for vm %s", vm)
741 bf5c82dc Christos Stavrakakis
742 71099804 Vangelis Koukis
    console = {}
743 71099804 Vangelis Koukis
    console['kind'] = 'vnc'
744 3524241a Christos Stavrakakis
745 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
746 3524241a Christos Stavrakakis
        i = client.GetInstance(vm.backend_vm_id)
747 3524241a Christos Stavrakakis
748 bd87213f Christos Stavrakakis
    if vm.backend.hypervisor == "kvm" and i['hvparams']['serial_console']:
749 71099804 Vangelis Koukis
        raise Exception("hv parameter serial_console cannot be true")
750 71099804 Vangelis Koukis
    console['host'] = i['pnode']
751 71099804 Vangelis Koukis
    console['port'] = i['network_port']
752 9afeb669 Kostas Papadimitriou
753 71099804 Vangelis Koukis
    return console
754 604b2bf8 Georgios Gousios
755 604b2bf8 Georgios Gousios
756 3524241a Christos Stavrakakis
def get_instance_info(vm):
757 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
758 198d91c3 Christos Stavrakakis
        return client.GetInstance(vm.backend_vm_id)
759 198d91c3 Christos Stavrakakis
760 198d91c3 Christos Stavrakakis
761 198d91c3 Christos Stavrakakis
def vm_exists_in_backend(vm):
762 198d91c3 Christos Stavrakakis
    try:
763 198d91c3 Christos Stavrakakis
        get_instance_info(vm)
764 198d91c3 Christos Stavrakakis
        return True
765 d2036274 Christos Stavrakakis
    except rapi.GanetiApiError as e:
766 198d91c3 Christos Stavrakakis
        if e.code == 404:
767 198d91c3 Christos Stavrakakis
            return False
768 198d91c3 Christos Stavrakakis
        raise e
769 198d91c3 Christos Stavrakakis
770 198d91c3 Christos Stavrakakis
771 198d91c3 Christos Stavrakakis
def get_network_info(backend_network):
772 198d91c3 Christos Stavrakakis
    with pooled_rapi_client(backend_network) as client:
773 198d91c3 Christos Stavrakakis
        return client.GetNetwork(backend_network.network.backend_id)
774 198d91c3 Christos Stavrakakis
775 198d91c3 Christos Stavrakakis
776 198d91c3 Christos Stavrakakis
def network_exists_in_backend(backend_network):
777 198d91c3 Christos Stavrakakis
    try:
778 198d91c3 Christos Stavrakakis
        get_network_info(backend_network)
779 198d91c3 Christos Stavrakakis
        return True
780 d2036274 Christos Stavrakakis
    except rapi.GanetiApiError as e:
781 198d91c3 Christos Stavrakakis
        if e.code == 404:
782 198d91c3 Christos Stavrakakis
            return False
783 f533f224 Vangelis Koukis
784 c25cc9ec Vangelis Koukis
785 01f5f8d9 Christos Stavrakakis
def job_is_still_running(vm):
786 01f5f8d9 Christos Stavrakakis
    with pooled_rapi_client(vm) as c:
787 01f5f8d9 Christos Stavrakakis
        try:
788 01f5f8d9 Christos Stavrakakis
            job_info = c.GetJobStatus(vm.backendjobid)
789 a1dae38d Christos Stavrakakis
            return not (job_info["status"] in rapi.JOB_STATUS_FINALIZED)
790 a1dae38d Christos Stavrakakis
        except rapi.GanetiApiError:
791 01f5f8d9 Christos Stavrakakis
            return False
792 01f5f8d9 Christos Stavrakakis
793 01f5f8d9 Christos Stavrakakis
794 d2036274 Christos Stavrakakis
def ensure_network_is_active(backend, network_id):
795 d2036274 Christos Stavrakakis
    """Ensure that a network is active in the specified backend
796 d2036274 Christos Stavrakakis

797 d2036274 Christos Stavrakakis
    Check that a network exists and is active in the specified backend. If not
798 d2036274 Christos Stavrakakis
    (re-)create the network. Return the corresponding BackendNetwork object
799 d2036274 Christos Stavrakakis
    and the IDs of the Ganeti job to create the network.
800 d2036274 Christos Stavrakakis

801 d2036274 Christos Stavrakakis
    """
802 d2036274 Christos Stavrakakis
    network = Network.objects.select_for_update().get(id=network_id)
803 d2036274 Christos Stavrakakis
    bnet, created = BackendNetwork.objects.get_or_create(backend=backend,
804 d2036274 Christos Stavrakakis
                                                         network=network)
805 d2036274 Christos Stavrakakis
    job_ids = []
806 d2036274 Christos Stavrakakis
    if bnet.operstate != "ACTIVE":
807 d2036274 Christos Stavrakakis
        job_ids = create_network(network, backend, connect=True)
808 d2036274 Christos Stavrakakis
809 d2036274 Christos Stavrakakis
    return bnet, job_ids
810 d2036274 Christos Stavrakakis
811 d2036274 Christos Stavrakakis
812 99af08a4 Christos Stavrakakis
def create_network(network, backend, connect=True):
813 99af08a4 Christos Stavrakakis
    """Create a network in a Ganeti backend"""
814 99af08a4 Christos Stavrakakis
    log.debug("Creating network %s in backend %s", network, backend)
815 64938cb0 Giorgos Verigakis
816 99af08a4 Christos Stavrakakis
    job_id = _create_network(network, backend)
817 c25cc9ec Vangelis Koukis
818 99af08a4 Christos Stavrakakis
    if connect:
819 99af08a4 Christos Stavrakakis
        job_ids = connect_network(network, backend, depends=[job_id])
820 99af08a4 Christos Stavrakakis
        return job_ids
821 99af08a4 Christos Stavrakakis
    else:
822 99af08a4 Christos Stavrakakis
        return [job_id]
823 37ca953f Christodoulos Psaltis
824 64938cb0 Giorgos Verigakis
825 3524241a Christos Stavrakakis
def _create_network(network, backend):
826 3524241a Christos Stavrakakis
    """Create a network."""
827 c25cc9ec Vangelis Koukis
828 22ee6892 Christos Stavrakakis
    tags = network.backend_tag
829 92d2d1ce Christos Stavrakakis
    subnet = None
830 92d2d1ce Christos Stavrakakis
    subnet6 = None
831 92d2d1ce Christos Stavrakakis
    gateway = None
832 92d2d1ce Christos Stavrakakis
    gateway6 = None
833 3278725f Christos Stavrakakis
    for _subnet in network.subnets.all():
834 a2bd0802 Christos Stavrakakis
        if _subnet.dhcp and not "nfdhcpd" in tags:
835 a2bd0802 Christos Stavrakakis
            tags.append("nfdhcpd")
836 3278725f Christos Stavrakakis
        if _subnet.ipversion == 4:
837 fe3b2809 Christos Stavrakakis
            subnet = _subnet.cidr
838 fe3b2809 Christos Stavrakakis
            gateway = _subnet.gateway
839 3278725f Christos Stavrakakis
        elif _subnet.ipversion == 6:
840 fe3b2809 Christos Stavrakakis
            subnet6 = _subnet.cidr
841 fe3b2809 Christos Stavrakakis
            gateway6 = _subnet.gateway
842 2d762302 Dimitris Aragiorgis
843 2d762302 Dimitris Aragiorgis
    if network.public:
844 2d762302 Dimitris Aragiorgis
        conflicts_check = True
845 700b85be Dimitris Aragiorgis
        tags.append('public')
846 2d762302 Dimitris Aragiorgis
    else:
847 2d762302 Dimitris Aragiorgis
        conflicts_check = False
848 700b85be Dimitris Aragiorgis
        tags.append('private')
849 22ee6892 Christos Stavrakakis
850 5aeb4e93 Christos Stavrakakis
    # Use a dummy network subnet for IPv6 only networks. Currently Ganeti does
851 5aeb4e93 Christos Stavrakakis
    # not support IPv6 only networks. To bypass this limitation, we create the
852 5aeb4e93 Christos Stavrakakis
    # network with a dummy network subnet, and make Cyclades connect instances
853 5aeb4e93 Christos Stavrakakis
    # to such networks, with address=None.
854 5aeb4e93 Christos Stavrakakis
    if subnet is None:
855 c12627c7 Christos Stavrakakis
        subnet = "10.0.0.0/29"
856 5aeb4e93 Christos Stavrakakis
857 3524241a Christos Stavrakakis
    try:
858 3524241a Christos Stavrakakis
        bn = BackendNetwork.objects.get(network=network, backend=backend)
859 3524241a Christos Stavrakakis
        mac_prefix = bn.mac_prefix
860 3524241a Christos Stavrakakis
    except BackendNetwork.DoesNotExist:
861 cc92b70f Christos Stavrakakis
        raise Exception("BackendNetwork for network '%s' in backend '%s'"
862 3524241a Christos Stavrakakis
                        " does not exist" % (network.id, backend.id))
863 3524241a Christos Stavrakakis
864 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
865 3524241a Christos Stavrakakis
        return client.CreateNetwork(network_name=network.backend_id,
866 5aeb4e93 Christos Stavrakakis
                                    network=subnet,
867 92d2d1ce Christos Stavrakakis
                                    network6=subnet6,
868 92d2d1ce Christos Stavrakakis
                                    gateway=gateway,
869 92d2d1ce Christos Stavrakakis
                                    gateway6=gateway6,
870 3524241a Christos Stavrakakis
                                    mac_prefix=mac_prefix,
871 2d762302 Dimitris Aragiorgis
                                    conflicts_check=conflicts_check,
872 3524241a Christos Stavrakakis
                                    tags=tags)
873 3524241a Christos Stavrakakis
874 3524241a Christos Stavrakakis
875 99af08a4 Christos Stavrakakis
def connect_network(network, backend, depends=[], group=None):
876 3524241a Christos Stavrakakis
    """Connect a network to nodegroups."""
877 bf5c82dc Christos Stavrakakis
    log.debug("Connecting network %s to backend %s", network, backend)
878 bf5c82dc Christos Stavrakakis
879 2d762302 Dimitris Aragiorgis
    if network.public:
880 2d762302 Dimitris Aragiorgis
        conflicts_check = True
881 2d762302 Dimitris Aragiorgis
    else:
882 2d762302 Dimitris Aragiorgis
        conflicts_check = False
883 2d762302 Dimitris Aragiorgis
884 d2036274 Christos Stavrakakis
    depends = create_job_dependencies(depends)
885 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
886 99af08a4 Christos Stavrakakis
        groups = [group] if group is not None else client.GetGroups()
887 99af08a4 Christos Stavrakakis
        job_ids = []
888 99af08a4 Christos Stavrakakis
        for group in groups:
889 99af08a4 Christos Stavrakakis
            job_id = client.ConnectNetwork(network.backend_id, group,
890 99af08a4 Christos Stavrakakis
                                           network.mode, network.link,
891 99af08a4 Christos Stavrakakis
                                           conflicts_check,
892 99af08a4 Christos Stavrakakis
                                           depends=depends)
893 99af08a4 Christos Stavrakakis
            job_ids.append(job_id)
894 99af08a4 Christos Stavrakakis
    return job_ids
895 22ee6892 Christos Stavrakakis
896 22ee6892 Christos Stavrakakis
897 99af08a4 Christos Stavrakakis
def delete_network(network, backend, disconnect=True):
898 99af08a4 Christos Stavrakakis
    log.debug("Deleting network %s from backend %s", network, backend)
899 22ee6892 Christos Stavrakakis
900 99af08a4 Christos Stavrakakis
    depends = []
901 99af08a4 Christos Stavrakakis
    if disconnect:
902 99af08a4 Christos Stavrakakis
        depends = disconnect_network(network, backend)
903 99af08a4 Christos Stavrakakis
    _delete_network(network, backend, depends=depends)
904 bf5c82dc Christos Stavrakakis
905 22ee6892 Christos Stavrakakis
906 99af08a4 Christos Stavrakakis
def _delete_network(network, backend, depends=[]):
907 d2036274 Christos Stavrakakis
    depends = create_job_dependencies(depends)
908 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
909 99af08a4 Christos Stavrakakis
        return client.DeleteNetwork(network.backend_id, depends)
910 22ee6892 Christos Stavrakakis
911 22ee6892 Christos Stavrakakis
912 3524241a Christos Stavrakakis
def disconnect_network(network, backend, group=None):
913 bf5c82dc Christos Stavrakakis
    log.debug("Disconnecting network %s to backend %s", network, backend)
914 22ee6892 Christos Stavrakakis
915 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
916 99af08a4 Christos Stavrakakis
        groups = [group] if group is not None else client.GetGroups()
917 99af08a4 Christos Stavrakakis
        job_ids = []
918 99af08a4 Christos Stavrakakis
        for group in groups:
919 99af08a4 Christos Stavrakakis
            job_id = client.DisconnectNetwork(network.backend_id, group)
920 99af08a4 Christos Stavrakakis
            job_ids.append(job_id)
921 99af08a4 Christos Stavrakakis
    return job_ids
922 36f4cb29 Christos Stavrakakis
923 36f4cb29 Christos Stavrakakis
924 2a2b01e5 Christos Stavrakakis
def connect_to_network(vm, nic):
925 2a2b01e5 Christos Stavrakakis
    network = nic.network
926 99af08a4 Christos Stavrakakis
    backend = vm.backend
927 d2036274 Christos Stavrakakis
    bnet, depend_jobs = ensure_network_is_active(backend, network.id)
928 99af08a4 Christos Stavrakakis
929 d2036274 Christos Stavrakakis
    depends = create_job_dependencies(depend_jobs)
930 99af08a4 Christos Stavrakakis
931 7c714455 Christos Stavrakakis
    nic = {'name': nic.backend_uuid,
932 7c714455 Christos Stavrakakis
           'network': network.backend_id,
933 8764d304 Christos Stavrakakis
           'ip': nic.ipv4_address}
934 22ee6892 Christos Stavrakakis
935 7c714455 Christos Stavrakakis
    log.debug("Adding NIC %s to VM %s", nic, vm)
936 22ee6892 Christos Stavrakakis
937 6488097c Christos Stavrakakis
    kwargs = {
938 6488097c Christos Stavrakakis
        "instance": vm.backend_vm_id,
939 1d74f135 Dimitris Aragiorgis
        "nics": [("add", "-1", nic)],
940 6488097c Christos Stavrakakis
        "depends": depends,
941 6488097c Christos Stavrakakis
    }
942 6488097c Christos Stavrakakis
    if vm.backend.use_hotplug():
943 6488097c Christos Stavrakakis
        kwargs["hotplug"] = True
944 6488097c Christos Stavrakakis
    if settings.TEST:
945 6488097c Christos Stavrakakis
        kwargs["dry_run"] = True
946 6488097c Christos Stavrakakis
947 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
948 6488097c Christos Stavrakakis
        return client.ModifyInstance(**kwargs)
949 22ee6892 Christos Stavrakakis
950 22ee6892 Christos Stavrakakis
951 3524241a Christos Stavrakakis
def disconnect_from_network(vm, nic):
952 7c714455 Christos Stavrakakis
    log.debug("Removing NIC %s of VM %s", nic, vm)
953 22ee6892 Christos Stavrakakis
954 6488097c Christos Stavrakakis
    kwargs = {
955 6488097c Christos Stavrakakis
        "instance": vm.backend_vm_id,
956 cd7ed999 Christos Stavrakakis
        "nics": [("remove", nic.backend_uuid, {})],
957 6488097c Christos Stavrakakis
    }
958 6488097c Christos Stavrakakis
    if vm.backend.use_hotplug():
959 6488097c Christos Stavrakakis
        kwargs["hotplug"] = True
960 6488097c Christos Stavrakakis
    if settings.TEST:
961 6488097c Christos Stavrakakis
        kwargs["dry_run"] = True
962 6488097c Christos Stavrakakis
963 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
964 6488097c Christos Stavrakakis
        jobID = client.ModifyInstance(**kwargs)
965 27435d42 Christos Stavrakakis
        firewall_profile = nic.firewall_profile
966 231b0fb6 Christos Stavrakakis
        if firewall_profile and firewall_profile != "DISABLED":
967 d0545590 Christos Stavrakakis
            tag = _firewall_tags[firewall_profile] % nic.backend_uuid
968 27435d42 Christos Stavrakakis
            client.DeleteInstanceTags(vm.backend_vm_id, [tag],
969 27435d42 Christos Stavrakakis
                                      dry_run=settings.TEST)
970 27435d42 Christos Stavrakakis
971 27435d42 Christos Stavrakakis
        return jobID
972 91826390 Giorgos Verigakis
973 c25cc9ec Vangelis Koukis
974 d0545590 Christos Stavrakakis
def set_firewall_profile(vm, profile, nic):
975 d0545590 Christos Stavrakakis
    uuid = nic.backend_uuid
976 26563957 Giorgos Verigakis
    try:
977 d0545590 Christos Stavrakakis
        tag = _firewall_tags[profile] % uuid
978 26563957 Giorgos Verigakis
    except KeyError:
979 91826390 Giorgos Verigakis
        raise ValueError("Unsopported Firewall Profile: %s" % profile)
980 37ca953f Christodoulos Psaltis
981 d0545590 Christos Stavrakakis
    log.debug("Setting tag of VM %s, NIC %s, to %s", vm, nic, profile)
982 bf5c82dc Christos Stavrakakis
983 3524241a Christos Stavrakakis
    with pooled_rapi_client(vm) as client:
984 b2791a77 Christos Stavrakakis
        # Delete previous firewall tags
985 b2791a77 Christos Stavrakakis
        old_tags = client.GetInstanceTags(vm.backend_vm_id)
986 d0545590 Christos Stavrakakis
        delete_tags = [(t % uuid) for t in _firewall_tags.values()
987 d0545590 Christos Stavrakakis
                       if (t % uuid) in old_tags]
988 b2791a77 Christos Stavrakakis
        if delete_tags:
989 b2791a77 Christos Stavrakakis
            client.DeleteInstanceTags(vm.backend_vm_id, delete_tags,
990 3524241a Christos Stavrakakis
                                      dry_run=settings.TEST)
991 37ca953f Christodoulos Psaltis
992 27435d42 Christos Stavrakakis
        if profile != "DISABLED":
993 27435d42 Christos Stavrakakis
            client.AddInstanceTags(vm.backend_vm_id, [tag],
994 27435d42 Christos Stavrakakis
                                   dry_run=settings.TEST)
995 9afeb669 Kostas Papadimitriou
996 3524241a Christos Stavrakakis
        # XXX NOP ModifyInstance call to force process_net_status to run
997 3524241a Christos Stavrakakis
        # on the dispatcher
998 cc92b70f Christos Stavrakakis
        os_name = settings.GANETI_CREATEINSTANCE_KWARGS['os']
999 3524241a Christos Stavrakakis
        client.ModifyInstance(vm.backend_vm_id,
1000 cc92b70f Christos Stavrakakis
                              os_name=os_name)
1001 41a7fae7 Christos Stavrakakis
    return None
1002 5eedb0e4 Vangelis Koukis
1003 41303ed0 Vangelis Koukis
1004 e77a29ab Christos Stavrakakis
def get_instances(backend, bulk=True):
1005 e77a29ab Christos Stavrakakis
    with pooled_rapi_client(backend) as c:
1006 e77a29ab Christos Stavrakakis
        return c.GetInstances(bulk=bulk)
1007 d986cb32 Christos Stavrakakis
1008 f5b4f2a3 Christos Stavrakakis
1009 e77a29ab Christos Stavrakakis
def get_nodes(backend, bulk=True):
1010 e77a29ab Christos Stavrakakis
    with pooled_rapi_client(backend) as c:
1011 e77a29ab Christos Stavrakakis
        return c.GetNodes(bulk=bulk)
1012 1a894bfe Christos Stavrakakis
1013 1a894bfe Christos Stavrakakis
1014 70a0afab Christos Stavrakakis
def get_jobs(backend, bulk=True):
1015 e77a29ab Christos Stavrakakis
    with pooled_rapi_client(backend) as c:
1016 70a0afab Christos Stavrakakis
        return c.GetJobs(bulk=bulk)
1017 f5b4f2a3 Christos Stavrakakis
1018 17852fe9 Giorgos Verigakis
1019 1a894bfe Christos Stavrakakis
def get_physical_resources(backend):
1020 1a894bfe Christos Stavrakakis
    """ Get the physical resources of a backend.
1021 1a894bfe Christos Stavrakakis

1022 1a894bfe Christos Stavrakakis
    Get the resources of a backend as reported by the backend (not the db).
1023 41303ed0 Vangelis Koukis

1024 1a894bfe Christos Stavrakakis
    """
1025 e77a29ab Christos Stavrakakis
    nodes = get_nodes(backend, bulk=True)
1026 1a894bfe Christos Stavrakakis
    attr = ['mfree', 'mtotal', 'dfree', 'dtotal', 'pinst_cnt', 'ctotal']
1027 1a894bfe Christos Stavrakakis
    res = {}
1028 1a894bfe Christos Stavrakakis
    for a in attr:
1029 1a894bfe Christos Stavrakakis
        res[a] = 0
1030 1a894bfe Christos Stavrakakis
    for n in nodes:
1031 1a894bfe Christos Stavrakakis
        # Filter out drained, offline and not vm_capable nodes since they will
1032 1a894bfe Christos Stavrakakis
        # not take part in the vm allocation process
1033 cc92b70f Christos Stavrakakis
        can_host_vms = n['vm_capable'] and not (n['drained'] or n['offline'])
1034 cc92b70f Christos Stavrakakis
        if can_host_vms and n['cnodes']:
1035 1a894bfe Christos Stavrakakis
            for a in attr:
1036 121b8921 Christos Stavrakakis
                res[a] += int(n[a] or 0)
1037 1a894bfe Christos Stavrakakis
    return res
1038 1a894bfe Christos Stavrakakis
1039 1a894bfe Christos Stavrakakis
1040 1da50fe3 Christos Stavrakakis
def update_backend_resources(backend, resources=None):
1041 1a894bfe Christos Stavrakakis
    """ Update the state of the backend resources in db.
1042 1a894bfe Christos Stavrakakis

1043 1a894bfe Christos Stavrakakis
    """
1044 17852fe9 Giorgos Verigakis
1045 1a894bfe Christos Stavrakakis
    if not resources:
1046 1a894bfe Christos Stavrakakis
        resources = get_physical_resources(backend)
1047 41303ed0 Vangelis Koukis
1048 1a894bfe Christos Stavrakakis
    backend.mfree = resources['mfree']
1049 1a894bfe Christos Stavrakakis
    backend.mtotal = resources['mtotal']
1050 1a894bfe Christos Stavrakakis
    backend.dfree = resources['dfree']
1051 1a894bfe Christos Stavrakakis
    backend.dtotal = resources['dtotal']
1052 1a894bfe Christos Stavrakakis
    backend.pinst_cnt = resources['pinst_cnt']
1053 1a894bfe Christos Stavrakakis
    backend.ctotal = resources['ctotal']
1054 1a894bfe Christos Stavrakakis
    backend.updated = datetime.now()
1055 1a894bfe Christos Stavrakakis
    backend.save()
1056 1a894bfe Christos Stavrakakis
1057 1a894bfe Christos Stavrakakis
1058 1a894bfe Christos Stavrakakis
def get_memory_from_instances(backend):
1059 1a894bfe Christos Stavrakakis
    """ Get the memory that is used from instances.
1060 1a894bfe Christos Stavrakakis

1061 1a894bfe Christos Stavrakakis
    Get the used memory of a backend. Note: This is different for
1062 1a894bfe Christos Stavrakakis
    the real memory used, due to kvm's memory de-duplication.
1063 1a894bfe Christos Stavrakakis

1064 1a894bfe Christos Stavrakakis
    """
1065 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1066 3524241a Christos Stavrakakis
        instances = client.GetInstances(bulk=True)
1067 1a894bfe Christos Stavrakakis
    mem = 0
1068 1a894bfe Christos Stavrakakis
    for i in instances:
1069 1a894bfe Christos Stavrakakis
        mem += i['oper_ram']
1070 1a894bfe Christos Stavrakakis
    return mem
1071 b3d28af2 Christos Stavrakakis
1072 1da50fe3 Christos Stavrakakis
1073 1da50fe3 Christos Stavrakakis
def get_available_disk_templates(backend):
1074 1da50fe3 Christos Stavrakakis
    """Get the list of available disk templates of a Ganeti backend.
1075 1da50fe3 Christos Stavrakakis

1076 1da50fe3 Christos Stavrakakis
    The list contains the disk templates that are enabled in the Ganeti backend
1077 1da50fe3 Christos Stavrakakis
    and also included in ipolicy-disk-templates.
1078 1da50fe3 Christos Stavrakakis

1079 1da50fe3 Christos Stavrakakis
    """
1080 1da50fe3 Christos Stavrakakis
    with pooled_rapi_client(backend) as c:
1081 1da50fe3 Christos Stavrakakis
        info = c.GetInfo()
1082 1da50fe3 Christos Stavrakakis
    ipolicy_disk_templates = info["ipolicy"]["disk-templates"]
1083 a8ae6989 Christos Stavrakakis
    try:
1084 a8ae6989 Christos Stavrakakis
        enabled_disk_templates = info["enabled_disk_templates"]
1085 a8ae6989 Christos Stavrakakis
        return [dp for dp in enabled_disk_templates
1086 a8ae6989 Christos Stavrakakis
                if dp in ipolicy_disk_templates]
1087 a8ae6989 Christos Stavrakakis
    except KeyError:
1088 a8ae6989 Christos Stavrakakis
        # Ganeti < 2.8 does not have 'enabled_disk_templates'
1089 a8ae6989 Christos Stavrakakis
        return ipolicy_disk_templates
1090 1da50fe3 Christos Stavrakakis
1091 1da50fe3 Christos Stavrakakis
1092 1da50fe3 Christos Stavrakakis
def update_backend_disk_templates(backend):
1093 1da50fe3 Christos Stavrakakis
    disk_templates = get_available_disk_templates(backend)
1094 1da50fe3 Christos Stavrakakis
    backend.disk_templates = disk_templates
1095 1da50fe3 Christos Stavrakakis
    backend.save()
1096 1da50fe3 Christos Stavrakakis
1097 1da50fe3 Christos Stavrakakis
1098 b3d28af2 Christos Stavrakakis
##
1099 b3d28af2 Christos Stavrakakis
## Synchronized operations for reconciliation
1100 b3d28af2 Christos Stavrakakis
##
1101 b3d28af2 Christos Stavrakakis
1102 b3d28af2 Christos Stavrakakis
1103 b3d28af2 Christos Stavrakakis
def create_network_synced(network, backend):
1104 b3d28af2 Christos Stavrakakis
    result = _create_network_synced(network, backend)
1105 d2036274 Christos Stavrakakis
    if result[0] != rapi.JOB_STATUS_SUCCESS:
1106 b3d28af2 Christos Stavrakakis
        return result
1107 b3d28af2 Christos Stavrakakis
    result = connect_network_synced(network, backend)
1108 b3d28af2 Christos Stavrakakis
    return result
1109 b3d28af2 Christos Stavrakakis
1110 b3d28af2 Christos Stavrakakis
1111 b3d28af2 Christos Stavrakakis
def _create_network_synced(network, backend):
1112 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1113 e60b4630 Christos Stavrakakis
        job = _create_network(network, backend)
1114 3524241a Christos Stavrakakis
        result = wait_for_job(client, job)
1115 3524241a Christos Stavrakakis
    return result
1116 b3d28af2 Christos Stavrakakis
1117 b3d28af2 Christos Stavrakakis
1118 b3d28af2 Christos Stavrakakis
def connect_network_synced(network, backend):
1119 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1120 3524241a Christos Stavrakakis
        for group in client.GetGroups():
1121 b7d38981 Dimitris Aragiorgis
            job = client.ConnectNetwork(network.backend_id, group,
1122 b7d38981 Dimitris Aragiorgis
                                        network.mode, network.link)
1123 3524241a Christos Stavrakakis
            result = wait_for_job(client, job)
1124 d2036274 Christos Stavrakakis
            if result[0] != rapi.JOB_STATUS_SUCCESS:
1125 3524241a Christos Stavrakakis
                return result
1126 b3d28af2 Christos Stavrakakis
1127 b3d28af2 Christos Stavrakakis
    return result
1128 b3d28af2 Christos Stavrakakis
1129 b3d28af2 Christos Stavrakakis
1130 b3d28af2 Christos Stavrakakis
def wait_for_job(client, jobid):
1131 b3d28af2 Christos Stavrakakis
    result = client.WaitForJobChange(jobid, ['status', 'opresult'], None, None)
1132 b3d28af2 Christos Stavrakakis
    status = result['job_info'][0]
1133 d2036274 Christos Stavrakakis
    while status not in rapi.JOB_STATUS_FINALIZED:
1134 b3d28af2 Christos Stavrakakis
        result = client.WaitForJobChange(jobid, ['status', 'opresult'],
1135 cc92b70f Christos Stavrakakis
                                         [result], None)
1136 b3d28af2 Christos Stavrakakis
        status = result['job_info'][0]
1137 b3d28af2 Christos Stavrakakis
1138 d2036274 Christos Stavrakakis
    if status == rapi.JOB_STATUS_SUCCESS:
1139 b3d28af2 Christos Stavrakakis
        return (status, None)
1140 b3d28af2 Christos Stavrakakis
    else:
1141 b3d28af2 Christos Stavrakakis
        error = result['job_info'][1]
1142 b3d28af2 Christos Stavrakakis
        return (status, error)
1143 d2036274 Christos Stavrakakis
1144 d2036274 Christos Stavrakakis
1145 d2036274 Christos Stavrakakis
def create_job_dependencies(job_ids=[], job_states=None):
1146 d2036274 Christos Stavrakakis
    """Transform a list of job IDs to Ganeti 'depends' attribute."""
1147 d2036274 Christos Stavrakakis
    if job_states is None:
1148 d2036274 Christos Stavrakakis
        job_states = list(rapi.JOB_STATUS_FINALIZED)
1149 d2036274 Christos Stavrakakis
    assert(type(job_states) == list)
1150 d2036274 Christos Stavrakakis
    return [[job_id, job_states] for job_id in job_ids]