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