Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / servers.py @ 23f29f98

History | View | Annotate | Download (29 kB)

1 ff5edb80 Giorgos Korfiatis
# Copyright 2011-2014 GRNET S.A. All rights reserved.
2 91884d63 Giorgos Korfiatis
#
3 91884d63 Giorgos Korfiatis
# Redistribution and use in source and binary forms, with or without
4 91884d63 Giorgos Korfiatis
# modification, are permitted provided that the following conditions
5 91884d63 Giorgos Korfiatis
# are met:
6 91884d63 Giorgos Korfiatis
#
7 91884d63 Giorgos Korfiatis
#   1. Redistributions of source code must retain the above copyright
8 91884d63 Giorgos Korfiatis
#      notice, this list of conditions and the following disclaimer.
9 91884d63 Giorgos Korfiatis
#
10 91884d63 Giorgos Korfiatis
#  2. Redistributions in binary form must reproduce the above copyright
11 91884d63 Giorgos Korfiatis
#     notice, this list of conditions and the following disclaimer in the
12 91884d63 Giorgos Korfiatis
#     documentation and/or other materials provided with the distribution.
13 91884d63 Giorgos Korfiatis
#
14 91884d63 Giorgos Korfiatis
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
15 91884d63 Giorgos Korfiatis
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 91884d63 Giorgos Korfiatis
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 91884d63 Giorgos Korfiatis
# ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
18 91884d63 Giorgos Korfiatis
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 91884d63 Giorgos Korfiatis
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 91884d63 Giorgos Korfiatis
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 91884d63 Giorgos Korfiatis
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 91884d63 Giorgos Korfiatis
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 91884d63 Giorgos Korfiatis
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 91884d63 Giorgos Korfiatis
# SUCH DAMAGE.
25 91884d63 Giorgos Korfiatis
#
26 91884d63 Giorgos Korfiatis
# The views and conclusions contained in the software and documentation are
27 91884d63 Giorgos Korfiatis
# those of the authors and should not be interpreted as representing official
28 91884d63 Giorgos Korfiatis
# policies, either expressed or implied, of GRNET S.A.
29 91884d63 Giorgos Korfiatis
30 41a7fae7 Christos Stavrakakis
import logging
31 41a7fae7 Christos Stavrakakis
32 41a7fae7 Christos Stavrakakis
from socket import getfqdn
33 41a7fae7 Christos Stavrakakis
from functools import wraps
34 41a7fae7 Christos Stavrakakis
from django import dispatch
35 41a7fae7 Christos Stavrakakis
from django.db import transaction
36 41a7fae7 Christos Stavrakakis
from django.utils import simplejson as json
37 41a7fae7 Christos Stavrakakis
38 41a7fae7 Christos Stavrakakis
from snf_django.lib.api import faults
39 0c09b1c0 Christos Stavrakakis
from django.conf import settings
40 41a7fae7 Christos Stavrakakis
from synnefo import quotas
41 41a7fae7 Christos Stavrakakis
from synnefo.api import util
42 ba6ad346 Dionysis Grigoropoulos
from synnefo.logic import backend, ips, utils
43 41a7fae7 Christos Stavrakakis
from synnefo.logic.backend_allocator import BackendAllocator
44 710b1c43 Christos Stavrakakis
from synnefo.db.models import (NetworkInterface, VirtualMachine,
45 3aecadc8 Christos Stavrakakis
                               VirtualMachineMetadata, IPAddressLog, Network)
46 41a7fae7 Christos Stavrakakis
from vncauthproxy.client import request_forwarding as request_vnc_forwarding
47 af1832fe Christos Stavrakakis
from synnefo.logic import rapi
48 41a7fae7 Christos Stavrakakis
49 41a7fae7 Christos Stavrakakis
log = logging.getLogger(__name__)
50 41a7fae7 Christos Stavrakakis
51 41a7fae7 Christos Stavrakakis
# server creation signal
52 41a7fae7 Christos Stavrakakis
server_created = dispatch.Signal(providing_args=["created_vm_params"])
53 41a7fae7 Christos Stavrakakis
54 41a7fae7 Christos Stavrakakis
55 41a7fae7 Christos Stavrakakis
def validate_server_action(vm, action):
56 41a7fae7 Christos Stavrakakis
    if vm.deleted:
57 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Server '%s' has been deleted." % vm.id)
58 41a7fae7 Christos Stavrakakis
59 41a7fae7 Christos Stavrakakis
    # Destroyin a server should always be permitted
60 41a7fae7 Christos Stavrakakis
    if action == "DESTROY":
61 41a7fae7 Christos Stavrakakis
        return
62 41a7fae7 Christos Stavrakakis
63 41a7fae7 Christos Stavrakakis
    # Check that there is no pending action
64 41a7fae7 Christos Stavrakakis
    pending_action = vm.task
65 41a7fae7 Christos Stavrakakis
    if pending_action:
66 41a7fae7 Christos Stavrakakis
        if pending_action == "BUILD":
67 41a7fae7 Christos Stavrakakis
            raise faults.BuildInProgress("Server '%s' is being build." % vm.id)
68 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot perform '%s' action while there is a"
69 41a7fae7 Christos Stavrakakis
                                " pending '%s'." % (action, pending_action))
70 41a7fae7 Christos Stavrakakis
71 41a7fae7 Christos Stavrakakis
    # Check if action can be performed to VM's operstate
72 41a7fae7 Christos Stavrakakis
    operstate = vm.operstate
73 ee688a71 Christos Stavrakakis
    if operstate == "ERROR":
74 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot perform '%s' action while server is"
75 ee688a71 Christos Stavrakakis
                                " in 'ERROR' state." % action)
76 ee688a71 Christos Stavrakakis
    elif operstate == "BUILD" and action != "BUILD":
77 41a7fae7 Christos Stavrakakis
        raise faults.BuildInProgress("Server '%s' is being build." % vm.id)
78 9599e997 Christos Stavrakakis
    elif (action == "START" and operstate != "STOPPED") or\
79 9599e997 Christos Stavrakakis
         (action == "STOP" and operstate != "STARTED") or\
80 9599e997 Christos Stavrakakis
         (action == "RESIZE" and operstate != "STOPPED") or\
81 9599e997 Christos Stavrakakis
         (action in ["CONNECT", "DISCONNECT"] and operstate != "STOPPED"
82 9599e997 Christos Stavrakakis
          and not settings.GANETI_USE_HOTPLUG):
83 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot perform '%s' action while server is"
84 41a7fae7 Christos Stavrakakis
                                " in '%s' state." % (action, operstate))
85 41a7fae7 Christos Stavrakakis
    return
86 41a7fae7 Christos Stavrakakis
87 41a7fae7 Christos Stavrakakis
88 64bca363 Giorgos Korfiatis
def server_command(action, action_fields=None):
89 41a7fae7 Christos Stavrakakis
    """Handle execution of a server action.
90 41a7fae7 Christos Stavrakakis

91 41a7fae7 Christos Stavrakakis
    Helper function to validate and execute a server action, handle quota
92 41a7fae7 Christos Stavrakakis
    commission and update the 'task' of the VM in the DB.
93 41a7fae7 Christos Stavrakakis

94 41a7fae7 Christos Stavrakakis
    1) Check if action can be performed. If it can, then there must be no
95 41a7fae7 Christos Stavrakakis
       pending task (with the exception of DESTROY).
96 41a7fae7 Christos Stavrakakis
    2) Handle previous commission if unresolved:
97 41a7fae7 Christos Stavrakakis
       * If it is not pending and it to accept, then accept
98 41a7fae7 Christos Stavrakakis
       * If it is not pending and to reject or is pending then reject it. Since
99 41a7fae7 Christos Stavrakakis
       the action can be performed only if there is no pending task, then there
100 41a7fae7 Christos Stavrakakis
       can be no pending commission. The exception is DESTROY, but in this case
101 41a7fae7 Christos Stavrakakis
       the commission can safely be rejected, and the dispatcher will generate
102 41a7fae7 Christos Stavrakakis
       the correct ones!
103 41a7fae7 Christos Stavrakakis
    3) Issue new commission and associate it with the VM. Also clear the task.
104 41a7fae7 Christos Stavrakakis
    4) Send job to ganeti
105 41a7fae7 Christos Stavrakakis
    5) Update task and commit
106 41a7fae7 Christos Stavrakakis
    """
107 41a7fae7 Christos Stavrakakis
    def decorator(func):
108 41a7fae7 Christos Stavrakakis
        @wraps(func)
109 41a7fae7 Christos Stavrakakis
        @transaction.commit_on_success
110 41a7fae7 Christos Stavrakakis
        def wrapper(vm, *args, **kwargs):
111 41a7fae7 Christos Stavrakakis
            user_id = vm.userid
112 41a7fae7 Christos Stavrakakis
            validate_server_action(vm, action)
113 c9fefba3 Christos Stavrakakis
            vm.action = action
114 41a7fae7 Christos Stavrakakis
115 5c8076b6 Christos Stavrakakis
            commission_name = "client: api, resource: %s" % vm
116 5c8076b6 Christos Stavrakakis
            quotas.handle_resource_commission(vm, action=action,
117 64bca363 Giorgos Korfiatis
                                              action_fields=action_fields,
118 5c8076b6 Christos Stavrakakis
                                              commission_name=commission_name)
119 5c8076b6 Christos Stavrakakis
            vm.save()
120 41a7fae7 Christos Stavrakakis
121 562bf712 Christos Stavrakakis
            # XXX: Special case for server creation!
122 562bf712 Christos Stavrakakis
            if action == "BUILD":
123 562bf712 Christos Stavrakakis
                # Perform a commit, because the VirtualMachine must be saved to
124 562bf712 Christos Stavrakakis
                # DB before the OP_INSTANCE_CREATE job in enqueued in Ganeti.
125 562bf712 Christos Stavrakakis
                # Otherwise, messages will arrive from snf-dispatcher about
126 562bf712 Christos Stavrakakis
                # this instance, before the VM is stored in DB.
127 562bf712 Christos Stavrakakis
                transaction.commit()
128 562bf712 Christos Stavrakakis
                # After committing the locks are released. Refetch the instance
129 562bf712 Christos Stavrakakis
                # to guarantee x-lock.
130 562bf712 Christos Stavrakakis
                vm = VirtualMachine.objects.select_for_update().get(id=vm.id)
131 562bf712 Christos Stavrakakis
132 41a7fae7 Christos Stavrakakis
            # Send the job to Ganeti and get the associated jobID
133 41a7fae7 Christos Stavrakakis
            try:
134 41a7fae7 Christos Stavrakakis
                job_id = func(vm, *args, **kwargs)
135 41a7fae7 Christos Stavrakakis
            except Exception as e:
136 41a7fae7 Christos Stavrakakis
                if vm.serial is not None:
137 41a7fae7 Christos Stavrakakis
                    # Since the job never reached Ganeti, reject the commission
138 41a7fae7 Christos Stavrakakis
                    log.debug("Rejecting commission: '%s', could not perform"
139 41a7fae7 Christos Stavrakakis
                              " action '%s': %s" % (vm.serial,  action, e))
140 41a7fae7 Christos Stavrakakis
                    transaction.rollback()
141 ba777b02 Giorgos Korfiatis
                    quotas.reject_resource_serial(vm)
142 41a7fae7 Christos Stavrakakis
                    transaction.commit()
143 41a7fae7 Christos Stavrakakis
                raise
144 41a7fae7 Christos Stavrakakis
145 88fd91af Christos Stavrakakis
            if action == "BUILD" and vm.serial is not None:
146 88fd91af Christos Stavrakakis
                # XXX: Special case for server creation: we must accept the
147 88fd91af Christos Stavrakakis
                # commission because the VM has been stored in DB. Also, if
148 88fd91af Christos Stavrakakis
                # communication with Ganeti fails, the job will never reach
149 88fd91af Christos Stavrakakis
                # Ganeti, and the commission will never be resolved.
150 ba777b02 Giorgos Korfiatis
                quotas.accept_resource_serial(vm)
151 88fd91af Christos Stavrakakis
152 41a7fae7 Christos Stavrakakis
            log.info("user: %s, vm: %s, action: %s, job_id: %s, serial: %s",
153 41a7fae7 Christos Stavrakakis
                     user_id, vm.id, action, job_id, vm.serial)
154 41a7fae7 Christos Stavrakakis
155 41a7fae7 Christos Stavrakakis
            # store the new task in the VM
156 41a7fae7 Christos Stavrakakis
            if job_id is not None:
157 41a7fae7 Christos Stavrakakis
                vm.task = action
158 41a7fae7 Christos Stavrakakis
                vm.task_job_id = job_id
159 41a7fae7 Christos Stavrakakis
            vm.save()
160 41a7fae7 Christos Stavrakakis
161 41a7fae7 Christos Stavrakakis
            return vm
162 41a7fae7 Christos Stavrakakis
        return wrapper
163 41a7fae7 Christos Stavrakakis
    return decorator
164 41a7fae7 Christos Stavrakakis
165 41a7fae7 Christos Stavrakakis
166 562bf712 Christos Stavrakakis
@transaction.commit_on_success
167 41a7fae7 Christos Stavrakakis
def create(userid, name, password, flavor, image, metadata={},
168 47c27955 Giorgos Korfiatis
           personality=[], networks=None, use_backend=None, project=None):
169 41a7fae7 Christos Stavrakakis
    if use_backend is None:
170 562bf712 Christos Stavrakakis
        # Allocate server to a Ganeti backend
171 562bf712 Christos Stavrakakis
        use_backend = allocate_new_server(userid, flavor)
172 41a7fae7 Christos Stavrakakis
173 ba6ad346 Dionysis Grigoropoulos
    utils.check_name_length(name, VirtualMachine.VIRTUAL_MACHINE_NAME_LENGTH,
174 ba6ad346 Dionysis Grigoropoulos
                            "Server name is too long")
175 ba6ad346 Dionysis Grigoropoulos
176 3aecadc8 Christos Stavrakakis
    # Create the ports for the server
177 16a7ced5 Christos Stavrakakis
    ports = create_instance_ports(userid, networks)
178 6193628f Christos Stavrakakis
179 41a7fae7 Christos Stavrakakis
    # Fix flavor for archipelago
180 41a7fae7 Christos Stavrakakis
    disk_template, provider = util.get_flavor_provider(flavor)
181 41a7fae7 Christos Stavrakakis
    if provider:
182 41a7fae7 Christos Stavrakakis
        flavor.disk_template = disk_template
183 41a7fae7 Christos Stavrakakis
        flavor.disk_provider = provider
184 41a7fae7 Christos Stavrakakis
        flavor.disk_origin = None
185 62232bba Christos Stavrakakis
        if provider in settings.GANETI_CLONE_PROVIDERS:
186 41a7fae7 Christos Stavrakakis
            flavor.disk_origin = image['checksum']
187 41a7fae7 Christos Stavrakakis
            image['backend_id'] = 'null'
188 41a7fae7 Christos Stavrakakis
    else:
189 41a7fae7 Christos Stavrakakis
        flavor.disk_provider = None
190 41a7fae7 Christos Stavrakakis
191 47c27955 Giorgos Korfiatis
    if project is None:
192 47c27955 Giorgos Korfiatis
        project = userid
193 47c27955 Giorgos Korfiatis
194 562bf712 Christos Stavrakakis
    # We must save the VM instance now, so that it gets a valid
195 562bf712 Christos Stavrakakis
    # vm.backend_vm_id.
196 562bf712 Christos Stavrakakis
    vm = VirtualMachine.objects.create(name=name,
197 562bf712 Christos Stavrakakis
                                       backend=use_backend,
198 562bf712 Christos Stavrakakis
                                       userid=userid,
199 47c27955 Giorgos Korfiatis
                                       project=project,
200 562bf712 Christos Stavrakakis
                                       imageid=image["id"],
201 562bf712 Christos Stavrakakis
                                       flavor=flavor,
202 562bf712 Christos Stavrakakis
                                       operstate="BUILD")
203 562bf712 Christos Stavrakakis
    log.info("Created entry in DB for VM '%s'", vm)
204 562bf712 Christos Stavrakakis
205 3aecadc8 Christos Stavrakakis
    # Associate the ports with the server
206 3aecadc8 Christos Stavrakakis
    for index, port in enumerate(ports):
207 3aecadc8 Christos Stavrakakis
        associate_port_with_machine(port, vm)
208 3aecadc8 Christos Stavrakakis
        port.index = index
209 3aecadc8 Christos Stavrakakis
        port.save()
210 562bf712 Christos Stavrakakis
211 562bf712 Christos Stavrakakis
    for key, val in metadata.items():
212 562bf712 Christos Stavrakakis
        VirtualMachineMetadata.objects.create(
213 562bf712 Christos Stavrakakis
            meta_key=key,
214 562bf712 Christos Stavrakakis
            meta_value=val,
215 562bf712 Christos Stavrakakis
            vm=vm)
216 41a7fae7 Christos Stavrakakis
217 88fd91af Christos Stavrakakis
    # Create the server in Ganeti.
218 3aecadc8 Christos Stavrakakis
    vm = create_server(vm, ports, flavor, image, personality, password)
219 41a7fae7 Christos Stavrakakis
220 41a7fae7 Christos Stavrakakis
    return vm
221 41a7fae7 Christos Stavrakakis
222 41a7fae7 Christos Stavrakakis
223 562bf712 Christos Stavrakakis
@transaction.commit_on_success
224 562bf712 Christos Stavrakakis
def allocate_new_server(userid, flavor):
225 562bf712 Christos Stavrakakis
    """Allocate a new server to a Ganeti backend.
226 562bf712 Christos Stavrakakis

227 562bf712 Christos Stavrakakis
    Allocation is performed based on the owner of the server and the specified
228 562bf712 Christos Stavrakakis
    flavor. Also, backends that do not have a public IPv4 address are excluded
229 562bf712 Christos Stavrakakis
    from server allocation.
230 562bf712 Christos Stavrakakis

231 562bf712 Christos Stavrakakis
    This function runs inside a transaction, because after allocating the
232 562bf712 Christos Stavrakakis
    instance a commit must be performed in order to release all locks.
233 562bf712 Christos Stavrakakis

234 562bf712 Christos Stavrakakis
    """
235 562bf712 Christos Stavrakakis
    backend_allocator = BackendAllocator()
236 562bf712 Christos Stavrakakis
    use_backend = backend_allocator.allocate(userid, flavor)
237 562bf712 Christos Stavrakakis
    if use_backend is None:
238 562bf712 Christos Stavrakakis
        log.error("No available backend for VM with flavor %s", flavor)
239 562bf712 Christos Stavrakakis
        raise faults.ServiceUnavailable("No available backends")
240 562bf712 Christos Stavrakakis
    return use_backend
241 562bf712 Christos Stavrakakis
242 562bf712 Christos Stavrakakis
243 562bf712 Christos Stavrakakis
@server_command("BUILD")
244 562bf712 Christos Stavrakakis
def create_server(vm, nics, flavor, image, personality, password):
245 562bf712 Christos Stavrakakis
    # dispatch server created signal needed to trigger the 'vmapi', which
246 562bf712 Christos Stavrakakis
    # enriches the vm object with the 'config_url' attribute which must be
247 562bf712 Christos Stavrakakis
    # passed to the Ganeti job.
248 562bf712 Christos Stavrakakis
    server_created.send(sender=vm, created_vm_params={
249 562bf712 Christos Stavrakakis
        'img_id': image['backend_id'],
250 562bf712 Christos Stavrakakis
        'img_passwd': password,
251 562bf712 Christos Stavrakakis
        'img_format': str(image['format']),
252 562bf712 Christos Stavrakakis
        'img_personality': json.dumps(personality),
253 562bf712 Christos Stavrakakis
        'img_properties': json.dumps(image['metadata']),
254 562bf712 Christos Stavrakakis
    })
255 562bf712 Christos Stavrakakis
    # send job to Ganeti
256 88fd91af Christos Stavrakakis
    try:
257 88fd91af Christos Stavrakakis
        jobID = backend.create_instance(vm, nics, flavor, image)
258 88fd91af Christos Stavrakakis
    except:
259 88fd91af Christos Stavrakakis
        log.exception("Failed create instance '%s'", vm)
260 88fd91af Christos Stavrakakis
        jobID = None
261 88fd91af Christos Stavrakakis
        vm.operstate = "ERROR"
262 88fd91af Christos Stavrakakis
        vm.backendlogmsg = "Failed to send job to Ganeti."
263 88fd91af Christos Stavrakakis
        vm.save()
264 88fd91af Christos Stavrakakis
        vm.nics.all().update(state="ERROR")
265 88fd91af Christos Stavrakakis
266 562bf712 Christos Stavrakakis
    # At this point the job is enqueued in the Ganeti backend
267 80a548e3 Christos Stavrakakis
    vm.backendopcode = "OP_INSTANCE_CREATE"
268 562bf712 Christos Stavrakakis
    vm.backendjobid = jobID
269 562bf712 Christos Stavrakakis
    vm.save()
270 562bf712 Christos Stavrakakis
    log.info("User %s created VM %s, NICs %s, Backend %s, JobID %s",
271 2fa6faca Christos Stavrakakis
             vm.userid, vm, nics, vm.backend, str(jobID))
272 562bf712 Christos Stavrakakis
273 562bf712 Christos Stavrakakis
    return jobID
274 562bf712 Christos Stavrakakis
275 562bf712 Christos Stavrakakis
276 41a7fae7 Christos Stavrakakis
@server_command("DESTROY")
277 c397dbce Christos Stavrakakis
def destroy(vm, shutdown_timeout=None):
278 80a548e3 Christos Stavrakakis
    # XXX: Workaround for race where OP_INSTANCE_REMOVE starts executing on
279 80a548e3 Christos Stavrakakis
    # Ganeti before OP_INSTANCE_CREATE. This will be fixed when
280 80a548e3 Christos Stavrakakis
    # OP_INSTANCE_REMOVE supports the 'depends' request attribute.
281 80a548e3 Christos Stavrakakis
    if (vm.backendopcode == "OP_INSTANCE_CREATE" and
282 af1832fe Christos Stavrakakis
       vm.backendjobstatus not in rapi.JOB_STATUS_FINALIZED and
283 80a548e3 Christos Stavrakakis
       backend.job_is_still_running(vm) and
284 80a548e3 Christos Stavrakakis
       not backend.vm_exists_in_backend(vm)):
285 80a548e3 Christos Stavrakakis
            raise faults.BuildInProgress("Server is being build")
286 41a7fae7 Christos Stavrakakis
    log.info("Deleting VM %s", vm)
287 c915a198 Giorgos Korfiatis
    return backend.delete_instance(vm, shutdown_timeout=shutdown_timeout)
288 41a7fae7 Christos Stavrakakis
289 41a7fae7 Christos Stavrakakis
290 41a7fae7 Christos Stavrakakis
@server_command("START")
291 41a7fae7 Christos Stavrakakis
def start(vm):
292 41a7fae7 Christos Stavrakakis
    log.info("Starting VM %s", vm)
293 41a7fae7 Christos Stavrakakis
    return backend.startup_instance(vm)
294 41a7fae7 Christos Stavrakakis
295 41a7fae7 Christos Stavrakakis
296 41a7fae7 Christos Stavrakakis
@server_command("STOP")
297 c397dbce Christos Stavrakakis
def stop(vm, shutdown_timeout=None):
298 41a7fae7 Christos Stavrakakis
    log.info("Stopping VM %s", vm)
299 c915a198 Giorgos Korfiatis
    return backend.shutdown_instance(vm, shutdown_timeout=shutdown_timeout)
300 41a7fae7 Christos Stavrakakis
301 41a7fae7 Christos Stavrakakis
302 41a7fae7 Christos Stavrakakis
@server_command("REBOOT")
303 c397dbce Christos Stavrakakis
def reboot(vm, reboot_type, shutdown_timeout=None):
304 41a7fae7 Christos Stavrakakis
    if reboot_type not in ("SOFT", "HARD"):
305 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Malformed request. Invalid reboot"
306 41a7fae7 Christos Stavrakakis
                                " type %s" % reboot_type)
307 41a7fae7 Christos Stavrakakis
    log.info("Rebooting VM %s. Type %s", vm, reboot_type)
308 41a7fae7 Christos Stavrakakis
309 c397dbce Christos Stavrakakis
    return backend.reboot_instance(vm, reboot_type.lower(),
310 c397dbce Christos Stavrakakis
                                   shutdown_timeout=shutdown_timeout)
311 41a7fae7 Christos Stavrakakis
312 41a7fae7 Christos Stavrakakis
313 41a7fae7 Christos Stavrakakis
def resize(vm, flavor):
314 64bca363 Giorgos Korfiatis
    action_fields = {"beparams": {"vcpus": flavor.cpu,
315 64bca363 Giorgos Korfiatis
                                  "maxmem": flavor.ram}}
316 64bca363 Giorgos Korfiatis
    comm = server_command("RESIZE", action_fields=action_fields)
317 64bca363 Giorgos Korfiatis
    return comm(_resize)(vm, flavor)
318 64bca363 Giorgos Korfiatis
319 64bca363 Giorgos Korfiatis
320 64bca363 Giorgos Korfiatis
def _resize(vm, flavor):
321 41a7fae7 Christos Stavrakakis
    old_flavor = vm.flavor
322 41a7fae7 Christos Stavrakakis
    # User requested the same flavor
323 41a7fae7 Christos Stavrakakis
    if old_flavor.id == flavor.id:
324 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Server '%s' flavor is already '%s'."
325 41a7fae7 Christos Stavrakakis
                                % (vm, flavor))
326 41a7fae7 Christos Stavrakakis
    # Check that resize can be performed
327 41a7fae7 Christos Stavrakakis
    if old_flavor.disk != flavor.disk:
328 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot resize instance disk.")
329 41a7fae7 Christos Stavrakakis
    if old_flavor.disk_template != flavor.disk_template:
330 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot change instance disk template.")
331 41a7fae7 Christos Stavrakakis
332 41a7fae7 Christos Stavrakakis
    log.info("Resizing VM from flavor '%s' to '%s", old_flavor, flavor)
333 41a7fae7 Christos Stavrakakis
    return backend.resize_instance(vm, vcpus=flavor.cpu, memory=flavor.ram)
334 41a7fae7 Christos Stavrakakis
335 41a7fae7 Christos Stavrakakis
336 99667854 Giorgos Korfiatis
@transaction.commit_on_success
337 99667854 Giorgos Korfiatis
def reassign(vm, project):
338 99667854 Giorgos Korfiatis
    action_fields = {"to_project": project, "from_project": vm.project}
339 23f29f98 Giorgos Korfiatis
    log.info("Reassigning VM %s from project %s to %s",
340 23f29f98 Giorgos Korfiatis
             vm, vm.project, project)
341 99667854 Giorgos Korfiatis
    vm.project = project
342 99667854 Giorgos Korfiatis
    vm.save()
343 99667854 Giorgos Korfiatis
    quotas.issue_and_accept_commission(vm, action="REASSIGN",
344 99667854 Giorgos Korfiatis
                                       action_fields=action_fields)
345 99667854 Giorgos Korfiatis
346 99667854 Giorgos Korfiatis
347 41a7fae7 Christos Stavrakakis
@server_command("SET_FIREWALL_PROFILE")
348 d0545590 Christos Stavrakakis
def set_firewall_profile(vm, profile, nic):
349 d0545590 Christos Stavrakakis
    log.info("Setting VM %s, NIC %s, firewall %s", vm, nic, profile)
350 41a7fae7 Christos Stavrakakis
351 41a7fae7 Christos Stavrakakis
    if profile not in [x[0] for x in NetworkInterface.FIREWALL_PROFILES]:
352 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Unsupported firewall profile")
353 d0545590 Christos Stavrakakis
    backend.set_firewall_profile(vm, profile=profile, nic=nic)
354 41a7fae7 Christos Stavrakakis
    return None
355 41a7fae7 Christos Stavrakakis
356 41a7fae7 Christos Stavrakakis
357 41a7fae7 Christos Stavrakakis
@server_command("CONNECT")
358 6b8dc47c Christos Stavrakakis
def connect(vm, network, port=None):
359 6b8dc47c Christos Stavrakakis
    if port is None:
360 fae6e5f0 Christos Stavrakakis
        port = _create_port(vm.userid, network)
361 fae6e5f0 Christos Stavrakakis
    associate_port_with_machine(port, vm)
362 41a7fae7 Christos Stavrakakis
363 fae6e5f0 Christos Stavrakakis
    log.info("Creating NIC %s with IPv4 Address %s", port, port.ipv4_address)
364 41a7fae7 Christos Stavrakakis
365 fae6e5f0 Christos Stavrakakis
    return backend.connect_to_network(vm, port)
366 41a7fae7 Christos Stavrakakis
367 41a7fae7 Christos Stavrakakis
368 41a7fae7 Christos Stavrakakis
@server_command("DISCONNECT")
369 7c714455 Christos Stavrakakis
def disconnect(vm, nic):
370 7c714455 Christos Stavrakakis
    log.info("Removing NIC %s from VM %s", nic, vm)
371 41a7fae7 Christos Stavrakakis
    return backend.disconnect_from_network(vm, nic)
372 41a7fae7 Christos Stavrakakis
373 41a7fae7 Christos Stavrakakis
374 41a7fae7 Christos Stavrakakis
def console(vm, console_type):
375 41a7fae7 Christos Stavrakakis
    """Arrange for an OOB console of the specified type
376 41a7fae7 Christos Stavrakakis

377 41a7fae7 Christos Stavrakakis
    This method arranges for an OOB console of the specified type.
378 41a7fae7 Christos Stavrakakis
    Only consoles of type "vnc" are supported for now.
379 41a7fae7 Christos Stavrakakis

380 41a7fae7 Christos Stavrakakis
    It uses a running instance of vncauthproxy to setup proper
381 41a7fae7 Christos Stavrakakis
    VNC forwarding with a random password, then returns the necessary
382 41a7fae7 Christos Stavrakakis
    VNC connection info to the caller.
383 41a7fae7 Christos Stavrakakis

384 41a7fae7 Christos Stavrakakis
    """
385 41a7fae7 Christos Stavrakakis
    log.info("Get console  VM %s, type %s", vm, console_type)
386 41a7fae7 Christos Stavrakakis
387 41a7fae7 Christos Stavrakakis
    # Use RAPI to get VNC console information for this instance
388 41a7fae7 Christos Stavrakakis
    if vm.operstate != "STARTED":
389 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest('Server not in ACTIVE state.')
390 41a7fae7 Christos Stavrakakis
391 41a7fae7 Christos Stavrakakis
    if settings.TEST:
392 41a7fae7 Christos Stavrakakis
        console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000}
393 41a7fae7 Christos Stavrakakis
    else:
394 41a7fae7 Christos Stavrakakis
        console_data = backend.get_instance_console(vm)
395 41a7fae7 Christos Stavrakakis
396 41a7fae7 Christos Stavrakakis
    if console_data['kind'] != 'vnc':
397 41a7fae7 Christos Stavrakakis
        message = 'got console of kind %s, not "vnc"' % console_data['kind']
398 41a7fae7 Christos Stavrakakis
        raise faults.ServiceUnavailable(message)
399 41a7fae7 Christos Stavrakakis
400 41a7fae7 Christos Stavrakakis
    # Let vncauthproxy decide on the source port.
401 41a7fae7 Christos Stavrakakis
    # The alternative: static allocation, e.g.
402 41a7fae7 Christos Stavrakakis
    # sport = console_data['port'] - 1000
403 41a7fae7 Christos Stavrakakis
    sport = 0
404 41a7fae7 Christos Stavrakakis
    daddr = console_data['host']
405 41a7fae7 Christos Stavrakakis
    dport = console_data['port']
406 41a7fae7 Christos Stavrakakis
    password = util.random_password()
407 41a7fae7 Christos Stavrakakis
408 41a7fae7 Christos Stavrakakis
    if settings.TEST:
409 41a7fae7 Christos Stavrakakis
        fwd = {'source_port': 1234, 'status': 'OK'}
410 41a7fae7 Christos Stavrakakis
    else:
411 f3c5f1df Stratos Psomadakis
        vnc_extra_opts = settings.CYCLADES_VNCAUTHPROXY_OPTS
412 f3c5f1df Stratos Psomadakis
        fwd = request_vnc_forwarding(sport, daddr, dport, password,
413 f3c5f1df Stratos Psomadakis
                                     **vnc_extra_opts)
414 41a7fae7 Christos Stavrakakis
415 41a7fae7 Christos Stavrakakis
    if fwd['status'] != "OK":
416 41a7fae7 Christos Stavrakakis
        raise faults.ServiceUnavailable('vncauthproxy returned error status')
417 41a7fae7 Christos Stavrakakis
418 41a7fae7 Christos Stavrakakis
    # Verify that the VNC server settings haven't changed
419 41a7fae7 Christos Stavrakakis
    if not settings.TEST:
420 41a7fae7 Christos Stavrakakis
        if console_data != backend.get_instance_console(vm):
421 41a7fae7 Christos Stavrakakis
            raise faults.ServiceUnavailable('VNC Server settings changed.')
422 41a7fae7 Christos Stavrakakis
423 41a7fae7 Christos Stavrakakis
    console = {
424 41a7fae7 Christos Stavrakakis
        'type': 'vnc',
425 41a7fae7 Christos Stavrakakis
        'host': getfqdn(),
426 41a7fae7 Christos Stavrakakis
        'port': fwd['source_port'],
427 41a7fae7 Christos Stavrakakis
        'password': password}
428 41a7fae7 Christos Stavrakakis
429 41a7fae7 Christos Stavrakakis
    return console
430 9ba6bb95 Christos Stavrakakis
431 9ba6bb95 Christos Stavrakakis
432 a52cc1b4 Christos Stavrakakis
def rename(server, new_name):
433 a52cc1b4 Christos Stavrakakis
    """Rename a VirtualMachine."""
434 a52cc1b4 Christos Stavrakakis
    old_name = server.name
435 a52cc1b4 Christos Stavrakakis
    server.name = new_name
436 a52cc1b4 Christos Stavrakakis
    server.save()
437 a52cc1b4 Christos Stavrakakis
    log.info("Renamed server '%s' from '%s' to '%s'", server, old_name,
438 a52cc1b4 Christos Stavrakakis
             new_name)
439 a52cc1b4 Christos Stavrakakis
    return server
440 816d7588 Christos Stavrakakis
441 816d7588 Christos Stavrakakis
442 fae6e5f0 Christos Stavrakakis
@transaction.commit_on_success
443 fae6e5f0 Christos Stavrakakis
def create_port(*args, **kwargs):
444 129b94d5 Christos Stavrakakis
    vm = kwargs.get("machine", None)
445 129b94d5 Christos Stavrakakis
    if vm is None and len(args) >= 3:
446 129b94d5 Christos Stavrakakis
        vm = args[2]
447 129b94d5 Christos Stavrakakis
    if vm is not None:
448 129b94d5 Christos Stavrakakis
        if vm.nics.count() == settings.GANETI_MAX_NICS_PER_INSTANCE:
449 129b94d5 Christos Stavrakakis
            raise faults.BadRequest("Maximum ports per server limit reached")
450 fae6e5f0 Christos Stavrakakis
    return _create_port(*args, **kwargs)
451 fae6e5f0 Christos Stavrakakis
452 fae6e5f0 Christos Stavrakakis
453 fae6e5f0 Christos Stavrakakis
def _create_port(userid, network, machine=None, use_ipaddress=None,
454 fae6e5f0 Christos Stavrakakis
                 address=None, name="", security_groups=None,
455 fae6e5f0 Christos Stavrakakis
                 device_owner=None):
456 fae6e5f0 Christos Stavrakakis
    """Create a new port on the specified network.
457 816d7588 Christos Stavrakakis

458 fae6e5f0 Christos Stavrakakis
    Create a new Port(NetworkInterface model) on the specified Network. If
459 fae6e5f0 Christos Stavrakakis
    'machine' is specified, the machine will be connected to the network using
460 fae6e5f0 Christos Stavrakakis
    this port. If 'use_ipaddress' argument is specified, the port will be
461 fae6e5f0 Christos Stavrakakis
    assigned this IPAddress. Otherwise, an IPv4 address from the IPv4 subnet
462 fae6e5f0 Christos Stavrakakis
    will be allocated.
463 816d7588 Christos Stavrakakis

464 816d7588 Christos Stavrakakis
    """
465 fae6e5f0 Christos Stavrakakis
    if network.state != "ACTIVE":
466 8f335041 Christos Stavrakakis
        raise faults.Conflict("Cannot create port while network '%s' is in"
467 8f335041 Christos Stavrakakis
                              " '%s' status" % (network.id, network.state))
468 8f335041 Christos Stavrakakis
    elif network.action == "DESTROY":
469 8d5795b4 Christos Stavrakakis
        msg = "Cannot create port. Network %s is being deleted."
470 3f18f035 Christos Stavrakakis
        raise faults.Conflict(msg % network.id)
471 32b1ed4a Christos Stavrakakis
    elif network.drained:
472 32b1ed4a Christos Stavrakakis
        raise faults.Conflict("Cannot create port while network %s is in"
473 32b1ed4a Christos Stavrakakis
                              " 'SNF:DRAINED' status" % network.id)
474 8f335041 Christos Stavrakakis
475 ba6ad346 Dionysis Grigoropoulos
    utils.check_name_length(name, NetworkInterface.NETWORK_IFACE_NAME_LENGTH,
476 ba6ad346 Dionysis Grigoropoulos
                            "Port name is too long")
477 ba6ad346 Dionysis Grigoropoulos
478 fae6e5f0 Christos Stavrakakis
    ipaddress = None
479 fae6e5f0 Christos Stavrakakis
    if use_ipaddress is not None:
480 fae6e5f0 Christos Stavrakakis
        # Use an existing IPAddress object.
481 fae6e5f0 Christos Stavrakakis
        ipaddress = use_ipaddress
482 fae6e5f0 Christos Stavrakakis
        if ipaddress and (ipaddress.network_id != network.id):
483 fae6e5f0 Christos Stavrakakis
            msg = "IP Address %s does not belong to network %s"
484 fae6e5f0 Christos Stavrakakis
            raise faults.Conflict(msg % (ipaddress.address, network.id))
485 fae6e5f0 Christos Stavrakakis
    else:
486 fae6e5f0 Christos Stavrakakis
        # If network has IPv4 subnets, try to allocate the address that the
487 fae6e5f0 Christos Stavrakakis
        # the user specified or a random one.
488 fae6e5f0 Christos Stavrakakis
        if network.subnets.filter(ipversion=4).exists():
489 0292883e Christos Stavrakakis
            ipaddress = ips.allocate_ip(network, userid=userid,
490 0292883e Christos Stavrakakis
                                        address=address)
491 fae6e5f0 Christos Stavrakakis
        elif address is not None:
492 fae6e5f0 Christos Stavrakakis
            raise faults.BadRequest("Address %s is not a valid IP for the"
493 fae6e5f0 Christos Stavrakakis
                                    " defined network subnets" % address)
494 fae6e5f0 Christos Stavrakakis
495 fae6e5f0 Christos Stavrakakis
    if ipaddress is not None and ipaddress.nic is not None:
496 fae6e5f0 Christos Stavrakakis
        raise faults.Conflict("IP address '%s' is already in use" %
497 fae6e5f0 Christos Stavrakakis
                              ipaddress.address)
498 fae6e5f0 Christos Stavrakakis
499 fae6e5f0 Christos Stavrakakis
    port = NetworkInterface.objects.create(network=network,
500 fae6e5f0 Christos Stavrakakis
                                           state="DOWN",
501 fae6e5f0 Christos Stavrakakis
                                           userid=userid,
502 fae6e5f0 Christos Stavrakakis
                                           device_owner=None,
503 39f723aa Christos Stavrakakis
                                           public=network.public,
504 fae6e5f0 Christos Stavrakakis
                                           name=name)
505 fae6e5f0 Christos Stavrakakis
506 fae6e5f0 Christos Stavrakakis
    # add the security groups if any
507 fae6e5f0 Christos Stavrakakis
    if security_groups:
508 fae6e5f0 Christos Stavrakakis
        port.security_groups.add(*security_groups)
509 fae6e5f0 Christos Stavrakakis
510 fae6e5f0 Christos Stavrakakis
    if ipaddress is not None:
511 fae6e5f0 Christos Stavrakakis
        # Associate IPAddress with the Port
512 fae6e5f0 Christos Stavrakakis
        ipaddress.nic = port
513 fae6e5f0 Christos Stavrakakis
        ipaddress.save()
514 fae6e5f0 Christos Stavrakakis
515 fae6e5f0 Christos Stavrakakis
    if machine is not None:
516 ae2da8a2 Christos Stavrakakis
        # Connect port to the instance.
517 fae6e5f0 Christos Stavrakakis
        machine = connect(machine, network, port)
518 fae6e5f0 Christos Stavrakakis
        jobID = machine.task_job_id
519 fae6e5f0 Christos Stavrakakis
        log.info("Created Port %s with IP %s. Ganeti Job: %s",
520 fae6e5f0 Christos Stavrakakis
                 port, ipaddress, jobID)
521 fae6e5f0 Christos Stavrakakis
    else:
522 fae6e5f0 Christos Stavrakakis
        log.info("Created Port %s with IP %s not attached to any instance",
523 fae6e5f0 Christos Stavrakakis
                 port, ipaddress)
524 9ba6bb95 Christos Stavrakakis
525 fae6e5f0 Christos Stavrakakis
    return port
526 9ba6bb95 Christos Stavrakakis
527 e7f74e7d Christos Stavrakakis
528 fae6e5f0 Christos Stavrakakis
def associate_port_with_machine(port, machine):
529 fae6e5f0 Christos Stavrakakis
    """Associate a Port with a VirtualMachine.
530 9ba6bb95 Christos Stavrakakis

531 fae6e5f0 Christos Stavrakakis
    Associate the port with the VirtualMachine and add an entry to the
532 fae6e5f0 Christos Stavrakakis
    IPAddressLog if the port has a public IPv4 address from a public network.
533 9ba6bb95 Christos Stavrakakis

534 fae6e5f0 Christos Stavrakakis
    """
535 ae2da8a2 Christos Stavrakakis
    if port.machine is not None:
536 ae2da8a2 Christos Stavrakakis
        raise faults.Conflict("Port %s is already in use." % port.id)
537 fae6e5f0 Christos Stavrakakis
    if port.network.public:
538 fae6e5f0 Christos Stavrakakis
        ipv4_address = port.ipv4_address
539 fae6e5f0 Christos Stavrakakis
        if ipv4_address is not None:
540 fae6e5f0 Christos Stavrakakis
            ip_log = IPAddressLog.objects.create(server_id=machine.id,
541 fae6e5f0 Christos Stavrakakis
                                                 network_id=port.network_id,
542 fae6e5f0 Christos Stavrakakis
                                                 address=ipv4_address,
543 fae6e5f0 Christos Stavrakakis
                                                 active=True)
544 fae6e5f0 Christos Stavrakakis
            log.debug("Created IP log entry %s", ip_log)
545 fae6e5f0 Christos Stavrakakis
    port.machine = machine
546 fae6e5f0 Christos Stavrakakis
    port.state = "BUILD"
547 fae6e5f0 Christos Stavrakakis
    port.device_owner = "vm"
548 fae6e5f0 Christos Stavrakakis
    port.save()
549 fae6e5f0 Christos Stavrakakis
    return port
550 9ba6bb95 Christos Stavrakakis
551 9ba6bb95 Christos Stavrakakis
552 fae6e5f0 Christos Stavrakakis
@transaction.commit_on_success
553 fae6e5f0 Christos Stavrakakis
def delete_port(port):
554 fae6e5f0 Christos Stavrakakis
    """Delete a port by removing the NIC card from the instance.
555 9ba6bb95 Christos Stavrakakis

556 fae6e5f0 Christos Stavrakakis
    Send a Job to remove the NIC card from the instance. The port
557 fae6e5f0 Christos Stavrakakis
    will be deleted and the associated IPv4 addressess will be released
558 6e73f499 Christos Stavrakakis
    when the job completes successfully.
559 a52cc1b4 Christos Stavrakakis

560 fae6e5f0 Christos Stavrakakis
    """
561 a52cc1b4 Christos Stavrakakis
562 371ab004 Christos Stavrakakis
    vm = port.machine
563 371ab004 Christos Stavrakakis
    if vm is not None and not vm.deleted:
564 fae6e5f0 Christos Stavrakakis
        vm = disconnect(port.machine, port)
565 fae6e5f0 Christos Stavrakakis
        log.info("Removing port %s, Job: %s", port, vm.task_job_id)
566 fae6e5f0 Christos Stavrakakis
    else:
567 fae6e5f0 Christos Stavrakakis
        backend.remove_nic_ips(port)
568 fae6e5f0 Christos Stavrakakis
        port.delete()
569 fae6e5f0 Christos Stavrakakis
        log.info("Removed port %s", port)
570 fae6e5f0 Christos Stavrakakis
571 fae6e5f0 Christos Stavrakakis
    return port
572 3aecadc8 Christos Stavrakakis
573 3aecadc8 Christos Stavrakakis
574 3aecadc8 Christos Stavrakakis
def create_instance_ports(user_id, networks=None):
575 3aecadc8 Christos Stavrakakis
    # First connect the instance to the networks defined by the admin
576 3aecadc8 Christos Stavrakakis
    forced_ports = create_ports_for_setting(user_id, category="admin")
577 3aecadc8 Christos Stavrakakis
    if networks is None:
578 3aecadc8 Christos Stavrakakis
        # If the user did not asked for any networks, connect instance to
579 3aecadc8 Christos Stavrakakis
        # default networks as defined by the admin
580 3aecadc8 Christos Stavrakakis
        ports = create_ports_for_setting(user_id, category="default")
581 3aecadc8 Christos Stavrakakis
    else:
582 3aecadc8 Christos Stavrakakis
        # Else just connect to the networks that the user defined
583 3aecadc8 Christos Stavrakakis
        ports = create_ports_for_request(user_id, networks)
584 129b94d5 Christos Stavrakakis
    total_ports = forced_ports + ports
585 129b94d5 Christos Stavrakakis
    if len(total_ports) > settings.GANETI_MAX_NICS_PER_INSTANCE:
586 129b94d5 Christos Stavrakakis
        raise faults.BadRequest("Maximum ports per server limit reached")
587 129b94d5 Christos Stavrakakis
    return total_ports
588 3aecadc8 Christos Stavrakakis
589 3aecadc8 Christos Stavrakakis
590 3aecadc8 Christos Stavrakakis
def create_ports_for_setting(user_id, category):
591 3aecadc8 Christos Stavrakakis
    if category == "admin":
592 3aecadc8 Christos Stavrakakis
        network_setting = settings.CYCLADES_FORCED_SERVER_NETWORKS
593 e74a5b4b Christos Stavrakakis
        exception = faults.ServiceUnavailable
594 3aecadc8 Christos Stavrakakis
    elif category == "default":
595 3aecadc8 Christos Stavrakakis
        network_setting = settings.CYCLADES_DEFAULT_SERVER_NETWORKS
596 e74a5b4b Christos Stavrakakis
        exception = faults.Conflict
597 3aecadc8 Christos Stavrakakis
    else:
598 3aecadc8 Christos Stavrakakis
        raise ValueError("Unknown category: %s" % category)
599 3aecadc8 Christos Stavrakakis
600 3aecadc8 Christos Stavrakakis
    ports = []
601 3aecadc8 Christos Stavrakakis
    for network_ids in network_setting:
602 3aecadc8 Christos Stavrakakis
        # Treat even simple network IDs as group of networks with one network
603 3aecadc8 Christos Stavrakakis
        if type(network_ids) not in (list, tuple):
604 3aecadc8 Christos Stavrakakis
            network_ids = [network_ids]
605 3aecadc8 Christos Stavrakakis
606 e74a5b4b Christos Stavrakakis
        error_msgs = []
607 3aecadc8 Christos Stavrakakis
        for network_id in network_ids:
608 c32c74d9 Christos Stavrakakis
            success = False
609 3aecadc8 Christos Stavrakakis
            try:
610 3aecadc8 Christos Stavrakakis
                ports.append(_port_from_setting(user_id, network_id, category))
611 e74a5b4b Christos Stavrakakis
                # Port successfully created in one of the networks. Skip the
612 e74a5b4b Christos Stavrakakis
                # the rest.
613 c32c74d9 Christos Stavrakakis
                success = True
614 3aecadc8 Christos Stavrakakis
                break
615 e74a5b4b Christos Stavrakakis
            except faults.Conflict as e:
616 e74a5b4b Christos Stavrakakis
                if len(network_ids) == 1:
617 e74a5b4b Christos Stavrakakis
                    raise exception(e.message)
618 e74a5b4b Christos Stavrakakis
                else:
619 e74a5b4b Christos Stavrakakis
                    error_msgs.append(e.message)
620 e74a5b4b Christos Stavrakakis
621 c32c74d9 Christos Stavrakakis
        if not success:
622 c32c74d9 Christos Stavrakakis
            if category == "admin":
623 c32c74d9 Christos Stavrakakis
                log.error("Cannot connect server to forced networks '%s': %s",
624 c32c74d9 Christos Stavrakakis
                          network_ids, error_msgs)
625 c32c74d9 Christos Stavrakakis
                raise exception("Cannot connect server to forced server"
626 c32c74d9 Christos Stavrakakis
                                " networks.")
627 c32c74d9 Christos Stavrakakis
            else:
628 c32c74d9 Christos Stavrakakis
                log.debug("Cannot connect server to default networks '%s': %s",
629 c32c74d9 Christos Stavrakakis
                          network_ids, error_msgs)
630 c32c74d9 Christos Stavrakakis
                raise exception("Cannot connect server to default server"
631 c32c74d9 Christos Stavrakakis
                                " networks.")
632 e74a5b4b Christos Stavrakakis
633 3aecadc8 Christos Stavrakakis
    return ports
634 3aecadc8 Christos Stavrakakis
635 3aecadc8 Christos Stavrakakis
636 3aecadc8 Christos Stavrakakis
def _port_from_setting(user_id, network_id, category):
637 3aecadc8 Christos Stavrakakis
    # TODO: Fix this..you need only IPv4 and only IPv6 network
638 3aecadc8 Christos Stavrakakis
    if network_id == "SNF:ANY_PUBLIC_IPV4":
639 3aecadc8 Christos Stavrakakis
        return create_public_ipv4_port(user_id, category=category)
640 3aecadc8 Christos Stavrakakis
    elif network_id == "SNF:ANY_PUBLIC_IPV6":
641 3aecadc8 Christos Stavrakakis
        return create_public_ipv6_port(user_id, category=category)
642 3aecadc8 Christos Stavrakakis
    elif network_id == "SNF:ANY_PUBLIC":
643 3aecadc8 Christos Stavrakakis
        try:
644 3aecadc8 Christos Stavrakakis
            return create_public_ipv4_port(user_id, category=category)
645 e74a5b4b Christos Stavrakakis
        except faults.Conflict as e1:
646 e74a5b4b Christos Stavrakakis
            try:
647 e74a5b4b Christos Stavrakakis
                return create_public_ipv6_port(user_id, category=category)
648 e74a5b4b Christos Stavrakakis
            except faults.Conflict as e2:
649 e74a5b4b Christos Stavrakakis
                log.error("Failed to connect server to a public IPv4 or IPv6"
650 e74a5b4b Christos Stavrakakis
                          " network. IPv4: %s, IPv6: %s", e1, e2)
651 e74a5b4b Christos Stavrakakis
                msg = ("Cannot connect server to a public IPv4 or IPv6"
652 e74a5b4b Christos Stavrakakis
                       " network.")
653 e74a5b4b Christos Stavrakakis
                raise faults.Conflict(msg)
654 3aecadc8 Christos Stavrakakis
    else:  # Case of network ID
655 3aecadc8 Christos Stavrakakis
        if category in ["user", "default"]:
656 3aecadc8 Christos Stavrakakis
            return _port_for_request(user_id, {"uuid": network_id})
657 3aecadc8 Christos Stavrakakis
        elif category == "admin":
658 3aecadc8 Christos Stavrakakis
            network = util.get_network(network_id, user_id, non_deleted=True)
659 3aecadc8 Christos Stavrakakis
            return _create_port(user_id, network)
660 3aecadc8 Christos Stavrakakis
        else:
661 3aecadc8 Christos Stavrakakis
            raise ValueError("Unknown category: %s" % category)
662 3aecadc8 Christos Stavrakakis
663 3aecadc8 Christos Stavrakakis
664 3aecadc8 Christos Stavrakakis
def create_public_ipv4_port(user_id, network=None, address=None,
665 3aecadc8 Christos Stavrakakis
                            category="user"):
666 3aecadc8 Christos Stavrakakis
    """Create a port in a public IPv4 network.
667 3aecadc8 Christos Stavrakakis

668 3aecadc8 Christos Stavrakakis
    Create a port in a public IPv4 network (that may also have an IPv6
669 3aecadc8 Christos Stavrakakis
    subnet). If the category is 'user' or 'default' this will try to use
670 3aecadc8 Christos Stavrakakis
    one of the users floating IPs. If the category is 'admin' will
671 3aecadc8 Christos Stavrakakis
    create a port to the public network (without floating IPs or quotas).
672 3aecadc8 Christos Stavrakakis

673 3aecadc8 Christos Stavrakakis
    """
674 3aecadc8 Christos Stavrakakis
    if category in ["user", "default"]:
675 3aecadc8 Christos Stavrakakis
        if address is None:
676 3aecadc8 Christos Stavrakakis
            ipaddress = ips.get_free_floating_ip(user_id, network)
677 3aecadc8 Christos Stavrakakis
        else:
678 3aecadc8 Christos Stavrakakis
            ipaddress = util.get_floating_ip_by_address(user_id, address,
679 3aecadc8 Christos Stavrakakis
                                                        for_update=True)
680 3aecadc8 Christos Stavrakakis
    elif category == "admin":
681 3aecadc8 Christos Stavrakakis
        if network is None:
682 3aecadc8 Christos Stavrakakis
            ipaddress = ips.allocate_public_ip(user_id)
683 3aecadc8 Christos Stavrakakis
        else:
684 3aecadc8 Christos Stavrakakis
            ipaddress = ips.allocate_ip(network, user_id)
685 3aecadc8 Christos Stavrakakis
    else:
686 3aecadc8 Christos Stavrakakis
        raise ValueError("Unknown category: %s" % category)
687 3aecadc8 Christos Stavrakakis
    if network is None:
688 3aecadc8 Christos Stavrakakis
        network = ipaddress.network
689 3aecadc8 Christos Stavrakakis
    return _create_port(user_id, network, use_ipaddress=ipaddress)
690 3aecadc8 Christos Stavrakakis
691 3aecadc8 Christos Stavrakakis
692 3aecadc8 Christos Stavrakakis
def create_public_ipv6_port(user_id, category=None):
693 3aecadc8 Christos Stavrakakis
    """Create a port in a public IPv6 only network."""
694 3aecadc8 Christos Stavrakakis
    networks = Network.objects.filter(public=True, deleted=False,
695 3aecadc8 Christos Stavrakakis
                                      drained=False, subnets__ipversion=6)\
696 3aecadc8 Christos Stavrakakis
                              .exclude(subnets__ipversion=4)
697 3aecadc8 Christos Stavrakakis
    if networks:
698 3aecadc8 Christos Stavrakakis
        return _create_port(user_id, networks[0])
699 3aecadc8 Christos Stavrakakis
    else:
700 3aecadc8 Christos Stavrakakis
        msg = "No available IPv6 only network!"
701 3aecadc8 Christos Stavrakakis
        log.error(msg)
702 3aecadc8 Christos Stavrakakis
        raise faults.Conflict(msg)
703 3aecadc8 Christos Stavrakakis
704 3aecadc8 Christos Stavrakakis
705 3aecadc8 Christos Stavrakakis
def create_ports_for_request(user_id, networks):
706 3aecadc8 Christos Stavrakakis
    """Create the server ports requested by the user.
707 3aecadc8 Christos Stavrakakis

708 3aecadc8 Christos Stavrakakis
    Create the ports for the new servers as requested in the 'networks'
709 3aecadc8 Christos Stavrakakis
    attribute. The networks attribute contains either a list of network IDs
710 3aecadc8 Christos Stavrakakis
    ('uuid') or a list of ports IDs ('port'). In case of network IDs, the user
711 3aecadc8 Christos Stavrakakis
    can also specify an IPv4 address ('fixed_ip'). In order to connect to a
712 3aecadc8 Christos Stavrakakis
    public network, the 'fixed_ip' attribute must contain the IPv4 address of a
713 3aecadc8 Christos Stavrakakis
    floating IP. If the network is public but the 'fixed_ip' attribute is not
714 3aecadc8 Christos Stavrakakis
    specified, the system will automatically reserve one of the users floating
715 3aecadc8 Christos Stavrakakis
    IPs.
716 3aecadc8 Christos Stavrakakis

717 3aecadc8 Christos Stavrakakis
    """
718 14402edc Christos Stavrakakis
    if not isinstance(networks, list):
719 14402edc Christos Stavrakakis
        raise faults.BadRequest("Malformed request. Invalid 'networks' field")
720 3aecadc8 Christos Stavrakakis
    return [_port_for_request(user_id, network) for network in networks]
721 3aecadc8 Christos Stavrakakis
722 3aecadc8 Christos Stavrakakis
723 3aecadc8 Christos Stavrakakis
def _port_for_request(user_id, network_dict):
724 14402edc Christos Stavrakakis
    if not isinstance(network_dict, dict):
725 14402edc Christos Stavrakakis
        raise faults.BadRequest("Malformed request. Invalid 'networks' field")
726 3aecadc8 Christos Stavrakakis
    port_id = network_dict.get("port")
727 3aecadc8 Christos Stavrakakis
    network_id = network_dict.get("uuid")
728 3aecadc8 Christos Stavrakakis
    if port_id is not None:
729 3aecadc8 Christos Stavrakakis
        return util.get_port(port_id, user_id, for_update=True)
730 3aecadc8 Christos Stavrakakis
    elif network_id is not None:
731 3aecadc8 Christos Stavrakakis
        address = network_dict.get("fixed_ip")
732 3aecadc8 Christos Stavrakakis
        network = util.get_network(network_id, user_id, non_deleted=True)
733 3aecadc8 Christos Stavrakakis
        if network.public:
734 3aecadc8 Christos Stavrakakis
            if network.subnet4 is not None:
735 3aecadc8 Christos Stavrakakis
                if not "fixed_ip" in network_dict:
736 3aecadc8 Christos Stavrakakis
                    return create_public_ipv4_port(user_id, network)
737 3aecadc8 Christos Stavrakakis
                elif address is None:
738 3aecadc8 Christos Stavrakakis
                    msg = "Cannot connect to public network"
739 3aecadc8 Christos Stavrakakis
                    raise faults.BadRequest(msg % network.id)
740 3aecadc8 Christos Stavrakakis
                else:
741 3aecadc8 Christos Stavrakakis
                    return create_public_ipv4_port(user_id, network, address)
742 3aecadc8 Christos Stavrakakis
            else:
743 3aecadc8 Christos Stavrakakis
                raise faults.Forbidden("Cannot connect to IPv6 only public"
744 3aecadc8 Christos Stavrakakis
                                       " network %" % network.id)
745 3aecadc8 Christos Stavrakakis
        else:
746 3aecadc8 Christos Stavrakakis
            return _create_port(user_id, network, address=address)
747 3aecadc8 Christos Stavrakakis
    else:
748 3aecadc8 Christos Stavrakakis
        raise faults.BadRequest("Network 'uuid' or 'port' attribute"
749 3aecadc8 Christos Stavrakakis
                                " is required.")