Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (42.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 3278725f Christos Stavrakakis
SIMPLE_NIC_FIELDS = ["state", "mac", "network", "firewall_profile", "index"]
59 3278725f Christos Stavrakakis
COMPLEX_NIC_FIELDS = ["ipv4_address", "ipv6_address"]
60 3278725f Christos Stavrakakis
NIC_FIELDS = SIMPLE_NIC_FIELDS + COMPLEX_NIC_FIELDS
61 0d069390 Christos Stavrakakis
UNKNOWN_NIC_PREFIX = "unknown-"
62 a1baa42b Christos Stavrakakis
63 02feca11 Vassilios Karakoidas
64 41a7fae7 Christos Stavrakakis
def handle_vm_quotas(vm, job_id, job_opcode, job_status, job_fields):
65 41a7fae7 Christos Stavrakakis
    """Handle quotas for updated VirtualMachine.
66 41a7fae7 Christos Stavrakakis

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

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

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

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

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

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

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

377 3278725f Christos Stavrakakis
    Remove all IP addresses that are associated with the NetworkInterface
378 3278725f Christos Stavrakakis
    object, by returning them to the pool and deleting the IPAddress object. If
379 3278725f Christos Stavrakakis
    the IP is a floating IP, then it is just disassociated from the NIC.
380 bfb3f9c2 Christos Stavrakakis
    If version is specified, then only IP addressses of that version will be
381 bfb3f9c2 Christos Stavrakakis
    removed.
382 a1baa42b Christos Stavrakakis

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

468 99af08a4 Christos Stavrakakis
    Update the state of a Network based on the operstate of the networks in the
469 99af08a4 Christos Stavrakakis
    backends that network exists.
470 99af08a4 Christos Stavrakakis

471 99af08a4 Christos Stavrakakis
    The state of the network is:
472 99af08a4 Christos Stavrakakis
    * ACTIVE: If it is 'ACTIVE' in at least one backend.
473 99af08a4 Christos Stavrakakis
    * DELETED: If it is is 'DELETED' in all backends that have been created.
474 99af08a4 Christos Stavrakakis

475 99af08a4 Christos Stavrakakis
    This function also releases the resources (MAC prefix or Bridge) and the
476 99af08a4 Christos Stavrakakis
    quotas for the network.
477 99af08a4 Christos Stavrakakis

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

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

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

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

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

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

1047 1a894bfe Christos Stavrakakis
    """
1048 e77a29ab Christos Stavrakakis
    nodes = get_nodes(backend, bulk=True)
1049 1a894bfe Christos Stavrakakis
    attr = ['mfree', 'mtotal', 'dfree', 'dtotal', 'pinst_cnt', 'ctotal']
1050 1a894bfe Christos Stavrakakis
    res = {}
1051 1a894bfe Christos Stavrakakis
    for a in attr:
1052 1a894bfe Christos Stavrakakis
        res[a] = 0
1053 1a894bfe Christos Stavrakakis
    for n in nodes:
1054 1a894bfe Christos Stavrakakis
        # Filter out drained, offline and not vm_capable nodes since they will
1055 1a894bfe Christos Stavrakakis
        # not take part in the vm allocation process
1056 cc92b70f Christos Stavrakakis
        can_host_vms = n['vm_capable'] and not (n['drained'] or n['offline'])
1057 cc92b70f Christos Stavrakakis
        if can_host_vms and n['cnodes']:
1058 1a894bfe Christos Stavrakakis
            for a in attr:
1059 121b8921 Christos Stavrakakis
                res[a] += int(n[a] or 0)
1060 1a894bfe Christos Stavrakakis
    return res
1061 1a894bfe Christos Stavrakakis
1062 1a894bfe Christos Stavrakakis
1063 1da50fe3 Christos Stavrakakis
def update_backend_resources(backend, resources=None):
1064 1a894bfe Christos Stavrakakis
    """ Update the state of the backend resources in db.
1065 1a894bfe Christos Stavrakakis

1066 1a894bfe Christos Stavrakakis
    """
1067 17852fe9 Giorgos Verigakis
1068 1a894bfe Christos Stavrakakis
    if not resources:
1069 1a894bfe Christos Stavrakakis
        resources = get_physical_resources(backend)
1070 41303ed0 Vangelis Koukis
1071 1a894bfe Christos Stavrakakis
    backend.mfree = resources['mfree']
1072 1a894bfe Christos Stavrakakis
    backend.mtotal = resources['mtotal']
1073 1a894bfe Christos Stavrakakis
    backend.dfree = resources['dfree']
1074 1a894bfe Christos Stavrakakis
    backend.dtotal = resources['dtotal']
1075 1a894bfe Christos Stavrakakis
    backend.pinst_cnt = resources['pinst_cnt']
1076 1a894bfe Christos Stavrakakis
    backend.ctotal = resources['ctotal']
1077 1a894bfe Christos Stavrakakis
    backend.updated = datetime.now()
1078 1a894bfe Christos Stavrakakis
    backend.save()
1079 1a894bfe Christos Stavrakakis
1080 1a894bfe Christos Stavrakakis
1081 1a894bfe Christos Stavrakakis
def get_memory_from_instances(backend):
1082 1a894bfe Christos Stavrakakis
    """ Get the memory that is used from instances.
1083 1a894bfe Christos Stavrakakis

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

1087 1a894bfe Christos Stavrakakis
    """
1088 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1089 3524241a Christos Stavrakakis
        instances = client.GetInstances(bulk=True)
1090 1a894bfe Christos Stavrakakis
    mem = 0
1091 1a894bfe Christos Stavrakakis
    for i in instances:
1092 1a894bfe Christos Stavrakakis
        mem += i['oper_ram']
1093 1a894bfe Christos Stavrakakis
    return mem
1094 b3d28af2 Christos Stavrakakis
1095 1da50fe3 Christos Stavrakakis
1096 1da50fe3 Christos Stavrakakis
def get_available_disk_templates(backend):
1097 1da50fe3 Christos Stavrakakis
    """Get the list of available disk templates of a Ganeti backend.
1098 1da50fe3 Christos Stavrakakis

1099 1da50fe3 Christos Stavrakakis
    The list contains the disk templates that are enabled in the Ganeti backend
1100 1da50fe3 Christos Stavrakakis
    and also included in ipolicy-disk-templates.
1101 1da50fe3 Christos Stavrakakis

1102 1da50fe3 Christos Stavrakakis
    """
1103 1da50fe3 Christos Stavrakakis
    with pooled_rapi_client(backend) as c:
1104 1da50fe3 Christos Stavrakakis
        info = c.GetInfo()
1105 1da50fe3 Christos Stavrakakis
    ipolicy_disk_templates = info["ipolicy"]["disk-templates"]
1106 a8ae6989 Christos Stavrakakis
    try:
1107 a8ae6989 Christos Stavrakakis
        enabled_disk_templates = info["enabled_disk_templates"]
1108 a8ae6989 Christos Stavrakakis
        return [dp for dp in enabled_disk_templates
1109 a8ae6989 Christos Stavrakakis
                if dp in ipolicy_disk_templates]
1110 a8ae6989 Christos Stavrakakis
    except KeyError:
1111 a8ae6989 Christos Stavrakakis
        # Ganeti < 2.8 does not have 'enabled_disk_templates'
1112 a8ae6989 Christos Stavrakakis
        return ipolicy_disk_templates
1113 1da50fe3 Christos Stavrakakis
1114 1da50fe3 Christos Stavrakakis
1115 1da50fe3 Christos Stavrakakis
def update_backend_disk_templates(backend):
1116 1da50fe3 Christos Stavrakakis
    disk_templates = get_available_disk_templates(backend)
1117 1da50fe3 Christos Stavrakakis
    backend.disk_templates = disk_templates
1118 1da50fe3 Christos Stavrakakis
    backend.save()
1119 1da50fe3 Christos Stavrakakis
1120 1da50fe3 Christos Stavrakakis
1121 b3d28af2 Christos Stavrakakis
##
1122 b3d28af2 Christos Stavrakakis
## Synchronized operations for reconciliation
1123 b3d28af2 Christos Stavrakakis
##
1124 b3d28af2 Christos Stavrakakis
1125 b3d28af2 Christos Stavrakakis
1126 b3d28af2 Christos Stavrakakis
def create_network_synced(network, backend):
1127 b3d28af2 Christos Stavrakakis
    result = _create_network_synced(network, backend)
1128 d2036274 Christos Stavrakakis
    if result[0] != rapi.JOB_STATUS_SUCCESS:
1129 b3d28af2 Christos Stavrakakis
        return result
1130 b3d28af2 Christos Stavrakakis
    result = connect_network_synced(network, backend)
1131 b3d28af2 Christos Stavrakakis
    return result
1132 b3d28af2 Christos Stavrakakis
1133 b3d28af2 Christos Stavrakakis
1134 b3d28af2 Christos Stavrakakis
def _create_network_synced(network, backend):
1135 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1136 e60b4630 Christos Stavrakakis
        job = _create_network(network, backend)
1137 3524241a Christos Stavrakakis
        result = wait_for_job(client, job)
1138 3524241a Christos Stavrakakis
    return result
1139 b3d28af2 Christos Stavrakakis
1140 b3d28af2 Christos Stavrakakis
1141 b3d28af2 Christos Stavrakakis
def connect_network_synced(network, backend):
1142 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1143 3524241a Christos Stavrakakis
        for group in client.GetGroups():
1144 b7d38981 Dimitris Aragiorgis
            job = client.ConnectNetwork(network.backend_id, group,
1145 b7d38981 Dimitris Aragiorgis
                                        network.mode, network.link)
1146 3524241a Christos Stavrakakis
            result = wait_for_job(client, job)
1147 d2036274 Christos Stavrakakis
            if result[0] != rapi.JOB_STATUS_SUCCESS:
1148 3524241a Christos Stavrakakis
                return result
1149 b3d28af2 Christos Stavrakakis
1150 b3d28af2 Christos Stavrakakis
    return result
1151 b3d28af2 Christos Stavrakakis
1152 b3d28af2 Christos Stavrakakis
1153 b3d28af2 Christos Stavrakakis
def wait_for_job(client, jobid):
1154 b3d28af2 Christos Stavrakakis
    result = client.WaitForJobChange(jobid, ['status', 'opresult'], None, None)
1155 b3d28af2 Christos Stavrakakis
    status = result['job_info'][0]
1156 d2036274 Christos Stavrakakis
    while status not in rapi.JOB_STATUS_FINALIZED:
1157 b3d28af2 Christos Stavrakakis
        result = client.WaitForJobChange(jobid, ['status', 'opresult'],
1158 cc92b70f Christos Stavrakakis
                                         [result], None)
1159 b3d28af2 Christos Stavrakakis
        status = result['job_info'][0]
1160 b3d28af2 Christos Stavrakakis
1161 d2036274 Christos Stavrakakis
    if status == rapi.JOB_STATUS_SUCCESS:
1162 b3d28af2 Christos Stavrakakis
        return (status, None)
1163 b3d28af2 Christos Stavrakakis
    else:
1164 b3d28af2 Christos Stavrakakis
        error = result['job_info'][1]
1165 b3d28af2 Christos Stavrakakis
        return (status, error)
1166 d2036274 Christos Stavrakakis
1167 d2036274 Christos Stavrakakis
1168 d2036274 Christos Stavrakakis
def create_job_dependencies(job_ids=[], job_states=None):
1169 d2036274 Christos Stavrakakis
    """Transform a list of job IDs to Ganeti 'depends' attribute."""
1170 d2036274 Christos Stavrakakis
    if job_states is None:
1171 d2036274 Christos Stavrakakis
        job_states = list(rapi.JOB_STATUS_FINALIZED)
1172 d2036274 Christos Stavrakakis
    assert(type(job_states) == list)
1173 d2036274 Christos Stavrakakis
    return [[job_id, job_states] for job_id in job_ids]