Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / logic / servers.py @ 64bca363

History | View | Annotate | Download (24.8 kB)

1
import logging
2

    
3
from socket import getfqdn
4
from functools import wraps
5
from django import dispatch
6
from django.db import transaction
7
from django.utils import simplejson as json
8

    
9
from snf_django.lib.api import faults
10
from django.conf import settings
11
from synnefo import quotas
12
from synnefo.api import util
13
from synnefo.logic import backend, ips
14
from synnefo.logic.backend_allocator import BackendAllocator
15
from synnefo.db.models import (NetworkInterface, VirtualMachine,
16
                               VirtualMachineMetadata, IPAddressLog, Network)
17
from vncauthproxy.client import request_forwarding as request_vnc_forwarding
18
from synnefo.logic import rapi
19

    
20
log = logging.getLogger(__name__)
21

    
22
# server creation signal
23
server_created = dispatch.Signal(providing_args=["created_vm_params"])
24

    
25

    
26
def validate_server_action(vm, action):
27
    if vm.deleted:
28
        raise faults.BadRequest("Server '%s' has been deleted." % vm.id)
29

    
30
    # Destroyin a server should always be permitted
31
    if action == "DESTROY":
32
        return
33

    
34
    # Check that there is no pending action
35
    pending_action = vm.task
36
    if pending_action:
37
        if pending_action == "BUILD":
38
            raise faults.BuildInProgress("Server '%s' is being build." % vm.id)
39
        raise faults.BadRequest("Cannot perform '%s' action while there is a"
40
                                " pending '%s'." % (action, pending_action))
41

    
42
    # Check if action can be performed to VM's operstate
43
    operstate = vm.operstate
44
    if operstate == "ERROR":
45
        raise faults.BadRequest("Cannot perform '%s' action while server is"
46
                                " in 'ERROR' state." % action)
47
    elif operstate == "BUILD" and action != "BUILD":
48
        raise faults.BuildInProgress("Server '%s' is being build." % vm.id)
49
    elif (action == "START" and operstate != "STOPPED") or\
50
         (action == "STOP" and operstate != "STARTED") or\
51
         (action == "RESIZE" and operstate != "STOPPED") or\
52
         (action in ["CONNECT", "DISCONNECT"] and operstate != "STOPPED"
53
          and not settings.GANETI_USE_HOTPLUG):
54
        raise faults.BadRequest("Cannot perform '%s' action while server is"
55
                                " in '%s' state." % (action, operstate))
56
    return
57

    
58

    
59
def server_command(action, action_fields=None):
60
    """Handle execution of a server action.
61

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

65
    1) Check if action can be performed. If it can, then there must be no
66
       pending task (with the exception of DESTROY).
67
    2) Handle previous commission if unresolved:
68
       * If it is not pending and it to accept, then accept
69
       * If it is not pending and to reject or is pending then reject it. Since
70
       the action can be performed only if there is no pending task, then there
71
       can be no pending commission. The exception is DESTROY, but in this case
72
       the commission can safely be rejected, and the dispatcher will generate
73
       the correct ones!
74
    3) Issue new commission and associate it with the VM. Also clear the task.
75
    4) Send job to ganeti
76
    5) Update task and commit
77
    """
78
    def decorator(func):
79
        @wraps(func)
80
        @transaction.commit_on_success
81
        def wrapper(vm, *args, **kwargs):
82
            user_id = vm.userid
83
            validate_server_action(vm, action)
84
            vm.action = action
85

    
86
            commission_name = "client: api, resource: %s" % vm
87
            quotas.handle_resource_commission(vm, action=action,
88
                                              action_fields=action_fields,
89
                                              commission_name=commission_name)
90
            vm.save()
91

    
92
            # XXX: Special case for server creation!
93
            if action == "BUILD":
94
                # Perform a commit, because the VirtualMachine must be saved to
95
                # DB before the OP_INSTANCE_CREATE job in enqueued in Ganeti.
96
                # Otherwise, messages will arrive from snf-dispatcher about
97
                # this instance, before the VM is stored in DB.
98
                transaction.commit()
99
                # After committing the locks are released. Refetch the instance
100
                # to guarantee x-lock.
101
                vm = VirtualMachine.objects.select_for_update().get(id=vm.id)
102

    
103
            # Send the job to Ganeti and get the associated jobID
104
            try:
105
                job_id = func(vm, *args, **kwargs)
106
            except Exception as e:
107
                if vm.serial is not None:
108
                    # Since the job never reached Ganeti, reject the commission
109
                    log.debug("Rejecting commission: '%s', could not perform"
110
                              " action '%s': %s" % (vm.serial,  action, e))
111
                    transaction.rollback()
112
                    quotas.reject_serial(vm.serial)
113
                    transaction.commit()
114
                raise
115

    
116
            if action == "BUILD" and vm.serial is not None:
117
                # XXX: Special case for server creation: we must accept the
118
                # commission because the VM has been stored in DB. Also, if
119
                # communication with Ganeti fails, the job will never reach
120
                # Ganeti, and the commission will never be resolved.
121
                quotas.accept_serial(vm.serial)
122

    
123
            log.info("user: %s, vm: %s, action: %s, job_id: %s, serial: %s",
124
                     user_id, vm.id, action, job_id, vm.serial)
125

    
126
            # store the new task in the VM
127
            if job_id is not None:
128
                vm.task = action
129
                vm.task_job_id = job_id
130
            vm.save()
131

    
132
            return vm
133
        return wrapper
134
    return decorator
135

    
136

    
137
@transaction.commit_on_success
138
def create(userid, name, password, flavor, image, metadata={},
139
           personality=[], networks=None, use_backend=None):
140
    if use_backend is None:
141
        # Allocate server to a Ganeti backend
142
        use_backend = allocate_new_server(userid, flavor)
143

    
144
    # Create the ports for the server
145
    try:
146
        ports = create_instance_ports(userid, networks)
147
    except Exception as e:
148
        raise e
149

    
150
    # Fix flavor for archipelago
151
    disk_template, provider = util.get_flavor_provider(flavor)
152
    if provider:
153
        flavor.disk_template = disk_template
154
        flavor.disk_provider = provider
155
        flavor.disk_origin = None
156
        if provider == 'vlmc':
157
            flavor.disk_origin = image['checksum']
158
            image['backend_id'] = 'null'
159
    else:
160
        flavor.disk_provider = None
161

    
162
    # We must save the VM instance now, so that it gets a valid
163
    # vm.backend_vm_id.
164
    vm = VirtualMachine.objects.create(name=name,
165
                                       backend=use_backend,
166
                                       userid=userid,
167
                                       imageid=image["id"],
168
                                       flavor=flavor,
169
                                       operstate="BUILD")
170
    log.info("Created entry in DB for VM '%s'", vm)
171

    
172
    # Associate the ports with the server
173
    for index, port in enumerate(ports):
174
        associate_port_with_machine(port, vm)
175
        port.index = index
176
        port.save()
177

    
178
    for key, val in metadata.items():
179
        VirtualMachineMetadata.objects.create(
180
            meta_key=key,
181
            meta_value=val,
182
            vm=vm)
183

    
184
    # Create the server in Ganeti.
185
    vm = create_server(vm, ports, flavor, image, personality, password)
186

    
187
    return vm
188

    
189

    
190
@transaction.commit_on_success
191
def allocate_new_server(userid, flavor):
192
    """Allocate a new server to a Ganeti backend.
193

194
    Allocation is performed based on the owner of the server and the specified
195
    flavor. Also, backends that do not have a public IPv4 address are excluded
196
    from server allocation.
197

198
    This function runs inside a transaction, because after allocating the
199
    instance a commit must be performed in order to release all locks.
200

201
    """
202
    backend_allocator = BackendAllocator()
203
    use_backend = backend_allocator.allocate(userid, flavor)
204
    if use_backend is None:
205
        log.error("No available backend for VM with flavor %s", flavor)
206
        raise faults.ServiceUnavailable("No available backends")
207
    return use_backend
208

    
209

    
210
@server_command("BUILD")
211
def create_server(vm, nics, flavor, image, personality, password):
212
    # dispatch server created signal needed to trigger the 'vmapi', which
213
    # enriches the vm object with the 'config_url' attribute which must be
214
    # passed to the Ganeti job.
215
    server_created.send(sender=vm, created_vm_params={
216
        'img_id': image['backend_id'],
217
        'img_passwd': password,
218
        'img_format': str(image['format']),
219
        'img_personality': json.dumps(personality),
220
        'img_properties': json.dumps(image['metadata']),
221
    })
222
    # send job to Ganeti
223
    try:
224
        jobID = backend.create_instance(vm, nics, flavor, image)
225
    except:
226
        log.exception("Failed create instance '%s'", vm)
227
        jobID = None
228
        vm.operstate = "ERROR"
229
        vm.backendlogmsg = "Failed to send job to Ganeti."
230
        vm.save()
231
        vm.nics.all().update(state="ERROR")
232

    
233
    # At this point the job is enqueued in the Ganeti backend
234
    vm.backendopcode = "OP_INSTANCE_CREATE"
235
    vm.backendjobid = jobID
236
    vm.save()
237
    log.info("User %s created VM %s, NICs %s, Backend %s, JobID %s",
238
             vm.userid, vm, nics, backend, str(jobID))
239

    
240
    return jobID
241

    
242

    
243
@server_command("DESTROY")
244
def destroy(vm):
245
    # XXX: Workaround for race where OP_INSTANCE_REMOVE starts executing on
246
    # Ganeti before OP_INSTANCE_CREATE. This will be fixed when
247
    # OP_INSTANCE_REMOVE supports the 'depends' request attribute.
248
    if (vm.backendopcode == "OP_INSTANCE_CREATE" and
249
       vm.backendjobstatus not in rapi.JOB_STATUS_FINALIZED and
250
       backend.job_is_still_running(vm) and
251
       not backend.vm_exists_in_backend(vm)):
252
            raise faults.BuildInProgress("Server is being build")
253
    log.info("Deleting VM %s", vm)
254
    return backend.delete_instance(vm)
255

    
256

    
257
@server_command("START")
258
def start(vm):
259
    log.info("Starting VM %s", vm)
260
    return backend.startup_instance(vm)
261

    
262

    
263
@server_command("STOP")
264
def stop(vm):
265
    log.info("Stopping VM %s", vm)
266
    return backend.shutdown_instance(vm)
267

    
268

    
269
@server_command("REBOOT")
270
def reboot(vm, reboot_type):
271
    if reboot_type not in ("SOFT", "HARD"):
272
        raise faults.BadRequest("Malformed request. Invalid reboot"
273
                                " type %s" % reboot_type)
274
    log.info("Rebooting VM %s. Type %s", vm, reboot_type)
275

    
276
    return backend.reboot_instance(vm, reboot_type.lower())
277

    
278

    
279
def resize(vm, flavor):
280
    action_fields = {"beparams": {"vcpus": flavor.cpu,
281
                                  "maxmem": flavor.ram}}
282
    comm = server_command("RESIZE", action_fields=action_fields)
283
    return comm(_resize)(vm, flavor)
284

    
285

    
286
def _resize(vm, flavor):
287
    old_flavor = vm.flavor
288
    # User requested the same flavor
289
    if old_flavor.id == flavor.id:
290
        raise faults.BadRequest("Server '%s' flavor is already '%s'."
291
                                % (vm, flavor))
292
    # Check that resize can be performed
293
    if old_flavor.disk != flavor.disk:
294
        raise faults.BadRequest("Cannot resize instance disk.")
295
    if old_flavor.disk_template != flavor.disk_template:
296
        raise faults.BadRequest("Cannot change instance disk template.")
297

    
298
    log.info("Resizing VM from flavor '%s' to '%s", old_flavor, flavor)
299
    return backend.resize_instance(vm, vcpus=flavor.cpu, memory=flavor.ram)
300

    
301

    
302
@server_command("SET_FIREWALL_PROFILE")
303
def set_firewall_profile(vm, profile, nic):
304
    log.info("Setting VM %s, NIC %s, firewall %s", vm, nic, profile)
305

    
306
    if profile not in [x[0] for x in NetworkInterface.FIREWALL_PROFILES]:
307
        raise faults.BadRequest("Unsupported firewall profile")
308
    backend.set_firewall_profile(vm, profile=profile, nic=nic)
309
    return None
310

    
311

    
312
@server_command("CONNECT")
313
def connect(vm, network, port=None):
314
    if port is None:
315
        port = _create_port(vm.userid, network)
316
    associate_port_with_machine(port, vm)
317

    
318
    log.info("Creating NIC %s with IPv4 Address %s", port, port.ipv4_address)
319

    
320
    return backend.connect_to_network(vm, port)
321

    
322

    
323
@server_command("DISCONNECT")
324
def disconnect(vm, nic):
325
    log.info("Removing NIC %s from VM %s", nic, vm)
326
    return backend.disconnect_from_network(vm, nic)
327

    
328

    
329
def console(vm, console_type):
330
    """Arrange for an OOB console of the specified type
331

332
    This method arranges for an OOB console of the specified type.
333
    Only consoles of type "vnc" are supported for now.
334

335
    It uses a running instance of vncauthproxy to setup proper
336
    VNC forwarding with a random password, then returns the necessary
337
    VNC connection info to the caller.
338

339
    """
340
    log.info("Get console  VM %s, type %s", vm, console_type)
341

    
342
    # Use RAPI to get VNC console information for this instance
343
    if vm.operstate != "STARTED":
344
        raise faults.BadRequest('Server not in ACTIVE state.')
345

    
346
    if settings.TEST:
347
        console_data = {'kind': 'vnc', 'host': 'ganeti_node', 'port': 1000}
348
    else:
349
        console_data = backend.get_instance_console(vm)
350

    
351
    if console_data['kind'] != 'vnc':
352
        message = 'got console of kind %s, not "vnc"' % console_data['kind']
353
        raise faults.ServiceUnavailable(message)
354

    
355
    # Let vncauthproxy decide on the source port.
356
    # The alternative: static allocation, e.g.
357
    # sport = console_data['port'] - 1000
358
    sport = 0
359
    daddr = console_data['host']
360
    dport = console_data['port']
361
    password = util.random_password()
362

    
363
    if settings.TEST:
364
        fwd = {'source_port': 1234, 'status': 'OK'}
365
    else:
366
        fwd = request_vnc_forwarding(sport, daddr, dport, password)
367

    
368
    if fwd['status'] != "OK":
369
        raise faults.ServiceUnavailable('vncauthproxy returned error status')
370

    
371
    # Verify that the VNC server settings haven't changed
372
    if not settings.TEST:
373
        if console_data != backend.get_instance_console(vm):
374
            raise faults.ServiceUnavailable('VNC Server settings changed.')
375

    
376
    console = {
377
        'type': 'vnc',
378
        'host': getfqdn(),
379
        'port': fwd['source_port'],
380
        'password': password}
381

    
382
    return console
383

    
384

    
385
def rename(server, new_name):
386
    """Rename a VirtualMachine."""
387
    old_name = server.name
388
    server.name = new_name
389
    server.save()
390
    log.info("Renamed server '%s' from '%s' to '%s'", server, old_name,
391
             new_name)
392
    return server
393

    
394

    
395
@transaction.commit_on_success
396
def create_port(*args, **kwargs):
397
    return _create_port(*args, **kwargs)
398

    
399

    
400
def _create_port(userid, network, machine=None, use_ipaddress=None,
401
                 address=None, name="", security_groups=None,
402
                 device_owner=None):
403
    """Create a new port on the specified network.
404

405
    Create a new Port(NetworkInterface model) on the specified Network. If
406
    'machine' is specified, the machine will be connected to the network using
407
    this port. If 'use_ipaddress' argument is specified, the port will be
408
    assigned this IPAddress. Otherwise, an IPv4 address from the IPv4 subnet
409
    will be allocated.
410

411
    """
412
    if network.state != "ACTIVE":
413
        raise faults.Conflict("Cannot create port while network '%s' is in"
414
                              " '%s' status" % (network.id, network.state))
415
    elif network.action == "DESTROY":
416
        msg = "Cannot create port. Network %s is being deleted."
417
        raise faults.Conflict(msg % network.id)
418
    elif network.drained:
419
        raise faults.Conflict("Cannot create port while network %s is in"
420
                              " 'SNF:DRAINED' status" % network.id)
421

    
422
    ipaddress = None
423
    if use_ipaddress is not None:
424
        # Use an existing IPAddress object.
425
        ipaddress = use_ipaddress
426
        if ipaddress and (ipaddress.network_id != network.id):
427
            msg = "IP Address %s does not belong to network %s"
428
            raise faults.Conflict(msg % (ipaddress.address, network.id))
429
    else:
430
        # If network has IPv4 subnets, try to allocate the address that the
431
        # the user specified or a random one.
432
        if network.subnets.filter(ipversion=4).exists():
433
            ipaddress = ips.allocate_ip(network, userid=userid,
434
                                        address=address)
435
        elif address is not None:
436
            raise faults.BadRequest("Address %s is not a valid IP for the"
437
                                    " defined network subnets" % address)
438

    
439
    if ipaddress is not None and ipaddress.nic is not None:
440
        raise faults.Conflict("IP address '%s' is already in use" %
441
                              ipaddress.address)
442

    
443
    port = NetworkInterface.objects.create(network=network,
444
                                           state="DOWN",
445
                                           userid=userid,
446
                                           device_owner=None,
447
                                           name=name)
448

    
449
    # add the security groups if any
450
    if security_groups:
451
        port.security_groups.add(*security_groups)
452

    
453
    if ipaddress is not None:
454
        # Associate IPAddress with the Port
455
        ipaddress.nic = port
456
        ipaddress.save()
457

    
458
    if machine is not None:
459
        # Connect port to the instance.
460
        machine = connect(machine, network, port)
461
        jobID = machine.task_job_id
462
        log.info("Created Port %s with IP %s. Ganeti Job: %s",
463
                 port, ipaddress, jobID)
464
    else:
465
        log.info("Created Port %s with IP %s not attached to any instance",
466
                 port, ipaddress)
467

    
468
    return port
469

    
470

    
471
def associate_port_with_machine(port, machine):
472
    """Associate a Port with a VirtualMachine.
473

474
    Associate the port with the VirtualMachine and add an entry to the
475
    IPAddressLog if the port has a public IPv4 address from a public network.
476

477
    """
478
    if port.machine is not None:
479
        raise faults.Conflict("Port %s is already in use." % port.id)
480
    if port.network.public:
481
        ipv4_address = port.ipv4_address
482
        if ipv4_address is not None:
483
            ip_log = IPAddressLog.objects.create(server_id=machine.id,
484
                                                 network_id=port.network_id,
485
                                                 address=ipv4_address,
486
                                                 active=True)
487
            log.debug("Created IP log entry %s", ip_log)
488
    port.machine = machine
489
    port.state = "BUILD"
490
    port.device_owner = "vm"
491
    port.save()
492
    return port
493

    
494

    
495
@transaction.commit_on_success
496
def delete_port(port):
497
    """Delete a port by removing the NIC card from the instance.
498

499
    Send a Job to remove the NIC card from the instance. The port
500
    will be deleted and the associated IPv4 addressess will be released
501
    when the job completes successfully.
502

503
    """
504

    
505
    if port.machine is not None:
506
        vm = disconnect(port.machine, port)
507
        log.info("Removing port %s, Job: %s", port, vm.task_job_id)
508
    else:
509
        backend.remove_nic_ips(port)
510
        port.delete()
511
        log.info("Removed port %s", port)
512

    
513
    return port
514

    
515

    
516
def create_instance_ports(user_id, networks=None):
517
    # First connect the instance to the networks defined by the admin
518
    forced_ports = create_ports_for_setting(user_id, category="admin")
519
    if networks is None:
520
        # If the user did not asked for any networks, connect instance to
521
        # default networks as defined by the admin
522
        ports = create_ports_for_setting(user_id, category="default")
523
    else:
524
        # Else just connect to the networks that the user defined
525
        ports = create_ports_for_request(user_id, networks)
526
    return forced_ports + ports
527

    
528

    
529
def create_ports_for_setting(user_id, category):
530
    if category == "admin":
531
        network_setting = settings.CYCLADES_FORCED_SERVER_NETWORKS
532
    elif category == "default":
533
        network_setting = settings.CYCLADES_DEFAULT_SERVER_NETWORKS
534
    else:
535
        raise ValueError("Unknown category: %s" % category)
536

    
537
    ports = []
538
    for network_ids in network_setting:
539
        # Treat even simple network IDs as group of networks with one network
540
        if type(network_ids) not in (list, tuple):
541
            network_ids = [network_ids]
542

    
543
        for network_id in network_ids:
544
            try:
545
                ports.append(_port_from_setting(user_id, network_id, category))
546
                break
547
            except faults.Conflict:
548
                # Try all network IDs in the network group
549
                pass
550

    
551
            # Diffrent exception for each category!
552
            if category == "admin":
553
                exception = faults.ServiceUnavailable
554
            else:
555
                exception = faults.Conflict
556
            raise exception("Cannot connect instance to any of the following"
557
                            " networks %s" % network_ids)
558
    return ports
559

    
560

    
561
def _port_from_setting(user_id, network_id, category):
562
    # TODO: Fix this..you need only IPv4 and only IPv6 network
563
    if network_id == "SNF:ANY_PUBLIC_IPV4":
564
        return create_public_ipv4_port(user_id, category=category)
565
    elif network_id == "SNF:ANY_PUBLIC_IPV6":
566
        return create_public_ipv6_port(user_id, category=category)
567
    elif network_id == "SNF:ANY_PUBLIC":
568
        try:
569
            return create_public_ipv4_port(user_id, category=category)
570
        except faults.Conflict:
571
            return create_public_ipv6_port(user_id, category=category)
572
    else:  # Case of network ID
573
        if category in ["user", "default"]:
574
            return _port_for_request(user_id, {"uuid": network_id})
575
        elif category == "admin":
576
            network = util.get_network(network_id, user_id, non_deleted=True)
577
            return _create_port(user_id, network)
578
        else:
579
            raise ValueError("Unknown category: %s" % category)
580

    
581

    
582
def create_public_ipv4_port(user_id, network=None, address=None,
583
                            category="user"):
584
    """Create a port in a public IPv4 network.
585

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

591
    """
592
    if category in ["user", "default"]:
593
        if address is None:
594
            ipaddress = ips.get_free_floating_ip(user_id, network)
595
        else:
596
            ipaddress = util.get_floating_ip_by_address(user_id, address,
597
                                                        for_update=True)
598
    elif category == "admin":
599
        if network is None:
600
            ipaddress = ips.allocate_public_ip(user_id)
601
        else:
602
            ipaddress = ips.allocate_ip(network, user_id)
603
    else:
604
        raise ValueError("Unknown category: %s" % category)
605
    if network is None:
606
        network = ipaddress.network
607
    return _create_port(user_id, network, use_ipaddress=ipaddress)
608

    
609

    
610
def create_public_ipv6_port(user_id, category=None):
611
    """Create a port in a public IPv6 only network."""
612
    networks = Network.objects.filter(public=True, deleted=False,
613
                                      drained=False, subnets__ipversion=6)\
614
                              .exclude(subnets__ipversion=4)
615
    if networks:
616
        return _create_port(user_id, networks[0])
617
    else:
618
        msg = "No available IPv6 only network!"
619
        log.error(msg)
620
        raise faults.Conflict(msg)
621

    
622

    
623
def create_ports_for_request(user_id, networks):
624
    """Create the server ports requested by the user.
625

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

635
    """
636
    return [_port_for_request(user_id, network) for network in networks]
637

    
638

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