Revision 3aecadc8

b/snf-cyclades-app/conf/20-snf-cyclades-app-api.conf
17 17
## Network Configuration
18 18
##
19 19
#
20
## List of network IDs. All created instances will get a NIC connected to each
21
## network of this list. If the special network ID "SNF:ANY_PUBLIC" is used,
22
## Cyclades will automatically choose a public network and connect the server to
23
## it.
24
#DEFAULT_INSTANCE_NETWORKS=["SNF:ANY_PUBLIC"]
20
## CYCLADES_DEFAULT_SERVER_NETWORKS setting contains a list of networks to
21
## connect a newly created server to, *if the user has not* specified them
22
## explicitly in the POST /server API call.
23
## Each member of the list may be a network UUID, a tuple of network UUIDs,
24
## "SNF:ANY_PUBLIC_IPV4" [any public network with an IPv4 subnet defined],
25
## "SNF:ANY_PUBLIC_IPV6 [any public network with only an IPV6 subnet defined],
26
##  or "SNF:ANY_PUBLIC" [any public network].
27
##
28
## Access control and quota policy are enforced, just as if the user had
29
## specified the value of CYCLADES_DEFAULT_SERVER_NETWORKS in the content
30
## of the POST /call, after processing of "SNF:*" directives."
31
#CYCLADES_DEFAULT_SERVER_NETWORKS = ["SNF:ANY_PUBLIC"]
32
#
33
## This setting contains a list of networks which every new server
34
## will be forced to connect to, regardless of the contents of the POST
35
## /servers call, or the value of CYCLADES_DEFAULT_SERVER_NETWORKS.
36
## Its format is identical to that of CYCLADES_DEFAULT_SERVER_NETWORKS.
37
#
38
## WARNING: No access control or quota policy are enforced.
39
## The server will get all IPv4/IPv6 addresses needed to connect to the
40
## networks specified in CYCLADES_FORCED_SERVER_NETWORKS, regardless
41
## of the state of the floating IP pool of the user, and without
42
## allocating any floating IPs."
43
#CYCLADES_FORCED_SERVER_NETWORKS = ["SNF:ANY_PUBLIC_IPV6"]
25 44
#
26 45
#
27 46
## Maximum allowed network size for private networks.
b/snf-cyclades-app/synnefo/api/management/commands/server-create.py
118 118
        network_ids = parse_list(options["network_ids"])
119 119
        port_ids = parse_list(options["port_ids"])
120 120
        floating_ip_ids = parse_list(options["floating_ip_ids"])
121
        floating_ips = \
122
            map(lambda x: common.get_floating_ip_by_id(x, for_update=True),
123
                floating_ip_ids)
121 124

  
125
        floating_ips = map(lambda fp: {"uuid": fp.network_id,
126
                                       "fixed_ip": fp.address},
127
                           floating_ips)
122 128
        networks = map(lambda x: {"uuid": x}, network_ids)
123 129
        ports = map(lambda x: {"port": x}, port_ids)
124 130

  
125 131
        server = servers.create(user_id, name, password, flavor, image,
126
                                networks=(ports+networks),
127
                                floating_ips=floating_ip_ids,
132
                                networks=(floating_ips+ports+networks),
128 133
                                use_backend=backend)
129 134
        pprint.pprint_server(server, stdout=self.stdout)
130 135

  
b/snf-cyclades-app/synnefo/api/servers.py
378 378
        flavor_id = server['flavorRef']
379 379
        personality = server.get('personality', [])
380 380
        assert isinstance(personality, list)
381
        networks = server.get("networks", [])
382
        assert isinstance(networks, list)
381
        networks = server.get("networks")
382
        if networks is not None:
383
            assert isinstance(networks, list)
383 384
    except (KeyError, AssertionError):
384 385
        raise faults.BadRequest("Malformed request")
385 386

  
b/snf-cyclades-app/synnefo/api/tests/servers.py
340 340
        response = self.mypost('servers/42/metadata/foo')
341 341
        self.assertMethodNotAllowed(response)
342 342

  
343

  
344 343
fixed_image = Mock()
345 344
fixed_image.return_value = {'location': 'pithos://foo',
346 345
                            'checksum': '1234',
......
355 354
class ServerCreateAPITest(ComputeAPITest):
356 355
    def setUp(self):
357 356
        self.flavor = mfactory.FlavorFactory()
358
        # Create public network and backend
359
        subnet = mfactory.IPv4SubnetFactory(network__public=True)
360
        self.network = subnet.network
361 357
        self.backend = mfactory.BackendFactory()
362
        mfactory.BackendNetworkFactory(network=self.network,
363
                                       backend=self.backend,
364
                                       operstate="ACTIVE")
365 358
        self.request = {
366 359
            "server": {
367 360
                "name": "new-server-test",
......
374 367
                "personality": []
375 368
            }
376 369
        }
370
        # Create dummy public IPv6 network
371
        sub6 = mfactory.IPv6SubnetFactory(network__public=True)
372
        self.net6 = sub6.network
373
        self.network_settings = {
374
            "CYCLADES_DEFAULT_SERVER_NETWORKS": [],
375
            "CYCLADES_FORCED_SERVER_NETWORKS": ["SNF:ANY_PUBLIC_IPV6"]
376
        }
377 377

  
378 378
    def test_create_server(self, mrapi):
379 379
        """Test if the create server call returns the expected response
380 380
           if a valid request has been speficied."""
381 381

  
382 382
        mrapi().CreateInstance.return_value = 12
383
        with override_settings(settings, DEFAULT_INSTANCE_NETWORKS=[]):
383
        with override_settings(settings, **self.network_settings):
384 384
            with mocked_quotaholder():
385 385
                response = self.mypost('servers', 'test_user',
386 386
                                       json.dumps(self.request), 'json')
......
398 398
        self.assertEqual(api_server['name'], db_vm.name)
399 399
        self.assertEqual(api_server['status'], db_vm.operstate)
400 400

  
401
        # Test drained flag in Network:
402
        self.network.drained = True
403
        self.network.save()
404
        with mocked_quotaholder():
405
            response = self.mypost('servers', 'test_user',
406
                                   json.dumps(self.request), 'json')
407
        self.assertEqual(response.status_code, 503, "serviceUnavailable")
408

  
409
    def test_create_server_with_port(self, mrapi):
410
        mrapi().CreateInstance.return_value = 42
411
        ip = mfactory.IPv4AddressFactory(nic__machine=None)
412
        port1 = ip.nic
401
    def test_create_server_no_flavor(self, mrapi):
413 402
        request = deepcopy(self.request)
414
        request["server"]["networks"] = [{"port": port1.id}]
415
        with mocked_quotaholder():
416
            response = self.mypost("servers", port1.userid,
417
                                   json.dumps(request), 'json')
403
        request["server"]["flavorRef"] = 42
404
        with override_settings(settings, **self.network_settings):
405
            with mocked_quotaholder():
406
                response = self.mypost('servers', 'test_user',
407
                                       json.dumps(request), 'json')
408
        self.assertItemNotFound(response)
409

  
410
    def test_create_server_error(self, mrapi):
411
        """Test if the create server call returns the expected response
412
           if a valid request has been speficied."""
413
        mrapi().CreateInstance.side_effect = GanetiApiError("..ganeti is down")
414

  
415
        request = self.request
416
        with override_settings(settings, **self.network_settings):
417
            with mocked_quotaholder():
418
                response = self.mypost('servers', 'test_user',
419
                                       json.dumps(request), 'json')
418 420
        self.assertEqual(response.status_code, 202)
419
        vm_id = json.loads(response.content)["server"]["id"]
420
        port1 = NetworkInterface.objects.get(id=port1.id)
421
        self.assertEqual(port1.machine_id, vm_id)
422
        # 409 if already used
423
        with mocked_quotaholder():
424
            response = self.mypost("servers", port1.userid,
425
                                   json.dumps(request), 'json')
426
        self.assertConflict(response)
427
        # Test permissions
428
        ip = mfactory.IPv4AddressFactory(userid="user1", nic__userid="user1")
429
        port2 = ip.nic
430
        request["server"]["networks"] = [{"port": port2.id}]
431
        with mocked_quotaholder():
432
            response = self.mypost("servers", "user2",
433
                                   json.dumps(request), 'json')
434
        self.assertEqual(response.status_code, 404)
421
        mrapi().CreateInstance.assert_called_once()
422
        vm = VirtualMachine.objects.get()
423
        # The VM has not been deleted
424
        self.assertFalse(vm.deleted)
425
        # but is in "ERROR" operstate
426
        self.assertEqual(vm.operstate, "ERROR")
435 427

  
436
    def test_create_network_settings(self, mrapi):
428
    def test_create_network_info(self, mrapi):
437 429
        mrapi().CreateInstance.return_value = 12
438
        # Create public network and backend
439
        subnet1 = mfactory.IPv4SubnetFactory(network__userid="test_user")
440
        bnet1 = mfactory.BackendNetworkFactory(network=subnet1.network,
441
                                               backend=self.backend,
442
                                               operstate="ACTIVE")
443
        subnet2 = mfactory.IPv4SubnetFactory(network__userid="test_user")
444
        bnet2 = mfactory.BackendNetworkFactory(network=subnet2.network,
445
                                               backend=self.backend,
446
                                               operstate="ACTIVE")
430

  
447 431
        # User requested private networks
432
        s1 = mfactory.IPv4SubnetFactory(network__userid="test")
433
        s2 = mfactory.IPv6SubnetFactory(network__userid="test")
434
        # and a public IPv6
448 435
        request = deepcopy(self.request)
449
        request["server"]["networks"] = [{"uuid": bnet1.network.id},
450
                                         {"uuid": bnet2.network.id}]
451
        with override_settings(settings,
452
                               DEFAULT_INSTANCE_NETWORKS=[
453
                                   "SNF:ANY_PUBLIC"]):
436
        request["server"]["networks"] = [{"uuid": s1.network_id},
437
                                         {"uuid": s2.network_id}]
438
        with override_settings(settings, **self.network_settings):
454 439
            with mocked_quotaholder():
455
                response = self.mypost('servers', 'test_user',
440
                response = self.mypost('servers', "test",
456 441
                                       json.dumps(request), 'json')
457 442
        self.assertEqual(response.status_code, 202)
458 443
        name, args, kwargs = mrapi().CreateInstance.mock_calls[0]
459 444
        self.assertEqual(len(kwargs["nics"]), 3)
460
        self.assertEqual(kwargs["nics"][0]["network"],
461
                         self.network.backend_id)
462
        self.assertEqual(kwargs["nics"][1]["network"],
463
                         bnet1.network.backend_id)
464
        self.assertEqual(kwargs["nics"][2]["network"],
465
                         bnet2.network.backend_id)
466

  
467
        subnet3 = mfactory.IPv4SubnetFactory(network__public=True,
468
                                             network__floating_ip_pool=True)
469
        bnet3 = mfactory.BackendNetworkFactory(network=subnet3.network,
470
                                               backend=self.backend,
471
                                               operstate="ACTIVE")
472
        request["server"]["floating_ips"] = []
473
        with override_settings(settings,
474
                               DEFAULT_INSTANCE_NETWORKS=[bnet3.network.id]):
475
            with mocked_quotaholder():
476
                response = self.mypost('servers', 'test_user',
477
                                       json.dumps(request), 'json')
478
        self.assertEqual(response.status_code, 202)
479
        name, args, kwargs = mrapi().CreateInstance.mock_calls[1]
480
        self.assertEqual(len(kwargs["nics"]), 3)
481
        self.assertEqual(kwargs["nics"][0]["network"],
482
                         bnet3.network.backend_id)
483
        self.assertEqual(kwargs["nics"][1]["network"],
484
                         bnet1.network.backend_id)
485
        self.assertEqual(kwargs["nics"][2]["network"],
486
                         bnet2.network.backend_id)
487

  
488
        # test invalid network in DEFAULT_INSTANCE_NETWORKS
489
        with override_settings(settings, DEFAULT_INSTANCE_NETWORKS=[42]):
490
            response = self.mypost('servers', 'test_user',
491
                                   json.dumps(request), 'json')
492
        self.assertFault(response, 500, "internalServerError")
493

  
494
        # test connect to public netwok
445
        self.assertEqual(kwargs["nics"][0]["network"], self.net6.backend_id)
446
        self.assertEqual(kwargs["nics"][1]["network"], s1.network.backend_id)
447
        self.assertEqual(kwargs["nics"][2]["network"], s2.network.backend_id)
448

  
449
        # but fail if others user network
450
        s3 = mfactory.IPv6SubnetFactory(network__userid="test_other")
495 451
        request = deepcopy(self.request)
496
        request["server"]["networks"] = [{"uuid": self.network.id}]
497
        with override_settings(settings,
498
                               DEFAULT_INSTANCE_NETWORKS=["SNF:ANY_PUBLIC"]):
499
            response = self.mypost('servers', 'test_user',
500
                                   json.dumps(request), 'json')
501
        self.assertBadRequest(response)
452
        request["server"]["networks"] = [{"uuid": s3.network_id}]
453
        response = self.mypost('servers', "test", json.dumps(request), 'json')
454
        self.assertEqual(response.status_code, 404)
502 455

  
503
        # test wrong user
456
        # User requested public networks
457
        # but no floating IP..
458
        s1 = mfactory.IPv4SubnetFactory(network__public=True)
504 459
        request = deepcopy(self.request)
505
        request["server"]["networks"] = [{"uuid": bnet1.network.id}]
506
        with override_settings(settings,
507
                               DEFAULT_INSTANCE_NETWORKS=["SNF:ANY_PUBLIC"]):
508
            with mocked_quotaholder():
509
                response = self.mypost('servers', 'dummy_user',
510
                                       json.dumps(request), 'json')
511
        self.assertItemNotFound(response)
460
        request["server"]["networks"] = [{"uuid": s1.network_id}]
461
        response = self.mypost('servers', "test", json.dumps(request), 'json')
462
        self.assertEqual(response.status_code, 409)
512 463

  
513
        # Test floating IPs
464
        # Add one floating IP
465
        fp1 = mfactory.IPv4AddressFactory(userid="test", subnet=s1,
466
                                          network=s1.network,
467
                                          floating_ip=True, nic=None)
468
        self.assertEqual(fp1.nic, None)
514 469
        request = deepcopy(self.request)
515
        fp1 = mfactory.FloatingIPFactory(address="10.0.0.2",
516
                                         userid="test_user",
517
                                         network=self.network,
518
                                         nic=None)
519
        fp2 = mfactory.FloatingIPFactory(address="10.0.0.3",
520
                                         userid="test_user",
521
                                         network=self.network,
522
                                         nic=None)
523
        request["server"]["networks"] = [{"uuid": bnet1.network.id},
524
                                         {"uuid": fp1.network.id,
525
                                          "fixed_ip": fp1.address},
526
                                         {"uuid": fp2.network.id,
527
                                          "fixed_ip": fp2.address}]
528
        with override_settings(settings,
529
                               DEFAULT_INSTANCE_NETWORKS=[bnet3.network.id]):
530
            with mocked_quotaholder():
531
                response = self.mypost('servers', 'test_user',
470
        request["server"]["networks"] = [{"uuid": s1.network_id,
471
                                          "fixed_ip": fp1.address}]
472
        with mocked_quotaholder():
473
            with override_settings(settings, **self.network_settings):
474
                response = self.mypost('servers', "test",
532 475
                                       json.dumps(request), 'json')
533 476
        self.assertEqual(response.status_code, 202)
534
        api_server = json.loads(response.content)['server']
535
        vm = VirtualMachine.objects.get(id=api_server["id"])
536
        fp1 = IPAddress.objects.get(floating_ip=True, id=fp1.id)
537
        fp2 = IPAddress.objects.get(floating_ip=True, id=fp2.id)
538
        self.assertEqual(fp1.nic.machine, vm)
539
        self.assertEqual(fp2.nic.machine, vm)
540
        name, args, kwargs = mrapi().CreateInstance.mock_calls[2]
541
        self.assertEqual(len(kwargs["nics"]), 4)
542
        self.assertEqual(kwargs["nics"][0]["network"],
543
                         bnet3.network.backend_id)
544
        self.assertEqual(kwargs["nics"][1]["network"],
545
                         bnet1.network.backend_id)
546
        self.assertEqual(kwargs["nics"][2]["network"], fp1.network.backend_id)
547
        self.assertEqual(kwargs["nics"][2]["ip"], fp1.address)
548
        self.assertEqual(kwargs["nics"][3]["network"], fp2.network.backend_id)
549
        self.assertEqual(kwargs["nics"][3]["ip"], fp2.address)
477
        server_id = json.loads(response.content)["server"]["id"]
478
        fp1 = IPAddress.objects.get(id=fp1.id)
479
        self.assertEqual(fp1.nic.machine_id, server_id)
550 480

  
551
    def test_create_server_no_flavor(self, mrapi):
481
        # check used floating IP
482
        response = self.mypost('servers', "test", json.dumps(request), 'json')
483
        self.assertEqual(response.status_code, 409)
484

  
485
        # Add more floating IP. but check auto-reserve
486
        fp2 = mfactory.IPv4AddressFactory(userid="test", subnet=s1,
487
                                          network=s1.network,
488
                                          floating_ip=True, nic=None)
489
        self.assertEqual(fp2.nic, None)
552 490
        request = deepcopy(self.request)
553
        request["server"]["flavorRef"] = 42
491
        request["server"]["networks"] = [{"uuid": s1.network_id}]
554 492
        with mocked_quotaholder():
555
            response = self.mypost('servers', 'test_user',
556
                                   json.dumps(request), 'json')
557
        self.assertItemNotFound(response)
493
            with override_settings(settings, **self.network_settings):
494
                response = self.mypost('servers', "test",
495
                                       json.dumps(request), 'json')
496
        self.assertEqual(response.status_code, 202)
497
        server_id = json.loads(response.content)["server"]["id"]
498
        fp2 = IPAddress.objects.get(id=fp2.id)
499
        self.assertEqual(fp2.nic.machine_id, server_id)
558 500

  
559
    def test_create_server_error(self, mrapi):
560
        """Test if the create server call returns the expected response
561
           if a valid request has been speficied."""
562
        mrapi().CreateInstance.side_effect = GanetiApiError("..ganeti is down")
501
        name, args, kwargs = mrapi().CreateInstance.mock_calls[-1]
502
        self.assertEqual(len(kwargs["nics"]), 2)
503
        self.assertEqual(kwargs["nics"][0]["network"], self.net6.backend_id)
504
        self.assertEqual(kwargs["nics"][1]["network"], fp2.network.backend_id)
563 505

  
564
        request = self.request
565
        with mocked_quotaholder():
566
            response = self.mypost('servers', 'test_user',
567
                                   json.dumps(request), 'json')
506
    def test_create_network_settings(self, mrapi):
507
        mrapi().CreateInstance.return_value = 12
508
        # User requested private networks
509
        # no public IPv4
510
        network_settings = {
511
            "CYCLADES_DEFAULT_SERVER_NETWORKS": [],
512
            "CYCLADES_FORCED_SERVER_NETWORKS": ["SNF:ANY_PUBLIC_IPV4"]
513
        }
514
        with override_settings(settings, **network_settings):
515
            response = self.mypost('servers', "test", json.dumps(self.request),
516
                                   'json')
517
        self.assertEqual(response.status_code, 503)
518
        # no public IPv4, IPv6 exists
519
        network_settings = {
520
            "CYCLADES_DEFAULT_SERVER_NETWORKS": [],
521
            "CYCLADES_FORCED_SERVER_NETWORKS": ["SNF:ANY_PUBLIC"]
522
        }
523
        with override_settings(settings, **network_settings):
524
            response = self.mypost('servers', "test", json.dumps(self.request),
525
                                   'json')
568 526
        self.assertEqual(response.status_code, 202)
569
        mrapi().CreateInstance.assert_called_once()
570
        vm = VirtualMachine.objects.get()
571
        # The VM has not been deleted
572
        self.assertFalse(vm.deleted)
573
        # but is in "ERROR" operstate
574
        self.assertEqual(vm.operstate, "ERROR")
527
        server_id = json.loads(response.content)["server"]["id"]
528
        vm = VirtualMachine.objects.get(id=server_id)
529
        self.assertEqual(vm.nics.get().ipv4_address, None)
530

  
531
        # IPv4 exists
532
        mfactory.IPv4SubnetFactory(network__public=True,
533
                                   cidr="192.168.2.0/24",
534
                                   pool__offset=2,
535
                                   pool__size=1)
536
        with override_settings(settings, **network_settings):
537
            response = self.mypost('servers', "test", json.dumps(self.request),
538
                                   'json')
539
        self.assertEqual(response.status_code, 202)
540
        server_id = json.loads(response.content)["server"]["id"]
541
        vm = VirtualMachine.objects.get(id=server_id)
542
        self.assertEqual(vm.nics.get().ipv4_address, "192.168.2.2")
543

  
544
        # Fixed networks
545
        net1 = mfactory.NetworkFactory(userid="test")
546
        net2 = mfactory.NetworkFactory(userid="test")
547
        net3 = mfactory.NetworkFactory(userid="test")
548
        network_settings = {
549
            "CYCLADES_DEFAULT_SERVER_NETWORKS": [],
550
            "CYCLADES_FORCED_SERVER_NETWORKS": [net1.id, [net2.id, net3.id],
551
                                                (net3.id, net2.id)]
552
        }
553
        with override_settings(settings, **network_settings):
554
            response = self.mypost('servers', "test", json.dumps(self.request),
555
                                   'json')
556
        self.assertEqual(response.status_code, 202)
557
        server_id = json.loads(response.content)["server"]["id"]
558
        vm = VirtualMachine.objects.get(id=server_id)
559
        self.assertEqual(len(vm.nics.all()), 3)
560

  
561
    def test_create_server_with_port(self, mrapi):
562
        mrapi().CreateInstance.return_value = 42
563
        ip = mfactory.IPv4AddressFactory(nic__machine=None)
564
        port1 = ip.nic
565
        request = deepcopy(self.request)
566
        request["server"]["networks"] = [{"port": port1.id}]
567
        with override_settings(settings, **self.network_settings):
568
            with mocked_quotaholder():
569
                response = self.mypost("servers", port1.userid,
570
                                       json.dumps(request), 'json')
571
        self.assertEqual(response.status_code, 202)
572
        vm_id = json.loads(response.content)["server"]["id"]
573
        port1 = NetworkInterface.objects.get(id=port1.id)
574
        self.assertEqual(port1.machine_id, vm_id)
575
        # 409 if already used
576
        with override_settings(settings, **self.network_settings):
577
            with mocked_quotaholder():
578
                response = self.mypost("servers", port1.userid,
579
                                       json.dumps(request), 'json')
580
        self.assertConflict(response)
581
        # Test permissions
582
        ip = mfactory.IPv4AddressFactory(userid="user1", nic__userid="user1")
583
        port2 = ip.nic
584
        request["server"]["networks"] = [{"port": port2.id}]
585
        with override_settings(settings, **self.network_settings):
586
            with mocked_quotaholder():
587
                response = self.mypost("servers", "user2",
588
                                       json.dumps(request), 'json')
589
        self.assertEqual(response.status_code, 404)
575 590

  
576 591

  
577 592
@patch('synnefo.logic.rapi_pool.GanetiRapiClient')
b/snf-cyclades-app/synnefo/app_settings/default/api.py
17 17
# Network Configuration
18 18
#
19 19

  
20
# List of network IDs. All created instances will get a NIC connected to each
21
# network of this list. If the special network ID "SNF:ANY_PUBLIC" is used,
22
# Cyclades will automatically choose a public network and connect the server to
23
# it.
24
DEFAULT_INSTANCE_NETWORKS = ["SNF:ANY_PUBLIC"]
20
# CYCLADES_DEFAULT_SERVER_NETWORKS setting contains a list of networks to
21
# connect a newly created server to, *if the user has not* specified them
22
# explicitly in the POST /server API call.
23
# Each member of the list may be a network UUID, a tuple of network UUIDs,
24
# "SNF:ANY_PUBLIC_IPV4" [any public network with an IPv4 subnet defined],
25
# "SNF:ANY_PUBLIC_IPV6 [any public network with only an IPV6 subnet defined],
26
#  or "SNF:ANY_PUBLIC" [any public network].
27
#
28
# Access control and quota policy are enforced, just as if the user had
29
# specified the value of CYCLADES_DEFAULT_SERVER_NETWORKS in the content
30
# of the POST /call, after processing of "SNF:*" directives."
31
CYCLADES_DEFAULT_SERVER_NETWORKS = ["SNF:ANY_PUBLIC"]
32

  
33
# This setting contains a list of networks which every new server
34
# will be forced to connect to, regardless of the contents of the POST
35
# /servers call, or the value of CYCLADES_DEFAULT_SERVER_NETWORKS.
36
# Its format is identical to that of CYCLADES_DEFAULT_SERVER_NETWORKS.
37

  
38
# WARNING: No access control or quota policy are enforced.
39
# The server will get all IPv4/IPv6 addresses needed to connect to the
40
# networks specified in CYCLADES_FORCED_SERVER_NETWORKS, regardless
41
# of the state of the floating IP pool of the user, and without
42
# allocating any floating IPs."
43
CYCLADES_FORCED_SERVER_NETWORKS = ["SNF:ANY_PUBLIC_IPV6"]
25 44

  
26 45
# Maximum allowed network size for private networks.
27 46
MAX_CIDR_BLOCK = 22
b/snf-cyclades-app/synnefo/logic/backend_allocator.py
34 34
from django.conf import settings
35 35
from synnefo.db.models import Backend
36 36
from synnefo.logic import backend as backend_mod
37
from synnefo.api import util
38 37

  
39 38
log = logging.getLogger(__name__)
40 39

  
......
112 111
    backends = backends.filter(offline=False, drained=False,
113 112
                               disk_templates__contains=disk_template)
114 113
    backends = list(backends)
115
    if "SNF:ANY_PUBLIC" in settings.DEFAULT_INSTANCE_NETWORKS:
116
        backends = filter(lambda x: util.backend_has_free_public_ip(x),
117
                          backends)
118 114
    return backends
119 115

  
120 116

  
b/snf-cyclades-app/synnefo/logic/ips.py
61 61
                                (address, network.id))
62 62

  
63 63

  
64
def allocate_public_ip(userid, floating_ip=False, backend=None):
64
def allocate_public_ip(userid, floating_ip=False, backend=None, networks=None):
65 65
    """Try to allocate a public or floating IP address.
66 66

  
67 67
    Try to allocate a a public IPv4 address from one of the available networks.
......
78 78
        .filter(subnet__network__deleted=False)\
79 79
        .filter(subnet__network__public=True)\
80 80
        .filter(subnet__network__drained=False)
81
    if networks is not None:
82
        ip_pool_rows = ip_pool_rows.filter(subnet__network__in=networks)
81 83
    if floating_ip:
82 84
        ip_pool_rows = ip_pool_rows\
83 85
            .filter(subnet__network__floating_ip_pool=True)
......
99 101
            log_msg += " Backend: %s" % backend
100 102
        log.error(log_msg)
101 103
        exception_msg = "Can not allocate a %s IP address." % ip_type
102
        if floating_ip:
103
            raise faults.Conflict(exception_msg)
104
        else:
105
            raise faults.ServiceUnavailable(exception_msg)
104
        raise faults.Conflict(exception_msg)
106 105

  
107 106

  
108 107
@transaction.commit_on_success
......
131 130
    return floating_ip
132 131

  
133 132

  
133
def get_free_floating_ip(userid, network=None):
134
    """Get one of the free available floating IPs of the user.
135

  
136
    Get one of the users floating IPs that is not connected to any port
137
    or server. If network is specified, the floating IP must be from
138
    that network.
139

  
140
    """
141
    floating_ips = IPAddress.objects\
142
                            .filter(userid=userid, deleted=False, nic=None)
143
    if network is not None:
144
        floating_ips = floating_ips.filter(network=network)
145

  
146
    for floating_ip in floating_ips:
147
        floating_ip = IPAddress.objects.select_for_update()\
148
                                       .get(id=floating_ip.id)
149
        if floating_ip.nic is None:
150
            return floating_ip
151

  
152
    msg = "Cannot allocate a floating IP for connecting new server to"
153
    if network is not None:
154
        msg += " network '%s'." % network.id
155
    else:
156
        msg += " a public network."
157
    msg += " Please create more floating IPs."
158
    raise faults.Conflict(msg)
159

  
160

  
134 161
@transaction.commit_on_success
135 162
def delete_floating_ip(floating_ip):
136 163
    if floating_ip.nic:
b/snf-cyclades-app/synnefo/logic/servers.py
13 13
from synnefo.logic import backend, ips
14 14
from synnefo.logic.backend_allocator import BackendAllocator
15 15
from synnefo.db.models import (NetworkInterface, VirtualMachine,
16
                               VirtualMachineMetadata, IPAddressLog)
16
                               VirtualMachineMetadata, IPAddressLog, Network)
17 17
from vncauthproxy.client import request_forwarding as request_vnc_forwarding
18 18

  
19 19
log = logging.getLogger(__name__)
......
131 131

  
132 132
@transaction.commit_on_success
133 133
def create(userid, name, password, flavor, image, metadata={},
134
           personality=[], networks=None, floating_ips=None,
135
           use_backend=None):
134
           personality=[], networks=None, use_backend=None):
136 135
    if use_backend is None:
137 136
        # Allocate server to a Ganeti backend
138 137
        use_backend = allocate_new_server(userid, flavor)
139 138

  
140
    if networks is None:
141
        networks = []
142
    if floating_ips is None:
143
        floating_ips = []
139
    # Create the ports for the server
140
    try:
141
        ports = create_instance_ports(userid, networks)
142
    except Exception as e:
143
        raise e
144 144

  
145 145
    # Fix flavor for archipelago
146 146
    disk_template, provider = util.get_flavor_provider(flavor)
......
164 164
                                       operstate="BUILD")
165 165
    log.info("Created entry in DB for VM '%s'", vm)
166 166

  
167
    nics = create_instance_nics(vm, userid, networks, floating_ips)
167
    # Associate the ports with the server
168
    for index, port in enumerate(ports):
169
        associate_port_with_machine(port, vm)
170
        port.index = index
171
        port.save()
168 172

  
169 173
    for key, val in metadata.items():
170 174
        VirtualMachineMetadata.objects.create(
......
173 177
            vm=vm)
174 178

  
175 179
    # Create the server in Ganeti.
176
    vm = create_server(vm, nics, flavor, image, personality, password)
180
    vm = create_server(vm, ports, flavor, image, personality, password)
177 181

  
178 182
    return vm
179 183

  
......
230 234
    return jobID
231 235

  
232 236

  
233
def create_instance_nics(vm, userid, networks=[], floating_ips=[]):
234
    """Create NICs for VirtualMachine.
235

  
236
    Helper function for allocating IP addresses and creating NICs in the DB
237
    for a VirtualMachine. Created NICs are the combination of the default
238
    network policy (defined by administration settings) and the networks
239
    defined by the user.
240

  
241
    """
242
    ports = []
243
    for network_id in settings.DEFAULT_INSTANCE_NETWORKS:
244
        if network_id == "SNF:ANY_PUBLIC":
245
            ipaddress = ips.allocate_public_ip(userid=userid,
246
                                               backend=vm.backend)
247
            port = _create_port(userid, network=ipaddress.network,
248
                                use_ipaddress=ipaddress)
249
        else:
250
            try:
251
                network = util.get_network(network_id, userid,
252
                                           non_deleted=True)
253
            except faults.ItemNotFound:
254
                msg = "Invalid configuration. Setting"\
255
                      " 'DEFAULT_INSTANCE_NETWORKS' contains invalid"\
256
                      " network '%s'" % network_id
257
                log.error(msg)
258
                raise faults.InternalServerError(msg)
259
            port = _create_port(userid, network)
260
        ports.append(port)
261

  
262
    for floating_ip_id in floating_ips:
263
        floating_ip = util.get_floating_ip_by_id(userid, floating_ip_id,
264
                                                 for_update=True)
265
        port = _create_port(userid, network=floating_ip.network,
266
                            use_ipaddress=floating_ip)
267
        ports.append(port)
268

  
269
    for net in networks:
270
        port_id = net.get("port")
271
        net_id = net.get("uuid")
272
        if port_id is not None:
273
            port = util.get_port(port_id, userid, for_update=True)
274
            ports.append(port)
275
        elif net_id is not None:
276
            address = net.get("fixed_ip")
277
            network = util.get_network(net_id, userid, non_deleted=True)
278
            if network.public:
279
                if address is None:
280
                    msg = ("Can not connect to public network %s. Specify"
281
                           " 'fixed_ip'" " attribute to connect to a public"
282
                           " network")
283
                    raise faults.BadRequest(msg % network.id)
284
                floating_ip = util.get_floating_ip_by_address(userid,
285
                                                              address,
286
                                                              for_update=True)
287
                port = _create_port(userid, network, use_ipaddress=floating_ip)
288
            else:
289
                port = _create_port(userid, network, address=address)
290
            ports.append(port)
291
        else:
292
            raise faults.BadRequest("Network 'uuid' or 'port' attribute"
293
                                    " is required.")
294

  
295
    for index, port in enumerate(ports):
296
        associate_port_with_machine(port, vm)
297
        port.index = index
298
        port.save()
299
    return ports
300

  
301

  
302 237
@server_command("DESTROY")
303 238
def destroy(vm):
304 239
    log.info("Deleting VM %s", vm)
......
547 482

  
548 483
    Send a Job to remove the NIC card from the instance. The port
549 484
    will be deleted and the associated IPv4 addressess will be released
550
    when the job completes successfully.
485
    when the job completes successfully. Deleting port that is connected to
486
    a public network is allowed only if the port has an associated floating IP
487
    address.
551 488

  
552 489
    """
553 490

  
491
    if port.network.public and not port.ips.filter(floating_ip=True,
492
                                                   deleted=False).exists():
493
        raise faults.Forbidden("Can not disconnect from public network.")
494

  
554 495
    if port.machine is not None:
555 496
        vm = disconnect(port.machine, port)
556 497
        log.info("Removing port %s, Job: %s", port, vm.task_job_id)
......
560 501
        log.info("Removed port %s", port)
561 502

  
562 503
    return port
504

  
505

  
506
def create_instance_ports(user_id, networks=None):
507
    # First connect the instance to the networks defined by the admin
508
    forced_ports = create_ports_for_setting(user_id, category="admin")
509
    if networks is None:
510
        # If the user did not asked for any networks, connect instance to
511
        # default networks as defined by the admin
512
        ports = create_ports_for_setting(user_id, category="default")
513
    else:
514
        # Else just connect to the networks that the user defined
515
        ports = create_ports_for_request(user_id, networks)
516
    return forced_ports + ports
517

  
518

  
519
def create_ports_for_setting(user_id, category):
520
    if category == "admin":
521
        network_setting = settings.CYCLADES_FORCED_SERVER_NETWORKS
522
    elif category == "default":
523
        network_setting = settings.CYCLADES_DEFAULT_SERVER_NETWORKS
524
    else:
525
        raise ValueError("Unknown category: %s" % category)
526

  
527
    ports = []
528
    for network_ids in network_setting:
529
        # Treat even simple network IDs as group of networks with one network
530
        if type(network_ids) not in (list, tuple):
531
            network_ids = [network_ids]
532

  
533
        for network_id in network_ids:
534
            try:
535
                ports.append(_port_from_setting(user_id, network_id, category))
536
                break
537
            except faults.Conflict:
538
                # Try all network IDs in the network group
539
                pass
540

  
541
            # Diffrent exception for each category!
542
            if category == "admin":
543
                exception = faults.ServiceUnavailable
544
            else:
545
                exception = faults.Conflict
546
            raise exception("Cannot connect instance to any of the following"
547
                            " networks %s" % network_ids)
548
    return ports
549

  
550

  
551
def _port_from_setting(user_id, network_id, category):
552
    # TODO: Fix this..you need only IPv4 and only IPv6 network
553
    if network_id == "SNF:ANY_PUBLIC_IPV4":
554
        return create_public_ipv4_port(user_id, category=category)
555
    elif network_id == "SNF:ANY_PUBLIC_IPV6":
556
        return create_public_ipv6_port(user_id, category=category)
557
    elif network_id == "SNF:ANY_PUBLIC":
558
        try:
559
            return create_public_ipv4_port(user_id, category=category)
560
        except faults.Conflict:
561
            return create_public_ipv6_port(user_id, category=category)
562
    else:  # Case of network ID
563
        if category in ["user", "default"]:
564
            return _port_for_request(user_id, {"uuid": network_id})
565
        elif category == "admin":
566
            network = util.get_network(network_id, user_id, non_deleted=True)
567
            return _create_port(user_id, network)
568
        else:
569
            raise ValueError("Unknown category: %s" % category)
570

  
571

  
572
def create_public_ipv4_port(user_id, network=None, address=None,
573
                            category="user"):
574
    """Create a port in a public IPv4 network.
575

  
576
    Create a port in a public IPv4 network (that may also have an IPv6
577
    subnet). If the category is 'user' or 'default' this will try to use
578
    one of the users floating IPs. If the category is 'admin' will
579
    create a port to the public network (without floating IPs or quotas).
580

  
581
    """
582
    if category in ["user", "default"]:
583
        if address is None:
584
            ipaddress = ips.get_free_floating_ip(user_id, network)
585
        else:
586
            ipaddress = util.get_floating_ip_by_address(user_id, address,
587
                                                        for_update=True)
588
    elif category == "admin":
589
        if network is None:
590
            ipaddress = ips.allocate_public_ip(user_id)
591
        else:
592
            ipaddress = ips.allocate_ip(network, user_id)
593
    else:
594
        raise ValueError("Unknown category: %s" % category)
595
    if network is None:
596
        network = ipaddress.network
597
    return _create_port(user_id, network, use_ipaddress=ipaddress)
598

  
599

  
600
def create_public_ipv6_port(user_id, category=None):
601
    """Create a port in a public IPv6 only network."""
602
    networks = Network.objects.filter(public=True, deleted=False,
603
                                      drained=False, subnets__ipversion=6)\
604
                              .exclude(subnets__ipversion=4)
605
    if networks:
606
        return _create_port(user_id, networks[0])
607
    else:
608
        msg = "No available IPv6 only network!"
609
        log.error(msg)
610
        raise faults.Conflict(msg)
611

  
612

  
613
def create_ports_for_request(user_id, networks):
614
    """Create the server ports requested by the user.
615

  
616
    Create the ports for the new servers as requested in the 'networks'
617
    attribute. The networks attribute contains either a list of network IDs
618
    ('uuid') or a list of ports IDs ('port'). In case of network IDs, the user
619
    can also specify an IPv4 address ('fixed_ip'). In order to connect to a
620
    public network, the 'fixed_ip' attribute must contain the IPv4 address of a
621
    floating IP. If the network is public but the 'fixed_ip' attribute is not
622
    specified, the system will automatically reserve one of the users floating
623
    IPs.
624

  
625
    """
626
    return [_port_for_request(user_id, network) for network in networks]
627

  
628

  
629
def _port_for_request(user_id, network_dict):
630
    port_id = network_dict.get("port")
631
    network_id = network_dict.get("uuid")
632
    if port_id is not None:
633
        return util.get_port(port_id, user_id, for_update=True)
634
    elif network_id is not None:
635
        address = network_dict.get("fixed_ip")
636
        network = util.get_network(network_id, user_id, non_deleted=True)
637
        if network.public:
638
            if network.subnet4 is not None:
639
                if not "fixed_ip" in network_dict:
640
                    return create_public_ipv4_port(user_id, network)
641
                elif address is None:
642
                    msg = "Cannot connect to public network"
643
                    raise faults.BadRequest(msg % network.id)
644
                else:
645
                    return create_public_ipv4_port(user_id, network, address)
646
            else:
647
                raise faults.Forbidden("Cannot connect to IPv6 only public"
648
                                       " network %" % network.id)
649
        else:
650
            return _create_port(user_id, network, address=address)
651
    else:
652
        raise faults.BadRequest("Network 'uuid' or 'port' attribute"
653
                                " is required.")
b/snf-cyclades-app/synnefo/logic/tests/servers.py
60 60
        self.assertRaises(faults.ServiceUnavailable, servers.create, **kwargs)
61 61
        self.assertEqual(models.VirtualMachine.objects.count(), 0)
62 62

  
63
        subnet = mfactory.IPv4SubnetFactory(network__public=True)
64
        bn = mfactory.BackendNetworkFactory(network=subnet.network)
65
        backend = bn.backend
63
        mfactory.IPv4SubnetFactory(network__public=True)
64
        mfactory.IPv6SubnetFactory(network__public=True)
65
        mfactory.BackendFactory()
66 66

  
67 67
        # error in nics
68 68
        req = deepcopy(kwargs)
......
77 77
        vm = models.VirtualMachine.objects.get()
78 78
        self.assertFalse(vm.deleted)
79 79
        self.assertEqual(vm.operstate, "ERROR")
80
        self.assertEqual(len(vm.nics.all()), 1)
80
        self.assertEqual(len(vm.nics.all()), 2)
81 81
        for nic in vm.nics.all():
82 82
            self.assertEqual(nic.state, "ERROR")
83 83

  
84
        # success with no nics
85
        mrapi().CreateInstance.side_effect = None
86
        mrapi().CreateInstance.return_value = 42
87
        with override_settings(settings,
88
                               DEFAULT_INSTANCE_NETWORKS=[]):
89
            with mocked_quotaholder():
90
                vm = servers.create(**kwargs)
91
        vm = models.VirtualMachine.objects.get(id=vm.id)
92
        self.assertEqual(vm.nics.count(), 0)
93
        self.assertEqual(vm.backendjobid, 42)
94
        self.assertEqual(vm.task_job_id, 42)
95
        self.assertEqual(vm.task, "BUILD")
96

  
97
        # test connect in IPv6 only network
98
        subnet = mfactory.IPv6SubnetFactory(network__public=True)
99
        net = subnet.network
100
        mfactory.BackendNetworkFactory(network=net, backend=backend,
101
                                       operstate="ACTIVE")
102
        with override_settings(settings,
103
                               DEFAULT_INSTANCE_NETWORKS=[str(net.id)]):
104
            with mocked_quotaholder():
105
                vm = servers.create(**kwargs)
106
        nics = vm.nics.all()
107
        self.assertEqual(len(nics), 1)
108
        self.assertFalse(nics[0].ips.filter(subnet__ipversion=4).exists())
109
        args, kwargs = mrapi().CreateInstance.call_args
110
        ganeti_nic = kwargs["nics"][0]
111
        self.assertEqual(ganeti_nic["ip"], None)
112
        self.assertEqual(ganeti_nic["network"], net.backend_id)
113

  
114 84

  
115 85
@patch("synnefo.logic.rapi_pool.GanetiRapiClient")
116 86
class ServerTest(TransactionTestCase):

Also available in: Unified diff