Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (43.9 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 33ef7782 Christos Stavrakakis
            #status = "success"
196 33ef7782 Christos Stavrakakis
            vm.volumes.all().update(deleted=True, machine=None)
197 41a7fae7 Christos Stavrakakis
198 d2036274 Christos Stavrakakis
    if status in rapi.JOB_STATUS_FINALIZED:
199 41a7fae7 Christos Stavrakakis
        # Job is finalized: Handle quotas/commissioning
200 41a7fae7 Christos Stavrakakis
        vm = handle_vm_quotas(vm, job_id=jobid, job_opcode=opcode,
201 41a7fae7 Christos Stavrakakis
                              job_status=status, job_fields=job_fields)
202 41a7fae7 Christos Stavrakakis
        # and clear task fields
203 41a7fae7 Christos Stavrakakis
        if vm.task_job_id == jobid:
204 41a7fae7 Christos Stavrakakis
            vm.task = None
205 41a7fae7 Christos Stavrakakis
            vm.task_job_id = None
206 02feca11 Vassilios Karakoidas
207 1fdd8d69 Christos Stavrakakis
    if new_operstate is not None:
208 1fdd8d69 Christos Stavrakakis
        vm.operstate = new_operstate
209 3af1fb4b Christos Stavrakakis
    if new_flavor is not None:
210 3af1fb4b Christos Stavrakakis
        vm.flavor = new_flavor
211 1fdd8d69 Christos Stavrakakis
212 02feca11 Vassilios Karakoidas
    vm.save()
213 22e52ede Vassilios Karakoidas
214 ad2d6807 Vangelis Koukis
215 ca4d59e3 Christos Stavrakakis
def _process_resize(vm, beparams):
216 ca4d59e3 Christos Stavrakakis
    """Change flavor of a VirtualMachine based on new beparams."""
217 ca4d59e3 Christos Stavrakakis
    old_flavor = vm.flavor
218 41a7fae7 Christos Stavrakakis
    vcpus = beparams.get("vcpus", old_flavor.cpu)
219 41a7fae7 Christos Stavrakakis
    ram = beparams.get("maxmem", old_flavor.ram)
220 41a7fae7 Christos Stavrakakis
    if vcpus == old_flavor.cpu and ram == old_flavor.ram:
221 ca4d59e3 Christos Stavrakakis
        return
222 ca4d59e3 Christos Stavrakakis
    try:
223 ca4d59e3 Christos Stavrakakis
        new_flavor = Flavor.objects.get(cpu=vcpus, ram=ram,
224 ca4d59e3 Christos Stavrakakis
                                        disk=old_flavor.disk,
225 ca4d59e3 Christos Stavrakakis
                                        disk_template=old_flavor.disk_template)
226 ca4d59e3 Christos Stavrakakis
    except Flavor.DoesNotExist:
227 8d5795b4 Christos Stavrakakis
        raise Exception("Cannot find flavor for VM")
228 3af1fb4b Christos Stavrakakis
    return new_flavor
229 ca4d59e3 Christos Stavrakakis
230 ca4d59e3 Christos Stavrakakis
231 207b70d5 Giorgos Verigakis
@transaction.commit_on_success
232 c4e55622 Christos Stavrakakis
def process_net_status(vm, etime, nics):
233 fd95834e Christos Stavrakakis
    """Wrap _process_net_status inside transaction."""
234 fd95834e Christos Stavrakakis
    _process_net_status(vm, etime, nics)
235 fd95834e Christos Stavrakakis
236 fd95834e Christos Stavrakakis
237 fd95834e Christos Stavrakakis
def _process_net_status(vm, etime, nics):
238 ad2d6807 Vangelis Koukis
    """Process a net status notification from the backend
239 ad2d6807 Vangelis Koukis

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1105 1a894bfe Christos Stavrakakis
    """
1106 e77a29ab Christos Stavrakakis
    nodes = get_nodes(backend, bulk=True)
1107 1a894bfe Christos Stavrakakis
    attr = ['mfree', 'mtotal', 'dfree', 'dtotal', 'pinst_cnt', 'ctotal']
1108 1a894bfe Christos Stavrakakis
    res = {}
1109 1a894bfe Christos Stavrakakis
    for a in attr:
1110 1a894bfe Christos Stavrakakis
        res[a] = 0
1111 1a894bfe Christos Stavrakakis
    for n in nodes:
1112 1a894bfe Christos Stavrakakis
        # Filter out drained, offline and not vm_capable nodes since they will
1113 1a894bfe Christos Stavrakakis
        # not take part in the vm allocation process
1114 cc92b70f Christos Stavrakakis
        can_host_vms = n['vm_capable'] and not (n['drained'] or n['offline'])
1115 cc92b70f Christos Stavrakakis
        if can_host_vms and n['cnodes']:
1116 1a894bfe Christos Stavrakakis
            for a in attr:
1117 121b8921 Christos Stavrakakis
                res[a] += int(n[a] or 0)
1118 1a894bfe Christos Stavrakakis
    return res
1119 1a894bfe Christos Stavrakakis
1120 1a894bfe Christos Stavrakakis
1121 1da50fe3 Christos Stavrakakis
def update_backend_resources(backend, resources=None):
1122 1a894bfe Christos Stavrakakis
    """ Update the state of the backend resources in db.
1123 1a894bfe Christos Stavrakakis

1124 1a894bfe Christos Stavrakakis
    """
1125 17852fe9 Giorgos Verigakis
1126 1a894bfe Christos Stavrakakis
    if not resources:
1127 1a894bfe Christos Stavrakakis
        resources = get_physical_resources(backend)
1128 41303ed0 Vangelis Koukis
1129 1a894bfe Christos Stavrakakis
    backend.mfree = resources['mfree']
1130 1a894bfe Christos Stavrakakis
    backend.mtotal = resources['mtotal']
1131 1a894bfe Christos Stavrakakis
    backend.dfree = resources['dfree']
1132 1a894bfe Christos Stavrakakis
    backend.dtotal = resources['dtotal']
1133 1a894bfe Christos Stavrakakis
    backend.pinst_cnt = resources['pinst_cnt']
1134 1a894bfe Christos Stavrakakis
    backend.ctotal = resources['ctotal']
1135 1a894bfe Christos Stavrakakis
    backend.updated = datetime.now()
1136 1a894bfe Christos Stavrakakis
    backend.save()
1137 1a894bfe Christos Stavrakakis
1138 1a894bfe Christos Stavrakakis
1139 1a894bfe Christos Stavrakakis
def get_memory_from_instances(backend):
1140 1a894bfe Christos Stavrakakis
    """ Get the memory that is used from instances.
1141 1a894bfe Christos Stavrakakis

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

1145 1a894bfe Christos Stavrakakis
    """
1146 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1147 3524241a Christos Stavrakakis
        instances = client.GetInstances(bulk=True)
1148 1a894bfe Christos Stavrakakis
    mem = 0
1149 1a894bfe Christos Stavrakakis
    for i in instances:
1150 1a894bfe Christos Stavrakakis
        mem += i['oper_ram']
1151 1a894bfe Christos Stavrakakis
    return mem
1152 b3d28af2 Christos Stavrakakis
1153 1da50fe3 Christos Stavrakakis
1154 1da50fe3 Christos Stavrakakis
def get_available_disk_templates(backend):
1155 1da50fe3 Christos Stavrakakis
    """Get the list of available disk templates of a Ganeti backend.
1156 1da50fe3 Christos Stavrakakis

1157 1da50fe3 Christos Stavrakakis
    The list contains the disk templates that are enabled in the Ganeti backend
1158 1da50fe3 Christos Stavrakakis
    and also included in ipolicy-disk-templates.
1159 1da50fe3 Christos Stavrakakis

1160 1da50fe3 Christos Stavrakakis
    """
1161 1da50fe3 Christos Stavrakakis
    with pooled_rapi_client(backend) as c:
1162 1da50fe3 Christos Stavrakakis
        info = c.GetInfo()
1163 1da50fe3 Christos Stavrakakis
    ipolicy_disk_templates = info["ipolicy"]["disk-templates"]
1164 a8ae6989 Christos Stavrakakis
    try:
1165 a8ae6989 Christos Stavrakakis
        enabled_disk_templates = info["enabled_disk_templates"]
1166 a8ae6989 Christos Stavrakakis
        return [dp for dp in enabled_disk_templates
1167 a8ae6989 Christos Stavrakakis
                if dp in ipolicy_disk_templates]
1168 a8ae6989 Christos Stavrakakis
    except KeyError:
1169 a8ae6989 Christos Stavrakakis
        # Ganeti < 2.8 does not have 'enabled_disk_templates'
1170 a8ae6989 Christos Stavrakakis
        return ipolicy_disk_templates
1171 1da50fe3 Christos Stavrakakis
1172 1da50fe3 Christos Stavrakakis
1173 1da50fe3 Christos Stavrakakis
def update_backend_disk_templates(backend):
1174 1da50fe3 Christos Stavrakakis
    disk_templates = get_available_disk_templates(backend)
1175 1da50fe3 Christos Stavrakakis
    backend.disk_templates = disk_templates
1176 1da50fe3 Christos Stavrakakis
    backend.save()
1177 1da50fe3 Christos Stavrakakis
1178 1da50fe3 Christos Stavrakakis
1179 b3d28af2 Christos Stavrakakis
##
1180 b3d28af2 Christos Stavrakakis
## Synchronized operations for reconciliation
1181 b3d28af2 Christos Stavrakakis
##
1182 b3d28af2 Christos Stavrakakis
1183 b3d28af2 Christos Stavrakakis
1184 b3d28af2 Christos Stavrakakis
def create_network_synced(network, backend):
1185 b3d28af2 Christos Stavrakakis
    result = _create_network_synced(network, backend)
1186 d2036274 Christos Stavrakakis
    if result[0] != rapi.JOB_STATUS_SUCCESS:
1187 b3d28af2 Christos Stavrakakis
        return result
1188 b3d28af2 Christos Stavrakakis
    result = connect_network_synced(network, backend)
1189 b3d28af2 Christos Stavrakakis
    return result
1190 b3d28af2 Christos Stavrakakis
1191 b3d28af2 Christos Stavrakakis
1192 b3d28af2 Christos Stavrakakis
def _create_network_synced(network, backend):
1193 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1194 e60b4630 Christos Stavrakakis
        job = _create_network(network, backend)
1195 3524241a Christos Stavrakakis
        result = wait_for_job(client, job)
1196 3524241a Christos Stavrakakis
    return result
1197 b3d28af2 Christos Stavrakakis
1198 b3d28af2 Christos Stavrakakis
1199 b3d28af2 Christos Stavrakakis
def connect_network_synced(network, backend):
1200 3524241a Christos Stavrakakis
    with pooled_rapi_client(backend) as client:
1201 3524241a Christos Stavrakakis
        for group in client.GetGroups():
1202 b7d38981 Dimitris Aragiorgis
            job = client.ConnectNetwork(network.backend_id, group,
1203 b7d38981 Dimitris Aragiorgis
                                        network.mode, network.link)
1204 3524241a Christos Stavrakakis
            result = wait_for_job(client, job)
1205 d2036274 Christos Stavrakakis
            if result[0] != rapi.JOB_STATUS_SUCCESS:
1206 3524241a Christos Stavrakakis
                return result
1207 b3d28af2 Christos Stavrakakis
1208 b3d28af2 Christos Stavrakakis
    return result
1209 b3d28af2 Christos Stavrakakis
1210 b3d28af2 Christos Stavrakakis
1211 b3d28af2 Christos Stavrakakis
def wait_for_job(client, jobid):
1212 b3d28af2 Christos Stavrakakis
    result = client.WaitForJobChange(jobid, ['status', 'opresult'], None, None)
1213 b3d28af2 Christos Stavrakakis
    status = result['job_info'][0]
1214 d2036274 Christos Stavrakakis
    while status not in rapi.JOB_STATUS_FINALIZED:
1215 b3d28af2 Christos Stavrakakis
        result = client.WaitForJobChange(jobid, ['status', 'opresult'],
1216 cc92b70f Christos Stavrakakis
                                         [result], None)
1217 b3d28af2 Christos Stavrakakis
        status = result['job_info'][0]
1218 b3d28af2 Christos Stavrakakis
1219 d2036274 Christos Stavrakakis
    if status == rapi.JOB_STATUS_SUCCESS:
1220 b3d28af2 Christos Stavrakakis
        return (status, None)
1221 b3d28af2 Christos Stavrakakis
    else:
1222 b3d28af2 Christos Stavrakakis
        error = result['job_info'][1]
1223 b3d28af2 Christos Stavrakakis
        return (status, error)
1224 d2036274 Christos Stavrakakis
1225 d2036274 Christos Stavrakakis
1226 d2036274 Christos Stavrakakis
def create_job_dependencies(job_ids=[], job_states=None):
1227 d2036274 Christos Stavrakakis
    """Transform a list of job IDs to Ganeti 'depends' attribute."""
1228 d2036274 Christos Stavrakakis
    if job_states is None:
1229 d2036274 Christos Stavrakakis
        job_states = list(rapi.JOB_STATUS_FINALIZED)
1230 d2036274 Christos Stavrakakis
    assert(type(job_states) == list)
1231 d2036274 Christos Stavrakakis
    return [[job_id, job_states] for job_id in job_ids]