Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (42.4 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 ba777b02 Giorgos Korfiatis
            quotas.accept_resource_serial(vm)
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 ba777b02 Giorgos Korfiatis
            quotas.reject_resource_serial(vm)
97 16b959ce Giorgos Korfiatis
    elif job_status == rapi.JOB_STATUS_SUCCESS:
98 16b959ce Giorgos Korfiatis
        commission_info = quotas.get_commission_info(resource=vm,
99 16b959ce Giorgos Korfiatis
                                                     action=action,
100 16b959ce Giorgos Korfiatis
                                                     action_fields=job_fields)
101 16b959ce Giorgos Korfiatis
        if commission_info is not None:
102 16b959ce Giorgos Korfiatis
            # Commission for this change has not been issued, or the issued
103 16b959ce Giorgos Korfiatis
            # commission was unaware of the current change. Reject all previous
104 16b959ce Giorgos Korfiatis
            # commissions and create a new one in forced mode!
105 ba777b02 Giorgos Korfiatis
            log.debug("Expected job was %s. Processing job %s. "
106 ba777b02 Giorgos Korfiatis
                      "Attached serial %s",
107 ba777b02 Giorgos Korfiatis
                      vm.task_job_id, job_id, vm.serial)
108 16b959ce Giorgos Korfiatis
            reason = ("client: dispatcher, resource: %s, ganeti_job: %s"
109 16b959ce Giorgos Korfiatis
                      % (vm, job_id))
110 ba777b02 Giorgos Korfiatis
            serial = quotas.handle_resource_commission(
111 ba777b02 Giorgos Korfiatis
                vm, action,
112 ba777b02 Giorgos Korfiatis
                action_fields=job_fields,
113 ba777b02 Giorgos Korfiatis
                commission_name=reason,
114 ba777b02 Giorgos Korfiatis
                force=True,
115 ba777b02 Giorgos Korfiatis
                auto_accept=True)
116 ba777b02 Giorgos Korfiatis
            log.debug("Issued new commission: %s", serial)
117 41a7fae7 Christos Stavrakakis
    return vm
118 41a7fae7 Christos Stavrakakis
119 41a7fae7 Christos Stavrakakis
120 093f9c53 Vangelis Koukis
@transaction.commit_on_success
121 ca4d59e3 Christos Stavrakakis
def process_op_status(vm, etime, jobid, opcode, status, logmsg, nics=None,
122 e6fbada1 Christos Stavrakakis
                      job_fields=None):
123 ad2d6807 Vangelis Koukis
    """Process a job progress notification from the backend
124 02feca11 Vassilios Karakoidas

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1105 1da50fe3 Christos Stavrakakis
    The list contains the disk templates that are enabled in the Ganeti backend
1106 1da50fe3 Christos Stavrakakis
    and also included in ipolicy-disk-templates.
1107 1da50fe3 Christos Stavrakakis

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