This repository has been archived by the owner on Apr 27, 2022. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 54
/
Copy pathmain.py
2506 lines (2094 loc) · 101 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""Unit tests for hil.api
Some general notes:
Probably most of our technical debt is in this module; about 75% of it
was written in one day by one person, way back when the HIL codebase
was somewhat embarassing, we basically didn't have a test suite, and
we decided we needed one in a hurry. The biggest problem is that it's
a bit difficult to tell what properties a test is verifying, since
there's lots of setup code mixed in with the actual check. Unfortunately,
the setup code isn't really regular enough to factor out into one fixture.
A few efforts have been made to make reading the tests a bit easier to
navigate:
* In some places, you'll see a statement making some api call commented
out. This is a hint that this test is similar to another one near by,
which has that statement un-commented.
* Some lines have a comment "# changed" after them. This indicates that
nearby tests have a similar line, but something has been changed from
those lines (perhaps a single argument has a different value for
example).
* When two tests are very similar in what they test, the docstrings say
things like "same as above, but with foo instead of bar." Make sure when
modifying this file you don't change things such that those references are
no longer correct.
General advice on working with this module:
* Leave the code in better condition than you found it.
* Be wary of comments that make broad statements about what operations are
supported, particulary if that test doesn't actually verify that property;
sometimes the way HIL works changes, and we have historically not been
very good at updating every comment in here to match.
* Accordingly, avoid writing comments that state things about HIL that the
test doesn't verify; this file is huge and you're never going to remember
to update those statements if the changes don't cause failures.
* With new tests, cleanly separate setup code from the actual tests. This
might take the form of a single setUp() method on a class that holds
similar tests, or the use of a fixture. Just make sure it improves
readability and maintainability.
* make sure it is easy to see what a new test is trying to verify.
"""
from hil import model, deferred, errors, config, api
from hil.test_common import config_testsuite, config_merge, fresh_database, \
fail_on_log_warnings, additional_db, with_request_context, \
network_create_simple, server_init, uuid_pattern, spoof_enable_obm
from hil.network_allocator import get_network_allocator
from hil.auth import get_auth_backend
import pytest
import unittest
import json
import uuid
from schema import SchemaError
MOCK_SWITCH_TYPE = 'http://schema.massopencloud.org/haas/v0/switches/mock'
OBM_TYPE_MOCK = 'http://schema.massopencloud.org/haas/v0/obm/mock'
OBM_TYPE_IPMI = 'http://schema.massopencloud.org/haas/v0/obm/ipmi'
PORTS = ['gi1/0/1', 'gi1/0/2', 'gi1/0/3', 'gi1/0/4', 'gi1/0/5']
@pytest.fixture
def configure():
"""Configure HIL"""
config_testsuite()
config_merge({
'auth': {
'require_authentication': 'True',
},
'extensions': {
'hil.ext.auth.null': None,
'hil.ext.auth.mock': '',
'hil.ext.switches.mock': '',
'hil.ext.network_allocators.null': None,
'hil.ext.network_allocators.vlan_pool': '',
},
'hil.ext.network_allocators.vlan_pool': {
'vlans': '40-80',
},
})
config.load_extensions()
fresh_database = pytest.fixture(fresh_database)
additional_database = pytest.fixture(additional_db)
fail_on_log_warnings = pytest.fixture(fail_on_log_warnings)
server_init = pytest.fixture(server_init)
with_request_context = pytest.yield_fixture(with_request_context)
@pytest.fixture
def set_admin_auth():
"""Set admin auth for all calls"""
get_auth_backend().set_admin(True)
@pytest.fixture
def switchinit():
"""Create a switch with one port"""
api.switch_register('sw0',
type=MOCK_SWITCH_TYPE,
username="switch_user",
password="switch_pass",
hostname="switchname")
api.switch_register_port('sw0', PORTS[2])
def new_node(name):
"""Create a mock node named ``name``"""
api.node_register(
node=name,
obmd={
'uri': 'http://obmd.example.com/nodes/' + name,
'admin_token': 'secret',
},
)
default_fixtures = ['fail_on_log_warnings',
'configure',
'fresh_database',
'server_init',
'with_request_context',
'set_admin_auth']
pytestmark = pytest.mark.usefixtures(*default_fixtures)
class TestProjectCreateDelete:
"""Tests for the hil.api.project_{create,delete} functions."""
pytestmark = pytest.mark.usefixtures(*(default_fixtures +
['additional_database']))
def test_project_create_success(self):
"""(successful) call to project_create"""
api.project_create('anvil-nextgen')
api.get_or_404(model.Project, 'anvil-nextgen')
def test_project_create_duplicate(self):
"""Call to project_create should fail if the project already exists."""
with pytest.raises(errors.DuplicateError):
api.project_create('manhattan')
def test_project_delete(self):
"""project_delete should successfully delete a project."""
api.project_delete('empty-project')
with pytest.raises(errors.NotFoundError):
api.get_or_404(model.Project, 'empty-project')
def test_project_delete_nexist(self):
"""Deleting a project which doesn't exist should raise not found."""
with pytest.raises(errors.NotFoundError):
api.project_delete('anvil-nextgen')
def test_project_delete_hasnode(self):
"""Deleting a project which has nodes should fail."""
with pytest.raises(errors.BlockedError):
api.project_delete('manhattan')
def test_project_delete_success_nodesdeleted(self):
"""...but after deleting the nodes, should succeed."""
new_node('node-99')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
api.project_detach_node('anvil-nextgen', 'node-99')
api.project_delete('anvil-nextgen')
def test_project_delete_hasnetwork(self):
"""Deleting a project that has networks should fail."""
api.project_create('anvil-nextgen')
network_create_simple('hammernet', 'anvil-nextgen')
with pytest.raises(errors.BlockedError):
api.project_delete('anvil-nextgen')
def test_project_delete_success_networksdeleted(self):
"""...but after deleting the networks, should succeed."""
api.project_create('anvil-nextgen')
network_create_simple('hammernet', 'anvil-nextgen')
api.network_delete('hammernet')
api.project_delete('anvil-nextgen')
def test_project_delete_hasheadnode(self):
"""Deleting a project that has headnodes should fail."""
api.project_create('anvil-nextgen')
api.headnode_create('hn-01', 'anvil-nextgen', 'base-headnode')
with pytest.raises(errors.BlockedError):
api.project_delete('anvil-nextgen')
def test_duplicate_project_create(self):
"""Creating a project that already exists should fail."""
api.project_create('acme-corp')
with pytest.raises(errors.DuplicateError):
api.project_create('acme-corp')
class TestProjectAddDeleteNetwork:
"""Tests for adding and deleting a network from a project"""
pytestmark = pytest.mark.usefixtures(*(default_fixtures +
['additional_database']))
def test_network_grant_project_access(self):
"""network_grant_project_access should actually grant access."""
api.network_grant_project_access('manhattan', 'runway_pxe')
network = api.get_or_404(model.Network, 'runway_pxe')
project = api.get_or_404(model.Project, 'manhattan')
assert project in network.access
assert network in project.networks_access
def test_network_revoke_project_access(self):
"""network_revoke_project_access should actually revoke access."""
api.network_revoke_project_access('runway', 'runway_provider')
network = api.get_or_404(model.Network, 'runway_provider')
project = api.get_or_404(model.Project, 'runway')
assert project not in network.access
assert network not in project.networks_access
def test_network_revoke_project_access_connected_node(self):
"""Test reovking access to a project that has nodes on the network.
This should fail.
"""
api.node_connect_network(
'runway_node_0',
'boot-nic',
'runway_provider')
deferred.apply_networking()
with pytest.raises(errors.BlockedError):
api.network_revoke_project_access('runway', 'runway_provider')
def test_project_remove_network_owner(self):
"""Revoking access to a network's owner should fail."""
with pytest.raises(errors.BlockedError):
api.network_revoke_project_access('runway', 'runway_pxe')
class TestNetworking:
"""Misc. Networking related tests."""
def test_networking_involved(self):
"""Do a bunch of network related operations."""
api.switch_register('sw0',
type=MOCK_SWITCH_TYPE,
username="switch_user",
password="switch_pass",
hostname="switchname")
for port in PORTS[0], PORTS[1], PORTS[2]:
api.switch_register_port('sw0', port)
new_node('node-99')
new_node('node-98')
new_node('node-97')
api.node_register_nic('node-99', 'eth0', 'DE:AD:BE:EF:20:14')
api.node_register_nic('node-98', 'eth0', 'DE:AD:BE:EF:20:15')
api.node_register_nic('node-97', 'eth0', 'DE:AD:BE:EF:20:16')
for port, node in (PORTS[0], 'node-99'), (PORTS[1], 'node-98'), \
(PORTS[2], 'node-97'):
api.port_connect_nic('sw0', port, node, 'eth0')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
api.project_connect_node('anvil-nextgen', 'node-98')
network_create_simple('hammernet', 'anvil-nextgen')
network_create_simple('spiderwebs', 'anvil-nextgen')
api.node_connect_network('node-98', 'eth0', 'hammernet')
def test_networking_nic_no_port(self):
"""Connecting a nic with no port to a network should fail."""
new_node('node-99')
api.node_register_nic('node-99', 'eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
with pytest.raises(errors.NotFoundError):
api.node_connect_network('node-99', 'eth0', 'hammernet')
class TestProjectConnectDetachNode:
"""Test project_{connect,detach}_node."""
def test_project_connect_node(self):
"""Check that project_connect_node adds the node to the project."""
api.project_create('anvil-nextgen')
new_node('node-99')
api.project_connect_node('anvil-nextgen', 'node-99')
project = api.get_or_404(model.Project, 'anvil-nextgen')
node = api.get_or_404(model.Node, 'node-99')
assert node in project.nodes
assert node.project is project
def test_project_connect_node_project_nexist(self):
"""Tests that connecting a node to a nonexistent project fails"""
new_node('node-99')
with pytest.raises(errors.NotFoundError):
api.project_connect_node('anvil-nextgen', 'node-99')
def test_project_connect_node_node_nexist(self):
"""Tests that connecting a nonexistent node to a projcet fails"""
api.project_create('anvil-nextgen')
with pytest.raises(errors.NotFoundError):
api.project_connect_node('anvil-nextgen', 'node-99')
def test_project_connect_node_node_busy(self):
"""Connecting a node which is not free to a project should fail."""
new_node('node-99')
api.project_create('anvil-oldtimer')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-oldtimer', 'node-99')
with pytest.raises(errors.BlockedError):
api.project_connect_node('anvil-nextgen', 'node-99')
def test_project_detach_node(self):
"""Test that project_detach_node removes the node from the project."""
api.project_create('anvil-nextgen')
new_node('node-99')
api.project_connect_node('anvil-nextgen', 'node-99')
api.project_detach_node('anvil-nextgen', 'node-99')
project = api.get_or_404(model.Project, 'anvil-nextgen')
node = api.get_or_404(model.Node, 'node-99')
assert node not in project.nodes
assert node.project is not project
def test_project_detach_node_notattached(self):
"""Tests that removing a node from a project it's not in fails."""
api.project_create('anvil-nextgen')
new_node('node-99')
with pytest.raises(errors.NotFoundError):
api.project_detach_node('anvil-nextgen', 'node-99')
def test_project_detach_node_project_nexist(self):
"""Tests that removing a node from a nonexistent project fails."""
new_node('node-99')
with pytest.raises(errors.NotFoundError):
api.project_detach_node('anvil-nextgen', 'node-99')
def test_project_detach_node_node_nexist(self):
"""Tests that removing a nonexistent node from a project fails."""
api.project_create('anvil-nextgen')
with pytest.raises(errors.NotFoundError):
api.project_detach_node('anvil-nextgen', 'node-99')
def test_project_detach_node_on_network(self, switchinit):
"""Tests that project_detach_node fails if the node is on a network."""
api.project_create('anvil-nextgen')
new_node('node-99')
api.node_register_nic('node-99', 'eth0', 'DE:AD:BE:EF:20:13')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', 'eth0')
api.node_connect_network('node-99', 'eth0', 'hammernet')
with pytest.raises(errors.BlockedError):
api.project_detach_node('anvil-nextgen', 'node-99')
def test_project_detach_node_success_nic_not_on_network(self):
"""...but succeeds if not, all else being equal."""
api.project_create('anvil-nextgen')
new_node('node-99')
api.node_register_nic('node-99', 'eth0', 'DE:AD:BE:EF:20:13')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.project_detach_node('anvil-nextgen', 'node-99')
def test_project_detach_node_removed_from_network(self, switchinit):
"""Same as above, but we connect/disconnect from the network.
...rather than just having the node disconnected to begin with.
"""
api.project_create('anvil-nextgen')
new_node('node-99')
api.node_register_nic('node-99', 'eth0', 'DE:AD:BE:EF:20:13')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', 'eth0')
api.node_connect_network('node-99', 'eth0', 'hammernet')
deferred.apply_networking()
api.node_detach_network('node-99', 'eth0', 'hammernet')
deferred.apply_networking()
api.project_detach_node('anvil-nextgen', 'node-99')
class TestRegisterCorrectObm:
"""Tests that node_register stores obm information into the node table."""
def test_obmd_info(self):
"""Check for the label and obmd parameters."""
api.node_register(
node='compute-01',
obmd={
'uri': 'http://obmd.example.com/nodes/compute-01',
'admin_token': 'secret',
},
)
node_obj = model.Node.query.filter_by(label="compute-01").one()
assert str(node_obj.label) == 'compute-01'
assert str(node_obj.obmd_uri) == \
'http://obmd.example.com/nodes/compute-01'
assert str(node_obj.obmd_admin_token) == 'secret'
class TestNodeRegisterDelete:
"""Tests for the hil.api.node_{register,delete} functions."""
def test_node_register(self):
"""node_register should add the node to the db."""
api.node_register(
node='node-99',
obmd={
'uri': 'http://obmd.example.com/nodes/node-99',
'admin_token': 'secret',
},
)
api.get_or_404(model.Node, 'node-99')
def test_node_register_with_metadata(self):
"""Same thing, but try it with metadata."""
api.node_register(
node='node-99',
obmd={
'uri': 'http://obmd.example.com/nodes/node-99',
'admin_token': 'secret',
},
metadata={
"EK": "pk"
},
)
api.get_or_404(model.Node, 'node-99')
def test_node_register_with_bad_metadata(self):
"""Test attempting to register a node with a non-dictonary metadata."""
with pytest.raises(SchemaError):
api.node_register.api_schema.validate({
'node': 'node-99',
'obmd': {
'uri': 'http://obmd.example.com/nodes/node-99',
'admin_token': 'secret',
},
'metadata': 42,
})
def test_node_register_JSON_metadata(self):
"""...and with the metadata being something other than a string."""
api.node_register(
node='node-99',
obmd={
'uri': 'http://obmd.example.com/nodes/node-99',
'admin_token': 'secret',
},
metadata={
"EK": {"val1": 1, "val2": 2}
},
)
api.get_or_404(model.Node, 'node-99')
def test_node_register_with_multiple_metadata(self):
"""...and with multiple metadata keys."""
api.node_register(
node='node-99',
obmd={
'uri': 'http://obmd.example.com/nodes/node-99',
'admin_token': 'secret',
},
metadata={
"EK": "pk",
"SHA256": "b5962d8173c14e60259211bcf25d1263c36e0"
"ad7da32ba9d07b224eac1834813"
},
)
api.get_or_404(model.Node, 'node-99')
def test_duplicate_node_register(self):
"""Duplicate calls to node_register should fail."""
new_node('node-99')
with pytest.raises(errors.DuplicateError):
new_node('node-99')
def test_node_delete(self):
"""node_delete should remove the node from the db."""
new_node('node-99')
api.node_delete('node-99')
with pytest.raises(errors.NotFoundError):
api.get_or_404(model.Node, 'node-99')
def test_node_delete_nexist(self):
"""node_delete should fail if the node does not exist."""
with pytest.raises(errors.NotFoundError):
api.node_delete('node-99')
def test_node_delete_nic_exist(self):
"""node_delete should respond with an error if the node has nics."""
new_node('node-99')
api.node_register_nic('node-99', 'eth0', 'DE:AD:BE:EF:20:14')
with pytest.raises(errors.BlockedError):
api.node_delete('node-99')
def test_node_delete_in_project(self):
"""node_delete should respond with an error if node is in project"""
new_node('node-99')
api.project_create('skeleton')
api.project_connect_node('skeleton', 'node-99')
with pytest.raises(errors.BlockedError):
api.node_delete('node-99')
class TestNodeRegisterDeleteNic:
"""Test node_{register,delete}_nic."""
def test_node_register_nic(self):
"""node_register_nic should add the nic to the db."""
new_node('compute-01')
api.node_register_nic('compute-01', '01-eth0', 'DE:AD:BE:EF:20:14')
nic = api.get_or_404(model.Nic, '01-eth0')
assert nic.owner.label == 'compute-01'
def test_node_register_nic_no_node(self):
"""node_register_nic should fail if the node does not exist."""
with pytest.raises(errors.NotFoundError):
api.node_register_nic('compute-01', '01-eth0', 'DE:AD:BE:EF:20:14')
def test_node_register_nic_duplicate_nic(self):
"""node_register_nic should fail if the nic already exists."""
new_node('compute-01')
api.node_register_nic('compute-01', '01-eth0', 'DE:AD:BE:EF:20:14')
api.get_or_404(model.Nic, '01-eth0')
with pytest.raises(errors.DuplicateError):
api.node_register_nic('compute-01', '01-eth0', 'DE:AD:BE:EF:20:15')
def test_node_delete_nic_success(self):
"""node_delete_nic should remove the nic from the db.
However, it should *not* remove the node.
"""
new_node('compute-01')
api.node_register_nic('compute-01', '01-eth0', 'DE:AD:BE:EF:20:14')
api.node_delete_nic('compute-01', '01-eth0')
api.absent_or_conflict(model.Nic, '01-eth0')
api.get_or_404(model.Node, 'compute-01')
def test_node_delete_nic_nic_nexist(self):
"""node_delete_nic should fail if the nic does not exist."""
new_node('compute-01')
with pytest.raises(errors.NotFoundError):
api.node_delete_nic('compute-01', '01-eth0')
def test_node_delete_nic_node_nexist(self):
"""node_delete_nic should fail if the node does not exist."""
with pytest.raises(errors.NotFoundError):
api.node_delete_nic('compute-01', '01-eth0')
def test_node_delete_nic_wrong_node(self):
"""node_delete_nic should fail if the nic does not match the node.
...even if there is another node that has a nic by that name.
"""
new_node('compute-01')
new_node('compute-02')
api.node_register_nic('compute-01', '01-eth0', 'DE:AD:BE:EF:20:14')
with pytest.raises(errors.NotFoundError):
api.node_delete_nic('compute-02', '01-eth0')
def test_node_delete_nic_wrong_nexist_node(self):
"""Same thing, but with a node that does not exist."""
new_node('compute-01')
api.node_register_nic('compute-01', '01-eth0', 'DE:AD:BE:EF:20:14')
with pytest.raises(errors.NotFoundError):
api.node_delete_nic('compute-02', '01-eth0')
def test_node_delete_nic_after_networking_action(self, switchinit):
"""
Test node_delete_nic during various stages of a networking_action
1. Deleting a nic after the networking operation is done should work.
"""
# 1. Create a node with a nic. Connect that nic to a switchport, and
# add it to a project. Create a network and connect node's nic to
# that network.
new_node('node-99')
api.node_register_nic('node-99', 'nic1', 'DE:AD:BE:EF:20:18')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', 'nic1')
api.node_connect_network('node-99', 'nic1', 'hammernet')
deferred.apply_networking()
# 2. Remove node's nic from the network.
api.node_detach_network('node-99', 'nic1', 'hammernet')
deferred.apply_networking()
# 3. Deleting the nic should work now.
api.project_detach_node('anvil-nextgen', 'node-99')
api.node_delete_nic('node-99', 'nic1')
def test_node_delete_nic_in_project(self):
"""Deleting a nic on a node that belongs to a project should fail.
This also ensures that there are no pending actions or networks
connected to that nic."""
new_node('node-99')
api.node_register_nic('node-99', 'nic1', 'DE:AD:BE:EF:20:18')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
with pytest.raises(errors.BlockedError):
api.node_delete_nic('node-99', 'nic1')
api.project_detach_node('anvil-nextgen', 'node-99')
api.node_delete_nic('node-99', 'nic1')
def test_node_register_nic_diff_nodes(self):
"""Registering two nics with the same name on diff. nodes is ok."""
new_node('compute-01')
new_node('compute-02')
api.node_register_nic('compute-01', 'ipmi', 'DE:AD:BE:EF:20:14')
api.node_register_nic('compute-02', 'ipmi', 'DE:AD:BE:EF:20:14')
class TestNodeRegisterDeleteMetadata:
"""Test node_{set,delete}_metadata."""
pytestmark = pytest.mark.usefixtures(*(default_fixtures +
['additional_database']))
def test_node_set_metadata(self):
"""Setting new metadata on a node adds the metadata."""
api.node_set_metadata('free_node_0', 'EK', 'pk')
metadata = api.get_child_or_404(api.get_or_404(model.Node,
'free_node_0'),
model.Metadata, 'EK')
assert metadata.owner.label == 'free_node_0'
def test_node_update_metadata(self):
"""Updating existing metadata on a node works."""
api.node_set_metadata('runway_node_0', 'EK', 'new_pk')
metadata = api.get_child_or_404(api.get_or_404(model.Node,
'runway_node_0'),
model.Metadata, 'EK')
assert json.loads(metadata.value) == 'new_pk'
def test_node_set_metadata_no_node(self):
"""Setting metadata on a node that does not exist should fail."""
with pytest.raises(errors.NotFoundError):
api.node_set_metadata('compute-01', 'EK', 'pk')
def test_node_delete_metadata_success(self):
"""Deleting metadata from a node removes that key."""
api.node_set_metadata('free_node_0', 'EK', 'pk')
api.node_delete_metadata('free_node_0', 'EK')
api.absent_child_or_conflict(api.get_or_404(model.Node,
'free_node_0'),
model.Metadata, 'EK')
def test_node_delete_metadata_metadata_nexist(self):
"""Deleting a metadata key that does not exist fails."""
with pytest.raises(errors.NotFoundError):
api.node_delete_metadata('free_node_0', 'EK')
def test_node_delete_metadata_node_nexist(self):
"""Deleting a metadata key on a node that does not exist fails."""
with pytest.raises(errors.NotFoundError):
api.node_delete_metadata('compute-01', 'EK')
def test_node_delete_metadata_wrong_node(self):
"""Deleting metadata on the wrong node fails."""
api.node_set_metadata('free_node_0', 'EK', 'pk')
with pytest.raises(errors.NotFoundError):
api.node_delete_metadata('free_node_1', 'EK')
def test_node_delete_metadata_wrong_nexist_node(self):
"""...same thing, but with a node that doesn't exist."""
api.node_set_metadata('free_node_0', 'EK', 'pk')
with pytest.raises(errors.NotFoundError):
api.node_delete_metadata('compute-02', 'EK')
def test_node_set_metadata_diff_nodes(self):
"""Setting the same metadata key on two different nodes succeeds."""
api.node_set_metadata('free_node_0', 'EK', 'pk')
api.node_set_metadata('free_node_1', 'EK', 'pk')
def test_node_set_metadata_non_string(self):
"""Setting metadata whose value is not just a string works."""
api.node_set_metadata('free_node_0', 'JSON',
{"val1": 1, "val2": 2})
class TestNodeConnectDetachNetwork:
"""Test node_{connect,detach}_network."""
def test_node_connect_network_success(self, switchinit):
"""Call to node_connect_network adds a NetworkAttachment."""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
# Check the actual HTTP response and status, not just the success;
# we should do this at least once in the test suite, since this call
# returns 202 instead of 200 like most things.
response = api.node_connect_network('node-99', '99-eth0', 'hammernet')
assert response[1] == 202
response = json.loads(response[0])
assert uuid_pattern.match(response['status_id'])
deferred.apply_networking()
network = api.get_or_404(model.Network, 'hammernet')
nic = api.get_or_404(model.Nic, '99-eth0')
model.NetworkAttachment.query.filter_by(network=network,
nic=nic).one()
def test_node_connect_network_wrong_node_in_project(self, switchinit):
"""Connecting a nic that does not exist to a network fails
...even if the project has another node with a nic by that name.
"""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
api.node_connect_network('node-99', '99-eth0', 'hammernet')
new_node('node-98')
api.project_connect_node('anvil-nextgen', 'node-98') # added
with pytest.raises(errors.NotFoundError):
api.node_connect_network('node-98', '99-eth0', 'hammernet')
def test_node_connect_network_wrong_node_not_in_project(self):
"""...same thing, but with a node that is *not* part that project."""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
new_node('node-98')
with pytest.raises(errors.NotFoundError):
api.node_connect_network('node-98', '99-eth0', 'hammernet')
def test_node_connect_network_no_such_node(self):
"""Connecting a non-existent nic to a network fails.
...even if there is another node with a nic by that name.
"""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
with pytest.raises(errors.NotFoundError):
api.node_connect_network('node-98', '99-eth0', 'hammernet') # changed # noqa
def test_node_connect_network_no_such_nic(self):
"""Connecting a node to a network via a nic it doesn't have fails."""
new_node('node-99')
# api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
with pytest.raises(errors.NotFoundError):
api.node_connect_network('node-99', '99-eth0', 'hammernet')
def test_node_connect_network_no_such_network(self):
"""Connecting a node to a non-existent network fails."""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
# network_create_simple('hammernet', 'anvil-nextgen')
with pytest.raises(errors.NotFoundError):
api.node_connect_network('node-99', '99-eth0', 'hammernet')
def test_node_connect_network_node_not_in_project(self):
"""Connecting a node not in a project to a network fails."""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
# api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
with pytest.raises(errors.ProjectMismatchError):
api.node_connect_network('node-99', '99-eth0', 'hammernet')
def test_node_connect_network_different_projects(self, switchinit):
"""Connecting a node to a network owned by a different project fails.
(without a specific call to grant access).
"""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_create('anvil-oldtimer') # added
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-oldtimer') # changed
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
with pytest.raises(errors.ProjectMismatchError):
api.node_connect_network('node-99', '99-eth0', 'hammernet')
def test_node_connect_network_already_attached_to_same(self, switchinit):
"""Connecting a nic to a network twice should fail."""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
api.node_connect_network('node-99', '99-eth0', 'hammernet') # added
deferred.apply_networking() # added
with pytest.raises(errors.BlockedError):
api.node_connect_network('node-99', '99-eth0', 'hammernet')
def test_node_connect_network_already_attached_differently(self,
switchinit):
"""Test connecting a nic that is busy to another network.
i.e., If the nic is already connected to a different network (on
the same channel), trying to connect it should fail.
"""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
network_create_simple('hammernet2', 'anvil-nextgen') # added
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
api.node_connect_network('node-99', '99-eth0', 'hammernet') # added
deferred.apply_networking() # added
with pytest.raises(errors.BlockedError):
api.node_connect_network('node-99', '99-eth0', 'hammernet2')
def test_node_detach_network_success(self, switchinit):
"""Detaching a node from a network removes the NetworkAttachment."""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
api.node_connect_network('node-99', '99-eth0', 'hammernet')
deferred.apply_networking() # added
# Verify that the status is right, not just that it "succeeds."
response = api.node_detach_network('node-99', '99-eth0', 'hammernet')
assert response[1] == 202
response = json.loads(response[0])
assert uuid_pattern.match(response['status_id'])
deferred.apply_networking()
network = api.get_or_404(model.Network, 'hammernet')
nic = api.get_or_404(model.Nic, '99-eth0')
assert model.NetworkAttachment.query \
.filter_by(network=network, nic=nic).count() == 0
def test_node_detach_network_not_attached(self):
"""
Detaching a node from a network fails, if it isn't attached already.
"""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
# api.node_connect_network('node-99', '99-eth0', 'hammernet')
with pytest.raises(errors.BadArgumentError):
api.node_detach_network('node-99', '99-eth0', 'hammernet')
def test_node_detach_network_wrong_node_in_project(self, switchinit):
"""Detaching the "wrong" node from a network fails.
In particular, if we have two nodes in a project with nics by the
same name, with one connected to a network, specifying the wrong
node name (but right nic and network) will fail.
"""
new_node('node-99')
new_node('node-98')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
api.project_connect_node('anvil-nextgen', 'node-98') # added
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
api.node_connect_network('node-99', '99-eth0', 'hammernet')
with pytest.raises(errors.NotFoundError):
api.node_detach_network('node-98', '99-eth0', 'hammernet') # changed # noqa
def test_node_detach_network_wrong_node_not_in_project(self, switchinit):
"""Same as above, but the "wrong" node is not part of the project."""
new_node('node-99')
new_node('node-98')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
api.node_connect_network('node-99', '99-eth0', 'hammernet')
with pytest.raises(errors.NotFoundError):
api.node_detach_network('node-98', '99-eth0', 'hammernet') # changed # noqa
def test_node_detach_network_no_such_node(self, switchinit):
"""Same as above, but the "wrong" node doesn't exist."""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
api.node_connect_network('node-99', '99-eth0', 'hammernet')
with pytest.raises(errors.NotFoundError):
api.node_detach_network('node-98', '99-eth0', 'hammernet') # changed # noqa
def test_node_detach_network_no_such_nic(self, switchinit):
"""Detaching a nic that doesn't exist raises not found."""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
api.node_connect_network('node-99', '99-eth0', 'hammernet')
with pytest.raises(errors.NotFoundError):
api.node_detach_network('node-99', '99-eth1', 'hammernet') # changed # noqa
def test_node_detach_network_node_not_in_project(self, switchinit):
"""Detaching a node that is not in a network fails.
In particular, this should raise ProjectMismatchError.
Note that if this is the case, the node should never actually be
connected to a network; this is mostly checking the ordering of
the errors.
"""
new_node('node-99')
api.node_register_nic('node-99', '99-eth0', 'DE:AD:BE:EF:20:14')
api.project_create('anvil-nextgen')
# api.project_connect_node('anvil-nextgen', 'node-99')
network_create_simple('hammernet', 'anvil-nextgen')
api.port_connect_nic('sw0', PORTS[2], 'node-99', '99-eth0')
# api.node_connect_network('node-99', '99-eth0', 'hammernet')
with pytest.raises(errors.ProjectMismatchError):
api.node_detach_network('node-99', '99-eth0', 'hammernet')
class TestHeadnodeCreateDelete:
"""Test headnode_{create,delete}"""
def test_headnode_create_success(self):
"""(successful) call to headnode_create creates the headnode.
(in the database; this doesn't verify that a VM is actually created).
"""
api.project_create('anvil-nextgen')
api.headnode_create('hn-0', 'anvil-nextgen', 'base-headnode')
hn = api.get_or_404(model.Headnode, 'hn-0')
assert hn.project.label == 'anvil-nextgen'
def test_headnode_create_badproject(self):
"""Tests that creating a headnode with a nonexistent project fails"""
with pytest.raises(errors.NotFoundError):
api.headnode_create('hn-0', 'anvil-nextgen', 'base-headnode')
def test_headnode_create_duplicate(self):
"""Tests that creating a headnode with a duplicate name fails"""
api.project_create('anvil-nextgen')
api.project_create('anvil-oldtimer')
api.headnode_create('hn-0', 'anvil-nextgen', 'base-headnode')
with pytest.raises(errors.DuplicateError):
api.headnode_create('hn-0', 'anvil-oldtimer', 'base-headnode')
def test_headnode_create_second(self):
"""Tests that creating a second headnode one one project fails"""
api.project_create('anvil-nextgen')
api.headnode_create('hn-0', 'anvil-nextgen', 'base-headnode')
api.headnode_create('hn-1', 'anvil-nextgen', 'base-headnode')