Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / servers.py @ 435bb7fb

History | View | Annotate | Download (24.8 kB)

1 41a7fae7 Christos Stavrakakis
import logging
2 41a7fae7 Christos Stavrakakis
3 41a7fae7 Christos Stavrakakis
from socket import getfqdn
4 41a7fae7 Christos Stavrakakis
from functools import wraps
5 41a7fae7 Christos Stavrakakis
from django import dispatch
6 41a7fae7 Christos Stavrakakis
from django.db import transaction
7 41a7fae7 Christos Stavrakakis
from django.utils import simplejson as json
8 41a7fae7 Christos Stavrakakis
9 41a7fae7 Christos Stavrakakis
from snf_django.lib.api import faults
10 0c09b1c0 Christos Stavrakakis
from django.conf import settings
11 41a7fae7 Christos Stavrakakis
from synnefo import quotas
12 41a7fae7 Christos Stavrakakis
from synnefo.api import util
13 0292883e Christos Stavrakakis
from synnefo.logic import backend, ips
14 41a7fae7 Christos Stavrakakis
from synnefo.logic.backend_allocator import BackendAllocator
15 710b1c43 Christos Stavrakakis
from synnefo.db.models import (NetworkInterface, VirtualMachine,
16 3aecadc8 Christos Stavrakakis
                               VirtualMachineMetadata, IPAddressLog, Network)
17 41a7fae7 Christos Stavrakakis
from vncauthproxy.client import request_forwarding as request_vnc_forwarding
18 41a7fae7 Christos Stavrakakis
19 41a7fae7 Christos Stavrakakis
log = logging.getLogger(__name__)
20 41a7fae7 Christos Stavrakakis
21 41a7fae7 Christos Stavrakakis
# server creation signal
22 41a7fae7 Christos Stavrakakis
server_created = dispatch.Signal(providing_args=["created_vm_params"])
23 41a7fae7 Christos Stavrakakis
24 41a7fae7 Christos Stavrakakis
25 41a7fae7 Christos Stavrakakis
def validate_server_action(vm, action):
26 41a7fae7 Christos Stavrakakis
    if vm.deleted:
27 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Server '%s' has been deleted." % vm.id)
28 41a7fae7 Christos Stavrakakis
29 41a7fae7 Christos Stavrakakis
    # Destroyin a server should always be permitted
30 41a7fae7 Christos Stavrakakis
    if action == "DESTROY":
31 41a7fae7 Christos Stavrakakis
        return
32 41a7fae7 Christos Stavrakakis
33 41a7fae7 Christos Stavrakakis
    # Check that there is no pending action
34 41a7fae7 Christos Stavrakakis
    pending_action = vm.task
35 41a7fae7 Christos Stavrakakis
    if pending_action:
36 41a7fae7 Christos Stavrakakis
        if pending_action == "BUILD":
37 41a7fae7 Christos Stavrakakis
            raise faults.BuildInProgress("Server '%s' is being build." % vm.id)
38 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot perform '%s' action while there is a"
39 41a7fae7 Christos Stavrakakis
                                " pending '%s'." % (action, pending_action))
40 41a7fae7 Christos Stavrakakis
41 41a7fae7 Christos Stavrakakis
    # Check if action can be performed to VM's operstate
42 41a7fae7 Christos Stavrakakis
    operstate = vm.operstate
43 ee688a71 Christos Stavrakakis
    if operstate == "ERROR":
44 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot perform '%s' action while server is"
45 ee688a71 Christos Stavrakakis
                                " in 'ERROR' state." % action)
46 ee688a71 Christos Stavrakakis
    elif operstate == "BUILD" and action != "BUILD":
47 41a7fae7 Christos Stavrakakis
        raise faults.BuildInProgress("Server '%s' is being build." % vm.id)
48 9599e997 Christos Stavrakakis
    elif (action == "START" and operstate != "STOPPED") or\
49 9599e997 Christos Stavrakakis
         (action == "STOP" and operstate != "STARTED") or\
50 9599e997 Christos Stavrakakis
         (action == "RESIZE" and operstate != "STOPPED") or\
51 9599e997 Christos Stavrakakis
         (action in ["CONNECT", "DISCONNECT"] and operstate != "STOPPED"
52 9599e997 Christos Stavrakakis
          and not settings.GANETI_USE_HOTPLUG):
53 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot perform '%s' action while server is"
54 41a7fae7 Christos Stavrakakis
                                " in '%s' state." % (action, operstate))
55 41a7fae7 Christos Stavrakakis
    return
56 41a7fae7 Christos Stavrakakis
57 41a7fae7 Christos Stavrakakis
58 41a7fae7 Christos Stavrakakis
def server_command(action):
59 41a7fae7 Christos Stavrakakis
    """Handle execution of a server action.
60 41a7fae7 Christos Stavrakakis

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

64 41a7fae7 Christos Stavrakakis
    1) Check if action can be performed. If it can, then there must be no
65 41a7fae7 Christos Stavrakakis
       pending task (with the exception of DESTROY).
66 41a7fae7 Christos Stavrakakis
    2) Handle previous commission if unresolved:
67 41a7fae7 Christos Stavrakakis
       * If it is not pending and it to accept, then accept
68 41a7fae7 Christos Stavrakakis
       * If it is not pending and to reject or is pending then reject it. Since
69 41a7fae7 Christos Stavrakakis
       the action can be performed only if there is no pending task, then there
70 41a7fae7 Christos Stavrakakis
       can be no pending commission. The exception is DESTROY, but in this case
71 41a7fae7 Christos Stavrakakis
       the commission can safely be rejected, and the dispatcher will generate
72 41a7fae7 Christos Stavrakakis
       the correct ones!
73 41a7fae7 Christos Stavrakakis
    3) Issue new commission and associate it with the VM. Also clear the task.
74 41a7fae7 Christos Stavrakakis
    4) Send job to ganeti
75 41a7fae7 Christos Stavrakakis
    5) Update task and commit
76 41a7fae7 Christos Stavrakakis
    """
77 41a7fae7 Christos Stavrakakis
    def decorator(func):
78 41a7fae7 Christos Stavrakakis
        @wraps(func)
79 41a7fae7 Christos Stavrakakis
        @transaction.commit_on_success
80 41a7fae7 Christos Stavrakakis
        def wrapper(vm, *args, **kwargs):
81 41a7fae7 Christos Stavrakakis
            user_id = vm.userid
82 41a7fae7 Christos Stavrakakis
            validate_server_action(vm, action)
83 c9fefba3 Christos Stavrakakis
            vm.action = action
84 41a7fae7 Christos Stavrakakis
85 5c8076b6 Christos Stavrakakis
            commission_name = "client: api, resource: %s" % vm
86 5c8076b6 Christos Stavrakakis
            quotas.handle_resource_commission(vm, action=action,
87 5c8076b6 Christos Stavrakakis
                                              commission_name=commission_name)
88 5c8076b6 Christos Stavrakakis
            vm.save()
89 41a7fae7 Christos Stavrakakis
90 562bf712 Christos Stavrakakis
            # XXX: Special case for server creation!
91 562bf712 Christos Stavrakakis
            if action == "BUILD":
92 562bf712 Christos Stavrakakis
                # Perform a commit, because the VirtualMachine must be saved to
93 562bf712 Christos Stavrakakis
                # DB before the OP_INSTANCE_CREATE job in enqueued in Ganeti.
94 562bf712 Christos Stavrakakis
                # Otherwise, messages will arrive from snf-dispatcher about
95 562bf712 Christos Stavrakakis
                # this instance, before the VM is stored in DB.
96 562bf712 Christos Stavrakakis
                transaction.commit()
97 562bf712 Christos Stavrakakis
                # After committing the locks are released. Refetch the instance
98 562bf712 Christos Stavrakakis
                # to guarantee x-lock.
99 562bf712 Christos Stavrakakis
                vm = VirtualMachine.objects.select_for_update().get(id=vm.id)
100 562bf712 Christos Stavrakakis
101 41a7fae7 Christos Stavrakakis
            # Send the job to Ganeti and get the associated jobID
102 41a7fae7 Christos Stavrakakis
            try:
103 41a7fae7 Christos Stavrakakis
                job_id = func(vm, *args, **kwargs)
104 41a7fae7 Christos Stavrakakis
            except Exception as e:
105 41a7fae7 Christos Stavrakakis
                if vm.serial is not None:
106 41a7fae7 Christos Stavrakakis
                    # Since the job never reached Ganeti, reject the commission
107 41a7fae7 Christos Stavrakakis
                    log.debug("Rejecting commission: '%s', could not perform"
108 41a7fae7 Christos Stavrakakis
                              " action '%s': %s" % (vm.serial,  action, e))
109 41a7fae7 Christos Stavrakakis
                    transaction.rollback()
110 41a7fae7 Christos Stavrakakis
                    quotas.reject_serial(vm.serial)
111 41a7fae7 Christos Stavrakakis
                    transaction.commit()
112 41a7fae7 Christos Stavrakakis
                raise
113 41a7fae7 Christos Stavrakakis
114 88fd91af Christos Stavrakakis
            if action == "BUILD" and vm.serial is not None:
115 88fd91af Christos Stavrakakis
                # XXX: Special case for server creation: we must accept the
116 88fd91af Christos Stavrakakis
                # commission because the VM has been stored in DB. Also, if
117 88fd91af Christos Stavrakakis
                # communication with Ganeti fails, the job will never reach
118 88fd91af Christos Stavrakakis
                # Ganeti, and the commission will never be resolved.
119 88fd91af Christos Stavrakakis
                quotas.accept_serial(vm.serial)
120 88fd91af Christos Stavrakakis
121 41a7fae7 Christos Stavrakakis
            log.info("user: %s, vm: %s, action: %s, job_id: %s, serial: %s",
122 41a7fae7 Christos Stavrakakis
                     user_id, vm.id, action, job_id, vm.serial)
123 41a7fae7 Christos Stavrakakis
124 41a7fae7 Christos Stavrakakis
            # store the new task in the VM
125 41a7fae7 Christos Stavrakakis
            if job_id is not None:
126 41a7fae7 Christos Stavrakakis
                vm.task = action
127 41a7fae7 Christos Stavrakakis
                vm.task_job_id = job_id
128 41a7fae7 Christos Stavrakakis
            vm.save()
129 41a7fae7 Christos Stavrakakis
130 41a7fae7 Christos Stavrakakis
            return vm
131 41a7fae7 Christos Stavrakakis
        return wrapper
132 41a7fae7 Christos Stavrakakis
    return decorator
133 41a7fae7 Christos Stavrakakis
134 41a7fae7 Christos Stavrakakis
135 562bf712 Christos Stavrakakis
@transaction.commit_on_success
136 41a7fae7 Christos Stavrakakis
def create(userid, name, password, flavor, image, metadata={},
137 3aecadc8 Christos Stavrakakis
           personality=[], networks=None, use_backend=None):
138 41a7fae7 Christos Stavrakakis
    if use_backend is None:
139 562bf712 Christos Stavrakakis
        # Allocate server to a Ganeti backend
140 562bf712 Christos Stavrakakis
        use_backend = allocate_new_server(userid, flavor)
141 41a7fae7 Christos Stavrakakis
142 3aecadc8 Christos Stavrakakis
    # Create the ports for the server
143 3aecadc8 Christos Stavrakakis
    try:
144 3aecadc8 Christos Stavrakakis
        ports = create_instance_ports(userid, networks)
145 3aecadc8 Christos Stavrakakis
    except Exception as e:
146 3aecadc8 Christos Stavrakakis
        raise e
147 6193628f Christos Stavrakakis
148 41a7fae7 Christos Stavrakakis
    # Fix flavor for archipelago
149 41a7fae7 Christos Stavrakakis
    disk_template, provider = util.get_flavor_provider(flavor)
150 41a7fae7 Christos Stavrakakis
    if provider:
151 41a7fae7 Christos Stavrakakis
        flavor.disk_template = disk_template
152 41a7fae7 Christos Stavrakakis
        flavor.disk_provider = provider
153 41a7fae7 Christos Stavrakakis
        flavor.disk_origin = None
154 41a7fae7 Christos Stavrakakis
        if provider == 'vlmc':
155 41a7fae7 Christos Stavrakakis
            flavor.disk_origin = image['checksum']
156 41a7fae7 Christos Stavrakakis
            image['backend_id'] = 'null'
157 41a7fae7 Christos Stavrakakis
    else:
158 41a7fae7 Christos Stavrakakis
        flavor.disk_provider = None
159 41a7fae7 Christos Stavrakakis
160 562bf712 Christos Stavrakakis
    # We must save the VM instance now, so that it gets a valid
161 562bf712 Christos Stavrakakis
    # vm.backend_vm_id.
162 562bf712 Christos Stavrakakis
    vm = VirtualMachine.objects.create(name=name,
163 562bf712 Christos Stavrakakis
                                       backend=use_backend,
164 562bf712 Christos Stavrakakis
                                       userid=userid,
165 562bf712 Christos Stavrakakis
                                       imageid=image["id"],
166 562bf712 Christos Stavrakakis
                                       flavor=flavor,
167 562bf712 Christos Stavrakakis
                                       operstate="BUILD")
168 562bf712 Christos Stavrakakis
    log.info("Created entry in DB for VM '%s'", vm)
169 562bf712 Christos Stavrakakis
170 3aecadc8 Christos Stavrakakis
    # Associate the ports with the server
171 3aecadc8 Christos Stavrakakis
    for index, port in enumerate(ports):
172 3aecadc8 Christos Stavrakakis
        associate_port_with_machine(port, vm)
173 3aecadc8 Christos Stavrakakis
        port.index = index
174 3aecadc8 Christos Stavrakakis
        port.save()
175 562bf712 Christos Stavrakakis
176 562bf712 Christos Stavrakakis
    for key, val in metadata.items():
177 562bf712 Christos Stavrakakis
        VirtualMachineMetadata.objects.create(
178 562bf712 Christos Stavrakakis
            meta_key=key,
179 562bf712 Christos Stavrakakis
            meta_value=val,
180 562bf712 Christos Stavrakakis
            vm=vm)
181 41a7fae7 Christos Stavrakakis
182 88fd91af Christos Stavrakakis
    # Create the server in Ganeti.
183 3aecadc8 Christos Stavrakakis
    vm = create_server(vm, ports, flavor, image, personality, password)
184 41a7fae7 Christos Stavrakakis
185 41a7fae7 Christos Stavrakakis
    return vm
186 41a7fae7 Christos Stavrakakis
187 41a7fae7 Christos Stavrakakis
188 562bf712 Christos Stavrakakis
@transaction.commit_on_success
189 562bf712 Christos Stavrakakis
def allocate_new_server(userid, flavor):
190 562bf712 Christos Stavrakakis
    """Allocate a new server to a Ganeti backend.
191 562bf712 Christos Stavrakakis

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

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

199 562bf712 Christos Stavrakakis
    """
200 562bf712 Christos Stavrakakis
    backend_allocator = BackendAllocator()
201 562bf712 Christos Stavrakakis
    use_backend = backend_allocator.allocate(userid, flavor)
202 562bf712 Christos Stavrakakis
    if use_backend is None:
203 562bf712 Christos Stavrakakis
        log.error("No available backend for VM with flavor %s", flavor)
204 562bf712 Christos Stavrakakis
        raise faults.ServiceUnavailable("No available backends")
205 562bf712 Christos Stavrakakis
    return use_backend
206 562bf712 Christos Stavrakakis
207 562bf712 Christos Stavrakakis
208 562bf712 Christos Stavrakakis
@server_command("BUILD")
209 562bf712 Christos Stavrakakis
def create_server(vm, nics, flavor, image, personality, password):
210 562bf712 Christos Stavrakakis
    # dispatch server created signal needed to trigger the 'vmapi', which
211 562bf712 Christos Stavrakakis
    # enriches the vm object with the 'config_url' attribute which must be
212 562bf712 Christos Stavrakakis
    # passed to the Ganeti job.
213 562bf712 Christos Stavrakakis
    server_created.send(sender=vm, created_vm_params={
214 562bf712 Christos Stavrakakis
        'img_id': image['backend_id'],
215 562bf712 Christos Stavrakakis
        'img_passwd': password,
216 562bf712 Christos Stavrakakis
        'img_format': str(image['format']),
217 562bf712 Christos Stavrakakis
        'img_personality': json.dumps(personality),
218 562bf712 Christos Stavrakakis
        'img_properties': json.dumps(image['metadata']),
219 562bf712 Christos Stavrakakis
    })
220 562bf712 Christos Stavrakakis
    # send job to Ganeti
221 88fd91af Christos Stavrakakis
    try:
222 88fd91af Christos Stavrakakis
        jobID = backend.create_instance(vm, nics, flavor, image)
223 88fd91af Christos Stavrakakis
    except:
224 88fd91af Christos Stavrakakis
        log.exception("Failed create instance '%s'", vm)
225 88fd91af Christos Stavrakakis
        jobID = None
226 88fd91af Christos Stavrakakis
        vm.operstate = "ERROR"
227 88fd91af Christos Stavrakakis
        vm.backendlogmsg = "Failed to send job to Ganeti."
228 88fd91af Christos Stavrakakis
        vm.save()
229 88fd91af Christos Stavrakakis
        vm.nics.all().update(state="ERROR")
230 88fd91af Christos Stavrakakis
231 562bf712 Christos Stavrakakis
    # At this point the job is enqueued in the Ganeti backend
232 562bf712 Christos Stavrakakis
    vm.backendjobid = jobID
233 562bf712 Christos Stavrakakis
    vm.save()
234 562bf712 Christos Stavrakakis
    log.info("User %s created VM %s, NICs %s, Backend %s, JobID %s",
235 562bf712 Christos Stavrakakis
             vm.userid, vm, nics, backend, str(jobID))
236 562bf712 Christos Stavrakakis
237 562bf712 Christos Stavrakakis
    return jobID
238 562bf712 Christos Stavrakakis
239 562bf712 Christos Stavrakakis
240 41a7fae7 Christos Stavrakakis
@server_command("DESTROY")
241 41a7fae7 Christos Stavrakakis
def destroy(vm):
242 41a7fae7 Christos Stavrakakis
    log.info("Deleting VM %s", vm)
243 41a7fae7 Christos Stavrakakis
    return backend.delete_instance(vm)
244 41a7fae7 Christos Stavrakakis
245 41a7fae7 Christos Stavrakakis
246 41a7fae7 Christos Stavrakakis
@server_command("START")
247 41a7fae7 Christos Stavrakakis
def start(vm):
248 41a7fae7 Christos Stavrakakis
    log.info("Starting VM %s", vm)
249 41a7fae7 Christos Stavrakakis
    return backend.startup_instance(vm)
250 41a7fae7 Christos Stavrakakis
251 41a7fae7 Christos Stavrakakis
252 41a7fae7 Christos Stavrakakis
@server_command("STOP")
253 41a7fae7 Christos Stavrakakis
def stop(vm):
254 41a7fae7 Christos Stavrakakis
    log.info("Stopping VM %s", vm)
255 41a7fae7 Christos Stavrakakis
    return backend.shutdown_instance(vm)
256 41a7fae7 Christos Stavrakakis
257 41a7fae7 Christos Stavrakakis
258 41a7fae7 Christos Stavrakakis
@server_command("REBOOT")
259 41a7fae7 Christos Stavrakakis
def reboot(vm, reboot_type):
260 41a7fae7 Christos Stavrakakis
    if reboot_type not in ("SOFT", "HARD"):
261 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Malformed request. Invalid reboot"
262 41a7fae7 Christos Stavrakakis
                                " type %s" % reboot_type)
263 41a7fae7 Christos Stavrakakis
    log.info("Rebooting VM %s. Type %s", vm, reboot_type)
264 41a7fae7 Christos Stavrakakis
265 41a7fae7 Christos Stavrakakis
    return backend.reboot_instance(vm, reboot_type.lower())
266 41a7fae7 Christos Stavrakakis
267 41a7fae7 Christos Stavrakakis
268 41a7fae7 Christos Stavrakakis
@server_command("RESIZE")
269 41a7fae7 Christos Stavrakakis
def resize(vm, flavor):
270 41a7fae7 Christos Stavrakakis
    old_flavor = vm.flavor
271 41a7fae7 Christos Stavrakakis
    # User requested the same flavor
272 41a7fae7 Christos Stavrakakis
    if old_flavor.id == flavor.id:
273 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Server '%s' flavor is already '%s'."
274 41a7fae7 Christos Stavrakakis
                                % (vm, flavor))
275 41a7fae7 Christos Stavrakakis
        return None
276 41a7fae7 Christos Stavrakakis
    # Check that resize can be performed
277 41a7fae7 Christos Stavrakakis
    if old_flavor.disk != flavor.disk:
278 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot resize instance disk.")
279 41a7fae7 Christos Stavrakakis
    if old_flavor.disk_template != flavor.disk_template:
280 8d5795b4 Christos Stavrakakis
        raise faults.BadRequest("Cannot change instance disk template.")
281 41a7fae7 Christos Stavrakakis
282 41a7fae7 Christos Stavrakakis
    log.info("Resizing VM from flavor '%s' to '%s", old_flavor, flavor)
283 41a7fae7 Christos Stavrakakis
    commission_info = {"cyclades.cpu": flavor.cpu - old_flavor.cpu,
284 f05a25af Christos Stavrakakis
                       "cyclades.ram": 1048576 * (flavor.ram - old_flavor.ram)}
285 41a7fae7 Christos Stavrakakis
    # Save serial to VM, since it is needed by server_command decorator
286 41a7fae7 Christos Stavrakakis
    vm.serial = quotas.issue_commission(user=vm.userid,
287 41a7fae7 Christos Stavrakakis
                                        source=quotas.DEFAULT_SOURCE,
288 9122ffab Christos Stavrakakis
                                        provisions=commission_info,
289 9122ffab Christos Stavrakakis
                                        name="resource: %s. resize" % vm)
290 41a7fae7 Christos Stavrakakis
    return backend.resize_instance(vm, vcpus=flavor.cpu, memory=flavor.ram)
291 41a7fae7 Christos Stavrakakis
292 41a7fae7 Christos Stavrakakis
293 41a7fae7 Christos Stavrakakis
@server_command("SET_FIREWALL_PROFILE")
294 d0545590 Christos Stavrakakis
def set_firewall_profile(vm, profile, nic):
295 d0545590 Christos Stavrakakis
    log.info("Setting VM %s, NIC %s, firewall %s", vm, nic, profile)
296 41a7fae7 Christos Stavrakakis
297 41a7fae7 Christos Stavrakakis
    if profile not in [x[0] for x in NetworkInterface.FIREWALL_PROFILES]:
298 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest("Unsupported firewall profile")
299 d0545590 Christos Stavrakakis
    backend.set_firewall_profile(vm, profile=profile, nic=nic)
300 41a7fae7 Christos Stavrakakis
    return None
301 41a7fae7 Christos Stavrakakis
302 41a7fae7 Christos Stavrakakis
303 41a7fae7 Christos Stavrakakis
@server_command("CONNECT")
304 6b8dc47c Christos Stavrakakis
def connect(vm, network, port=None):
305 6b8dc47c Christos Stavrakakis
    if port is None:
306 fae6e5f0 Christos Stavrakakis
        port = _create_port(vm.userid, network)
307 fae6e5f0 Christos Stavrakakis
    associate_port_with_machine(port, vm)
308 41a7fae7 Christos Stavrakakis
309 fae6e5f0 Christos Stavrakakis
    log.info("Creating NIC %s with IPv4 Address %s", port, port.ipv4_address)
310 2a2b01e5 Christos Stavrakakis
311 fae6e5f0 Christos Stavrakakis
    return backend.connect_to_network(vm, port)
312 41a7fae7 Christos Stavrakakis
313 41a7fae7 Christos Stavrakakis
314 41a7fae7 Christos Stavrakakis
@server_command("DISCONNECT")
315 7c714455 Christos Stavrakakis
def disconnect(vm, nic):
316 7c714455 Christos Stavrakakis
    log.info("Removing NIC %s from VM %s", nic, vm)
317 41a7fae7 Christos Stavrakakis
    return backend.disconnect_from_network(vm, nic)
318 41a7fae7 Christos Stavrakakis
319 41a7fae7 Christos Stavrakakis
320 41a7fae7 Christos Stavrakakis
def console(vm, console_type):
321 41a7fae7 Christos Stavrakakis
    """Arrange for an OOB console of the specified type
322 41a7fae7 Christos Stavrakakis

323 41a7fae7 Christos Stavrakakis
    This method arranges for an OOB console of the specified type.
324 41a7fae7 Christos Stavrakakis
    Only consoles of type "vnc" are supported for now.
325 41a7fae7 Christos Stavrakakis

326 41a7fae7 Christos Stavrakakis
    It uses a running instance of vncauthproxy to setup proper
327 41a7fae7 Christos Stavrakakis
    VNC forwarding with a random password, then returns the necessary
328 41a7fae7 Christos Stavrakakis
    VNC connection info to the caller.
329 41a7fae7 Christos Stavrakakis

330 41a7fae7 Christos Stavrakakis
    """
331 41a7fae7 Christos Stavrakakis
    log.info("Get console  VM %s, type %s", vm, console_type)
332 41a7fae7 Christos Stavrakakis
333 41a7fae7 Christos Stavrakakis
    # Use RAPI to get VNC console information for this instance
334 41a7fae7 Christos Stavrakakis
    if vm.operstate != "STARTED":
335 41a7fae7 Christos Stavrakakis
        raise faults.BadRequest('Server not in ACTIVE state.')
336 41a7fae7 Christos Stavrakakis
337 41a7fae7 Christos Stavrakakis
    if settings.TEST:
338 41a7fae7 Christos Stavrakakis
        console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000}
339 41a7fae7 Christos Stavrakakis
    else:
340 41a7fae7 Christos Stavrakakis
        console_data = backend.get_instance_console(vm)
341 41a7fae7 Christos Stavrakakis
342 41a7fae7 Christos Stavrakakis
    if console_data['kind'] != 'vnc':
343 41a7fae7 Christos Stavrakakis
        message = 'got console of kind %s, not "vnc"' % console_data['kind']
344 41a7fae7 Christos Stavrakakis
        raise faults.ServiceUnavailable(message)
345 41a7fae7 Christos Stavrakakis
346 41a7fae7 Christos Stavrakakis
    # Let vncauthproxy decide on the source port.
347 41a7fae7 Christos Stavrakakis
    # The alternative: static allocation, e.g.
348 41a7fae7 Christos Stavrakakis
    # sport = console_data['port'] - 1000
349 41a7fae7 Christos Stavrakakis
    sport = 0
350 41a7fae7 Christos Stavrakakis
    daddr = console_data['host']
351 41a7fae7 Christos Stavrakakis
    dport = console_data['port']
352 41a7fae7 Christos Stavrakakis
    password = util.random_password()
353 41a7fae7 Christos Stavrakakis
354 41a7fae7 Christos Stavrakakis
    if settings.TEST:
355 41a7fae7 Christos Stavrakakis
        fwd = {'source_port': 1234, 'status': 'OK'}
356 41a7fae7 Christos Stavrakakis
    else:
357 41a7fae7 Christos Stavrakakis
        fwd = request_vnc_forwarding(sport, daddr, dport, password)
358 41a7fae7 Christos Stavrakakis
359 41a7fae7 Christos Stavrakakis
    if fwd['status'] != "OK":
360 41a7fae7 Christos Stavrakakis
        raise faults.ServiceUnavailable('vncauthproxy returned error status')
361 41a7fae7 Christos Stavrakakis
362 41a7fae7 Christos Stavrakakis
    # Verify that the VNC server settings haven't changed
363 41a7fae7 Christos Stavrakakis
    if not settings.TEST:
364 41a7fae7 Christos Stavrakakis
        if console_data != backend.get_instance_console(vm):
365 41a7fae7 Christos Stavrakakis
            raise faults.ServiceUnavailable('VNC Server settings changed.')
366 41a7fae7 Christos Stavrakakis
367 41a7fae7 Christos Stavrakakis
    console = {
368 41a7fae7 Christos Stavrakakis
        'type': 'vnc',
369 41a7fae7 Christos Stavrakakis
        'host': getfqdn(),
370 41a7fae7 Christos Stavrakakis
        'port': fwd['source_port'],
371 41a7fae7 Christos Stavrakakis
        'password': password}
372 41a7fae7 Christos Stavrakakis
373 41a7fae7 Christos Stavrakakis
    return console
374 9ba6bb95 Christos Stavrakakis
375 9ba6bb95 Christos Stavrakakis
376 a52cc1b4 Christos Stavrakakis
def rename(server, new_name):
377 a52cc1b4 Christos Stavrakakis
    """Rename a VirtualMachine."""
378 a52cc1b4 Christos Stavrakakis
    old_name = server.name
379 a52cc1b4 Christos Stavrakakis
    server.name = new_name
380 a52cc1b4 Christos Stavrakakis
    server.save()
381 a52cc1b4 Christos Stavrakakis
    log.info("Renamed server '%s' from '%s' to '%s'", server, old_name,
382 a52cc1b4 Christos Stavrakakis
             new_name)
383 a52cc1b4 Christos Stavrakakis
    return server
384 fae6e5f0 Christos Stavrakakis
385 fae6e5f0 Christos Stavrakakis
386 fae6e5f0 Christos Stavrakakis
@transaction.commit_on_success
387 fae6e5f0 Christos Stavrakakis
def create_port(*args, **kwargs):
388 fae6e5f0 Christos Stavrakakis
    return _create_port(*args, **kwargs)
389 fae6e5f0 Christos Stavrakakis
390 fae6e5f0 Christos Stavrakakis
391 fae6e5f0 Christos Stavrakakis
def _create_port(userid, network, machine=None, use_ipaddress=None,
392 fae6e5f0 Christos Stavrakakis
                 address=None, name="", security_groups=None,
393 fae6e5f0 Christos Stavrakakis
                 device_owner=None):
394 fae6e5f0 Christos Stavrakakis
    """Create a new port on the specified network.
395 fae6e5f0 Christos Stavrakakis

396 fae6e5f0 Christos Stavrakakis
    Create a new Port(NetworkInterface model) on the specified Network. If
397 fae6e5f0 Christos Stavrakakis
    'machine' is specified, the machine will be connected to the network using
398 fae6e5f0 Christos Stavrakakis
    this port. If 'use_ipaddress' argument is specified, the port will be
399 fae6e5f0 Christos Stavrakakis
    assigned this IPAddress. Otherwise, an IPv4 address from the IPv4 subnet
400 fae6e5f0 Christos Stavrakakis
    will be allocated.
401 fae6e5f0 Christos Stavrakakis

402 fae6e5f0 Christos Stavrakakis
    """
403 fae6e5f0 Christos Stavrakakis
    if network.state != "ACTIVE":
404 8f335041 Christos Stavrakakis
        raise faults.Conflict("Cannot create port while network '%s' is in"
405 8f335041 Christos Stavrakakis
                              " '%s' status" % (network.id, network.state))
406 8f335041 Christos Stavrakakis
    elif network.drained:
407 8f335041 Christos Stavrakakis
        raise faults.Conflict("Cannot create port while network %s is in"
408 8f335041 Christos Stavrakakis
                              " 'SNF:DRAINED' status" % network.id)
409 8f335041 Christos Stavrakakis
    elif network.action == "DESTROY":
410 8d5795b4 Christos Stavrakakis
        msg = "Cannot create port. Network %s is being deleted."
411 3f18f035 Christos Stavrakakis
        raise faults.Conflict(msg % network.id)
412 8f335041 Christos Stavrakakis
413 fae6e5f0 Christos Stavrakakis
    ipaddress = None
414 fae6e5f0 Christos Stavrakakis
    if use_ipaddress is not None:
415 fae6e5f0 Christos Stavrakakis
        # Use an existing IPAddress object.
416 fae6e5f0 Christos Stavrakakis
        ipaddress = use_ipaddress
417 fae6e5f0 Christos Stavrakakis
        if ipaddress and (ipaddress.network_id != network.id):
418 fae6e5f0 Christos Stavrakakis
            msg = "IP Address %s does not belong to network %s"
419 fae6e5f0 Christos Stavrakakis
            raise faults.Conflict(msg % (ipaddress.address, network.id))
420 fae6e5f0 Christos Stavrakakis
    else:
421 fae6e5f0 Christos Stavrakakis
        # If network has IPv4 subnets, try to allocate the address that the
422 fae6e5f0 Christos Stavrakakis
        # the user specified or a random one.
423 fae6e5f0 Christos Stavrakakis
        if network.subnets.filter(ipversion=4).exists():
424 0292883e Christos Stavrakakis
            ipaddress = ips.allocate_ip(network, userid=userid,
425 0292883e Christos Stavrakakis
                                        address=address)
426 fae6e5f0 Christos Stavrakakis
        elif address is not None:
427 fae6e5f0 Christos Stavrakakis
            raise faults.BadRequest("Address %s is not a valid IP for the"
428 fae6e5f0 Christos Stavrakakis
                                    " defined network subnets" % address)
429 fae6e5f0 Christos Stavrakakis
430 fae6e5f0 Christos Stavrakakis
    if ipaddress is not None and ipaddress.nic is not None:
431 fae6e5f0 Christos Stavrakakis
        raise faults.Conflict("IP address '%s' is already in use" %
432 fae6e5f0 Christos Stavrakakis
                              ipaddress.address)
433 fae6e5f0 Christos Stavrakakis
434 fae6e5f0 Christos Stavrakakis
    port = NetworkInterface.objects.create(network=network,
435 fae6e5f0 Christos Stavrakakis
                                           state="DOWN",
436 fae6e5f0 Christos Stavrakakis
                                           userid=userid,
437 fae6e5f0 Christos Stavrakakis
                                           device_owner=None,
438 fae6e5f0 Christos Stavrakakis
                                           name=name)
439 fae6e5f0 Christos Stavrakakis
440 fae6e5f0 Christos Stavrakakis
    # add the security groups if any
441 fae6e5f0 Christos Stavrakakis
    if security_groups:
442 fae6e5f0 Christos Stavrakakis
        port.security_groups.add(*security_groups)
443 fae6e5f0 Christos Stavrakakis
444 fae6e5f0 Christos Stavrakakis
    if ipaddress is not None:
445 fae6e5f0 Christos Stavrakakis
        # Associate IPAddress with the Port
446 fae6e5f0 Christos Stavrakakis
        ipaddress.nic = port
447 fae6e5f0 Christos Stavrakakis
        ipaddress.save()
448 fae6e5f0 Christos Stavrakakis
449 fae6e5f0 Christos Stavrakakis
    if machine is not None:
450 ae2da8a2 Christos Stavrakakis
        # Connect port to the instance.
451 fae6e5f0 Christos Stavrakakis
        machine = connect(machine, network, port)
452 fae6e5f0 Christos Stavrakakis
        jobID = machine.task_job_id
453 fae6e5f0 Christos Stavrakakis
        log.info("Created Port %s with IP %s. Ganeti Job: %s",
454 fae6e5f0 Christos Stavrakakis
                 port, ipaddress, jobID)
455 fae6e5f0 Christos Stavrakakis
    else:
456 fae6e5f0 Christos Stavrakakis
        log.info("Created Port %s with IP %s not attached to any instance",
457 fae6e5f0 Christos Stavrakakis
                 port, ipaddress)
458 fae6e5f0 Christos Stavrakakis
459 fae6e5f0 Christos Stavrakakis
    return port
460 fae6e5f0 Christos Stavrakakis
461 fae6e5f0 Christos Stavrakakis
462 fae6e5f0 Christos Stavrakakis
def associate_port_with_machine(port, machine):
463 fae6e5f0 Christos Stavrakakis
    """Associate a Port with a VirtualMachine.
464 fae6e5f0 Christos Stavrakakis

465 fae6e5f0 Christos Stavrakakis
    Associate the port with the VirtualMachine and add an entry to the
466 fae6e5f0 Christos Stavrakakis
    IPAddressLog if the port has a public IPv4 address from a public network.
467 fae6e5f0 Christos Stavrakakis

468 fae6e5f0 Christos Stavrakakis
    """
469 ae2da8a2 Christos Stavrakakis
    if port.machine is not None:
470 ae2da8a2 Christos Stavrakakis
        raise faults.Conflict("Port %s is already in use." % port.id)
471 fae6e5f0 Christos Stavrakakis
    if port.network.public:
472 fae6e5f0 Christos Stavrakakis
        ipv4_address = port.ipv4_address
473 fae6e5f0 Christos Stavrakakis
        if ipv4_address is not None:
474 fae6e5f0 Christos Stavrakakis
            ip_log = IPAddressLog.objects.create(server_id=machine.id,
475 fae6e5f0 Christos Stavrakakis
                                                 network_id=port.network_id,
476 fae6e5f0 Christos Stavrakakis
                                                 address=ipv4_address,
477 fae6e5f0 Christos Stavrakakis
                                                 active=True)
478 fae6e5f0 Christos Stavrakakis
            log.debug("Created IP log entry %s", ip_log)
479 fae6e5f0 Christos Stavrakakis
    port.machine = machine
480 fae6e5f0 Christos Stavrakakis
    port.state = "BUILD"
481 fae6e5f0 Christos Stavrakakis
    port.device_owner = "vm"
482 fae6e5f0 Christos Stavrakakis
    port.save()
483 fae6e5f0 Christos Stavrakakis
    return port
484 fae6e5f0 Christos Stavrakakis
485 fae6e5f0 Christos Stavrakakis
486 fae6e5f0 Christos Stavrakakis
@transaction.commit_on_success
487 fae6e5f0 Christos Stavrakakis
def delete_port(port):
488 fae6e5f0 Christos Stavrakakis
    """Delete a port by removing the NIC card from the instance.
489 fae6e5f0 Christos Stavrakakis

490 fae6e5f0 Christos Stavrakakis
    Send a Job to remove the NIC card from the instance. The port
491 fae6e5f0 Christos Stavrakakis
    will be deleted and the associated IPv4 addressess will be released
492 3aecadc8 Christos Stavrakakis
    when the job completes successfully. Deleting port that is connected to
493 3aecadc8 Christos Stavrakakis
    a public network is allowed only if the port has an associated floating IP
494 3aecadc8 Christos Stavrakakis
    address.
495 fae6e5f0 Christos Stavrakakis

496 fae6e5f0 Christos Stavrakakis
    """
497 fae6e5f0 Christos Stavrakakis
498 3aecadc8 Christos Stavrakakis
    if port.network.public and not port.ips.filter(floating_ip=True,
499 3aecadc8 Christos Stavrakakis
                                                   deleted=False).exists():
500 8d5795b4 Christos Stavrakakis
        raise faults.Forbidden("Cannot disconnect from public network.")
501 3aecadc8 Christos Stavrakakis
502 fae6e5f0 Christos Stavrakakis
    if port.machine is not None:
503 fae6e5f0 Christos Stavrakakis
        vm = disconnect(port.machine, port)
504 fae6e5f0 Christos Stavrakakis
        log.info("Removing port %s, Job: %s", port, vm.task_job_id)
505 fae6e5f0 Christos Stavrakakis
    else:
506 fae6e5f0 Christos Stavrakakis
        backend.remove_nic_ips(port)
507 fae6e5f0 Christos Stavrakakis
        port.delete()
508 fae6e5f0 Christos Stavrakakis
        log.info("Removed port %s", port)
509 fae6e5f0 Christos Stavrakakis
510 fae6e5f0 Christos Stavrakakis
    return port
511 3aecadc8 Christos Stavrakakis
512 3aecadc8 Christos Stavrakakis
513 3aecadc8 Christos Stavrakakis
def create_instance_ports(user_id, networks=None):
514 3aecadc8 Christos Stavrakakis
    # First connect the instance to the networks defined by the admin
515 3aecadc8 Christos Stavrakakis
    forced_ports = create_ports_for_setting(user_id, category="admin")
516 3aecadc8 Christos Stavrakakis
    if networks is None:
517 3aecadc8 Christos Stavrakakis
        # If the user did not asked for any networks, connect instance to
518 3aecadc8 Christos Stavrakakis
        # default networks as defined by the admin
519 3aecadc8 Christos Stavrakakis
        ports = create_ports_for_setting(user_id, category="default")
520 3aecadc8 Christos Stavrakakis
    else:
521 3aecadc8 Christos Stavrakakis
        # Else just connect to the networks that the user defined
522 3aecadc8 Christos Stavrakakis
        ports = create_ports_for_request(user_id, networks)
523 3aecadc8 Christos Stavrakakis
    return forced_ports + ports
524 3aecadc8 Christos Stavrakakis
525 3aecadc8 Christos Stavrakakis
526 3aecadc8 Christos Stavrakakis
def create_ports_for_setting(user_id, category):
527 3aecadc8 Christos Stavrakakis
    if category == "admin":
528 3aecadc8 Christos Stavrakakis
        network_setting = settings.CYCLADES_FORCED_SERVER_NETWORKS
529 3aecadc8 Christos Stavrakakis
    elif category == "default":
530 3aecadc8 Christos Stavrakakis
        network_setting = settings.CYCLADES_DEFAULT_SERVER_NETWORKS
531 3aecadc8 Christos Stavrakakis
    else:
532 3aecadc8 Christos Stavrakakis
        raise ValueError("Unknown category: %s" % category)
533 3aecadc8 Christos Stavrakakis
534 3aecadc8 Christos Stavrakakis
    ports = []
535 3aecadc8 Christos Stavrakakis
    for network_ids in network_setting:
536 3aecadc8 Christos Stavrakakis
        # Treat even simple network IDs as group of networks with one network
537 3aecadc8 Christos Stavrakakis
        if type(network_ids) not in (list, tuple):
538 3aecadc8 Christos Stavrakakis
            network_ids = [network_ids]
539 3aecadc8 Christos Stavrakakis
540 3aecadc8 Christos Stavrakakis
        for network_id in network_ids:
541 3aecadc8 Christos Stavrakakis
            try:
542 3aecadc8 Christos Stavrakakis
                ports.append(_port_from_setting(user_id, network_id, category))
543 3aecadc8 Christos Stavrakakis
                break
544 3aecadc8 Christos Stavrakakis
            except faults.Conflict:
545 3aecadc8 Christos Stavrakakis
                # Try all network IDs in the network group
546 3aecadc8 Christos Stavrakakis
                pass
547 3aecadc8 Christos Stavrakakis
548 3aecadc8 Christos Stavrakakis
            # Diffrent exception for each category!
549 3aecadc8 Christos Stavrakakis
            if category == "admin":
550 3aecadc8 Christos Stavrakakis
                exception = faults.ServiceUnavailable
551 3aecadc8 Christos Stavrakakis
            else:
552 3aecadc8 Christos Stavrakakis
                exception = faults.Conflict
553 3aecadc8 Christos Stavrakakis
            raise exception("Cannot connect instance to any of the following"
554 3aecadc8 Christos Stavrakakis
                            " networks %s" % network_ids)
555 3aecadc8 Christos Stavrakakis
    return ports
556 3aecadc8 Christos Stavrakakis
557 3aecadc8 Christos Stavrakakis
558 3aecadc8 Christos Stavrakakis
def _port_from_setting(user_id, network_id, category):
559 3aecadc8 Christos Stavrakakis
    # TODO: Fix this..you need only IPv4 and only IPv6 network
560 3aecadc8 Christos Stavrakakis
    if network_id == "SNF:ANY_PUBLIC_IPV4":
561 3aecadc8 Christos Stavrakakis
        return create_public_ipv4_port(user_id, category=category)
562 3aecadc8 Christos Stavrakakis
    elif network_id == "SNF:ANY_PUBLIC_IPV6":
563 3aecadc8 Christos Stavrakakis
        return create_public_ipv6_port(user_id, category=category)
564 3aecadc8 Christos Stavrakakis
    elif network_id == "SNF:ANY_PUBLIC":
565 3aecadc8 Christos Stavrakakis
        try:
566 3aecadc8 Christos Stavrakakis
            return create_public_ipv4_port(user_id, category=category)
567 3aecadc8 Christos Stavrakakis
        except faults.Conflict:
568 3aecadc8 Christos Stavrakakis
            return create_public_ipv6_port(user_id, category=category)
569 3aecadc8 Christos Stavrakakis
    else:  # Case of network ID
570 3aecadc8 Christos Stavrakakis
        if category in ["user", "default"]:
571 3aecadc8 Christos Stavrakakis
            return _port_for_request(user_id, {"uuid": network_id})
572 3aecadc8 Christos Stavrakakis
        elif category == "admin":
573 3aecadc8 Christos Stavrakakis
            network = util.get_network(network_id, user_id, non_deleted=True)
574 3aecadc8 Christos Stavrakakis
            return _create_port(user_id, network)
575 3aecadc8 Christos Stavrakakis
        else:
576 3aecadc8 Christos Stavrakakis
            raise ValueError("Unknown category: %s" % category)
577 3aecadc8 Christos Stavrakakis
578 3aecadc8 Christos Stavrakakis
579 3aecadc8 Christos Stavrakakis
def create_public_ipv4_port(user_id, network=None, address=None,
580 3aecadc8 Christos Stavrakakis
                            category="user"):
581 3aecadc8 Christos Stavrakakis
    """Create a port in a public IPv4 network.
582 3aecadc8 Christos Stavrakakis

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

588 3aecadc8 Christos Stavrakakis
    """
589 3aecadc8 Christos Stavrakakis
    if category in ["user", "default"]:
590 3aecadc8 Christos Stavrakakis
        if address is None:
591 3aecadc8 Christos Stavrakakis
            ipaddress = ips.get_free_floating_ip(user_id, network)
592 3aecadc8 Christos Stavrakakis
        else:
593 3aecadc8 Christos Stavrakakis
            ipaddress = util.get_floating_ip_by_address(user_id, address,
594 3aecadc8 Christos Stavrakakis
                                                        for_update=True)
595 3aecadc8 Christos Stavrakakis
    elif category == "admin":
596 3aecadc8 Christos Stavrakakis
        if network is None:
597 3aecadc8 Christos Stavrakakis
            ipaddress = ips.allocate_public_ip(user_id)
598 3aecadc8 Christos Stavrakakis
        else:
599 3aecadc8 Christos Stavrakakis
            ipaddress = ips.allocate_ip(network, user_id)
600 3aecadc8 Christos Stavrakakis
    else:
601 3aecadc8 Christos Stavrakakis
        raise ValueError("Unknown category: %s" % category)
602 3aecadc8 Christos Stavrakakis
    if network is None:
603 3aecadc8 Christos Stavrakakis
        network = ipaddress.network
604 3aecadc8 Christos Stavrakakis
    return _create_port(user_id, network, use_ipaddress=ipaddress)
605 3aecadc8 Christos Stavrakakis
606 3aecadc8 Christos Stavrakakis
607 3aecadc8 Christos Stavrakakis
def create_public_ipv6_port(user_id, category=None):
608 3aecadc8 Christos Stavrakakis
    """Create a port in a public IPv6 only network."""
609 3aecadc8 Christos Stavrakakis
    networks = Network.objects.filter(public=True, deleted=False,
610 3aecadc8 Christos Stavrakakis
                                      drained=False, subnets__ipversion=6)\
611 3aecadc8 Christos Stavrakakis
                              .exclude(subnets__ipversion=4)
612 3aecadc8 Christos Stavrakakis
    if networks:
613 3aecadc8 Christos Stavrakakis
        return _create_port(user_id, networks[0])
614 3aecadc8 Christos Stavrakakis
    else:
615 3aecadc8 Christos Stavrakakis
        msg = "No available IPv6 only network!"
616 3aecadc8 Christos Stavrakakis
        log.error(msg)
617 3aecadc8 Christos Stavrakakis
        raise faults.Conflict(msg)
618 3aecadc8 Christos Stavrakakis
619 3aecadc8 Christos Stavrakakis
620 3aecadc8 Christos Stavrakakis
def create_ports_for_request(user_id, networks):
621 3aecadc8 Christos Stavrakakis
    """Create the server ports requested by the user.
622 3aecadc8 Christos Stavrakakis

623 3aecadc8 Christos Stavrakakis
    Create the ports for the new servers as requested in the 'networks'
624 3aecadc8 Christos Stavrakakis
    attribute. The networks attribute contains either a list of network IDs
625 3aecadc8 Christos Stavrakakis
    ('uuid') or a list of ports IDs ('port'). In case of network IDs, the user
626 3aecadc8 Christos Stavrakakis
    can also specify an IPv4 address ('fixed_ip'). In order to connect to a
627 3aecadc8 Christos Stavrakakis
    public network, the 'fixed_ip' attribute must contain the IPv4 address of a
628 3aecadc8 Christos Stavrakakis
    floating IP. If the network is public but the 'fixed_ip' attribute is not
629 3aecadc8 Christos Stavrakakis
    specified, the system will automatically reserve one of the users floating
630 3aecadc8 Christos Stavrakakis
    IPs.
631 3aecadc8 Christos Stavrakakis

632 3aecadc8 Christos Stavrakakis
    """
633 3aecadc8 Christos Stavrakakis
    return [_port_for_request(user_id, network) for network in networks]
634 3aecadc8 Christos Stavrakakis
635 3aecadc8 Christos Stavrakakis
636 3aecadc8 Christos Stavrakakis
def _port_for_request(user_id, network_dict):
637 3aecadc8 Christos Stavrakakis
    port_id = network_dict.get("port")
638 3aecadc8 Christos Stavrakakis
    network_id = network_dict.get("uuid")
639 3aecadc8 Christos Stavrakakis
    if port_id is not None:
640 3aecadc8 Christos Stavrakakis
        return util.get_port(port_id, user_id, for_update=True)
641 3aecadc8 Christos Stavrakakis
    elif network_id is not None:
642 3aecadc8 Christos Stavrakakis
        address = network_dict.get("fixed_ip")
643 3aecadc8 Christos Stavrakakis
        network = util.get_network(network_id, user_id, non_deleted=True)
644 3aecadc8 Christos Stavrakakis
        if network.public:
645 3aecadc8 Christos Stavrakakis
            if network.subnet4 is not None:
646 3aecadc8 Christos Stavrakakis
                if not "fixed_ip" in network_dict:
647 3aecadc8 Christos Stavrakakis
                    return create_public_ipv4_port(user_id, network)
648 3aecadc8 Christos Stavrakakis
                elif address is None:
649 3aecadc8 Christos Stavrakakis
                    msg = "Cannot connect to public network"
650 3aecadc8 Christos Stavrakakis
                    raise faults.BadRequest(msg % network.id)
651 3aecadc8 Christos Stavrakakis
                else:
652 3aecadc8 Christos Stavrakakis
                    return create_public_ipv4_port(user_id, network, address)
653 3aecadc8 Christos Stavrakakis
            else:
654 3aecadc8 Christos Stavrakakis
                raise faults.Forbidden("Cannot connect to IPv6 only public"
655 3aecadc8 Christos Stavrakakis
                                       " network %" % network.id)
656 3aecadc8 Christos Stavrakakis
        else:
657 3aecadc8 Christos Stavrakakis
            return _create_port(user_id, network, address=address)
658 3aecadc8 Christos Stavrakakis
    else:
659 3aecadc8 Christos Stavrakakis
        raise faults.BadRequest("Network 'uuid' or 'port' attribute"
660 3aecadc8 Christos Stavrakakis
                                " is required.")