Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (42.7 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 1b3f1792 Christos Stavrakakis
            # NOTE: Since we rejected the serial that was associated with the
117 1b3f1792 Christos Stavrakakis
            # 'vm.task_job_id' job, we must also clear the 'vm.serial' field.
118 1b3f1792 Christos Stavrakakis
            # If not, there will be no new commission for the 'vm.task_job_id'
119 1b3f1792 Christos Stavrakakis
            # job!
120 1b3f1792 Christos Stavrakakis
            vm.serial = None
121 41a7fae7 Christos Stavrakakis
122 41a7fae7 Christos Stavrakakis
    return vm
123 41a7fae7 Christos Stavrakakis
124 41a7fae7 Christos Stavrakakis
125 093f9c53 Vangelis Koukis
@transaction.commit_on_success
126 ca4d59e3 Christos Stavrakakis
def process_op_status(vm, etime, jobid, opcode, status, logmsg, nics=None,
127 e6fbada1 Christos Stavrakakis
                      job_fields=None):
128 ad2d6807 Vangelis Koukis
    """Process a job progress notification from the backend
129 02feca11 Vassilios Karakoidas

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

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

243 ad2d6807 Vangelis Koukis
    Process an incoming message from the Ganeti backend,
244 ad2d6807 Vangelis Koukis
    detailing the NIC configuration of a VM instance.
245 ad2d6807 Vangelis Koukis

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

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

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

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

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

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

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

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

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

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

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

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

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

1058 1a894bfe Christos Stavrakakis
    """
1059 e77a29ab Christos Stavrakakis
    nodes = get_nodes(backend, bulk=True)
1060 1a894bfe Christos Stavrakakis
    attr = ['mfree', 'mtotal', 'dfree', 'dtotal', 'pinst_cnt', 'ctotal']
1061 1a894bfe Christos Stavrakakis
    res = {}
1062 1a894bfe Christos Stavrakakis
    for a in attr:
1063 1a894bfe Christos Stavrakakis
        res[a] = 0
1064 1a894bfe Christos Stavrakakis
    for n in nodes:
1065 1a894bfe Christos Stavrakakis
        # Filter out drained, offline and not vm_capable nodes since they will
1066 1a894bfe Christos Stavrakakis
        # not take part in the vm allocation process
1067 cc92b70f Christos Stavrakakis
        can_host_vms = n['vm_capable'] and not (n['drained'] or n['offline'])
1068 cc92b70f Christos Stavrakakis
        if can_host_vms and n['cnodes']:
1069 1a894bfe Christos Stavrakakis
            for a in attr:
1070 121b8921 Christos Stavrakakis
                res[a] += int(n[a] or 0)
1071 1a894bfe Christos Stavrakakis
    return res
1072 1a894bfe Christos Stavrakakis
1073 1a894bfe Christos Stavrakakis
1074 1da50fe3 Christos Stavrakakis
def update_backend_resources(backend, resources=None):
1075 1a894bfe Christos Stavrakakis
    """ Update the state of the backend resources in db.
1076 1a894bfe Christos Stavrakakis

1077 1a894bfe Christos Stavrakakis
    """
1078 17852fe9 Giorgos Verigakis
1079 1a894bfe Christos Stavrakakis
    if not resources:
1080 1a894bfe Christos Stavrakakis
        resources = get_physical_resources(backend)
1081 41303ed0 Vangelis Koukis
1082 1a894bfe Christos Stavrakakis
    backend.mfree = resources['mfree']
1083 1a894bfe Christos Stavrakakis
    backend.mtotal = resources['mtotal']
1084 1a894bfe Christos Stavrakakis
    backend.dfree = resources['dfree']
1085 1a894bfe Christos Stavrakakis
    backend.dtotal = resources['dtotal']
1086 1a894bfe Christos Stavrakakis
    backend.pinst_cnt = resources['pinst_cnt']
1087 1a894bfe Christos Stavrakakis
    backend.ctotal = resources['ctotal']
1088 1a894bfe Christos Stavrakakis
    backend.updated = datetime.now()
1089 1a894bfe Christos Stavrakakis
    backend.save()
1090 1a894bfe Christos Stavrakakis
1091 1a894bfe Christos Stavrakakis
1092 1a894bfe Christos Stavrakakis
def get_memory_from_instances(backend):
1093 1a894bfe Christos Stavrakakis
    """ Get the memory that is used from instances.
1094 1a894bfe Christos Stavrakakis

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

1098 1a894bfe Christos Stavrakakis
    """
1099 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1100 3524241a Christos Stavrakakis
        instances = client.GetInstances(bulk=True)
1101 1a894bfe Christos Stavrakakis
    mem = 0
1102 1a894bfe Christos Stavrakakis
    for i in instances:
1103 1a894bfe Christos Stavrakakis
        mem += i['oper_ram']
1104 1a894bfe Christos Stavrakakis
    return mem
1105 b3d28af2 Christos Stavrakakis
1106 1da50fe3 Christos Stavrakakis
1107 1da50fe3 Christos Stavrakakis
def get_available_disk_templates(backend):
1108 1da50fe3 Christos Stavrakakis
    """Get the list of available disk templates of a Ganeti backend.
1109 1da50fe3 Christos Stavrakakis

1110 1da50fe3 Christos Stavrakakis
    The list contains the disk templates that are enabled in the Ganeti backend
1111 1da50fe3 Christos Stavrakakis
    and also included in ipolicy-disk-templates.
1112 1da50fe3 Christos Stavrakakis

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