@@ -551,3 +551,123 @@ def spy_get_filtered_hosts(*args, **kwargs):
551
551
server = user_api .post_server (server_req )
552
552
self ._wait_for_state_change (user_api , server , 'ACTIVE' )
553
553
self .assertEqual (2 , len (self .filtered_hosts ))
554
+
555
+
556
+ class AggregateMultiTenancyIsolationColdMigrateTest (
557
+ test .TestCase , integrated_helpers .InstanceHelperMixin ):
558
+
559
+ @staticmethod
560
+ def _create_aggregate (admin_api , name ):
561
+ return admin_api .api_post (
562
+ '/os-aggregates' , {'aggregate' : {'name' : name }}).body ['aggregate' ]
563
+
564
+ @staticmethod
565
+ def _add_host_to_aggregate (admin_api , aggregate , host ):
566
+ add_host_req_body = {
567
+ "add_host" : {
568
+ "host" : host
569
+ }
570
+ }
571
+ admin_api .api_post (
572
+ '/os-aggregates/%s/action' % aggregate ['id' ], add_host_req_body )
573
+
574
+ @staticmethod
575
+ def _isolate_aggregate (admin_api , aggregate , tenant_id ):
576
+ set_meta_req_body = {
577
+ "set_metadata" : {
578
+ "metadata" : {
579
+ "filter_tenant_id" : tenant_id
580
+ }
581
+ }
582
+ }
583
+ admin_api .api_post (
584
+ '/os-aggregates/%s/action' % aggregate ['id' ], set_meta_req_body )
585
+
586
+ def setUp (self ):
587
+ super (AggregateMultiTenancyIsolationColdMigrateTest , self ).setUp ()
588
+ self .useFixture (policy_fixture .RealPolicyFixture ())
589
+ self .useFixture (nova_fixtures .NeutronFixture (self ))
590
+ self .useFixture (func_fixtures .PlacementFixture ())
591
+ # Intentionally keep these separate since we want to create the
592
+ # server with the non-admin user in a different project.
593
+ admin_api_fixture = self .useFixture (nova_fixtures .OSAPIFixture (
594
+ api_version = 'v2.1' , project_id = uuids .admin_project ))
595
+ self .admin_api = admin_api_fixture .admin_api
596
+ self .admin_api .microversion = 'latest'
597
+ user_api_fixture = self .useFixture (nova_fixtures .OSAPIFixture (
598
+ api_version = 'v2.1' , project_id = uuids .user_project ))
599
+ self .api = user_api_fixture .api
600
+ self .api .microversion = 'latest'
601
+
602
+ # the image fake backend needed for image discovery
603
+ nova .tests .unit .image .fake .stub_out_image_service (self )
604
+ self .addCleanup (nova .tests .unit .image .fake .FakeImageService_reset )
605
+
606
+ self .start_service ('conductor' )
607
+ # Enable the AggregateMultiTenancyIsolation filter before starting the
608
+ # scheduler service.
609
+ enabled_filters = CONF .filter_scheduler .enabled_filters
610
+ if 'AggregateMultiTenancyIsolation' not in enabled_filters :
611
+ enabled_filters .append ('AggregateMultiTenancyIsolation' )
612
+ self .flags (
613
+ enabled_filters = enabled_filters , group = 'filter_scheduler' )
614
+ # Add a custom weigher which will weigh host1, which will be in the
615
+ # admin project aggregate, higher than the other hosts which are in
616
+ # the non-admin project aggregate.
617
+ self .flags (weight_classes = [__name__ + '.HostNameWeigher' ],
618
+ group = 'filter_scheduler' )
619
+ self .start_service ('scheduler' )
620
+
621
+ for host in ('host1' , 'host2' , 'host3' ):
622
+ self .start_service ('compute' , host = host )
623
+
624
+ # Create an admin-only aggregate for the admin project. This is needed
625
+ # because if host1 is not in an aggregate with the filter_tenant_id
626
+ # metadata key, the filter will accept that host even for the non-admin
627
+ # project.
628
+ admin_aggregate = self ._create_aggregate (
629
+ self .admin_api , 'admin-aggregate' )
630
+ self ._add_host_to_aggregate (self .admin_api , admin_aggregate , 'host1' )
631
+
632
+ # Restrict the admin project to the admin aggregate.
633
+ self ._isolate_aggregate (
634
+ self .admin_api , admin_aggregate , uuids .admin_project )
635
+
636
+ # Create the tenant aggregate for the non-admin project.
637
+ tenant_aggregate = self ._create_aggregate (
638
+ self .admin_api , 'tenant-aggregate' )
639
+
640
+ # Add two compute hosts to the tenant aggregate. We exclude host1
641
+ # since that is weighed higher in HostNameWeigher and we want to
642
+ # ensure the scheduler properly filters out host1 before we even get
643
+ # to weighing the selected hosts.
644
+ for host in ('host2' , 'host3' ):
645
+ self ._add_host_to_aggregate (self .admin_api , tenant_aggregate , host )
646
+
647
+ # Restrict the non-admin project to the tenant aggregate.
648
+ self ._isolate_aggregate (
649
+ self .admin_api , tenant_aggregate , uuids .user_project )
650
+
651
+ def test_cold_migrate_server (self ):
652
+ """Creates a server using the non-admin project, then cold migrates
653
+ the server and asserts the server goes to the other host in the
654
+ isolated host aggregate via the AggregateMultiTenancyIsolation filter.
655
+ """
656
+ img = nova .tests .unit .image .fake .AUTO_DISK_CONFIG_ENABLED_IMAGE_UUID
657
+ server_req_body = self ._build_minimal_create_server_request (
658
+ self .api , 'test_cold_migrate_server' , image_uuid = img ,
659
+ networks = 'none' )
660
+ server = self .api .post_server ({'server' : server_req_body })
661
+ server = self ._wait_for_state_change (self .admin_api , server , 'ACTIVE' )
662
+ # Ensure the server ended up in host2 or host3
663
+ original_host = server ['OS-EXT-SRV-ATTR:host' ]
664
+ self .assertNotEqual ('host1' , original_host )
665
+ # Now cold migrate the server and it should end up in the other host
666
+ # in the same tenant-isolated aggregate.
667
+ self .admin_api .api_post (
668
+ '/servers/%s/action' % server ['id' ], {'migrate' : None })
669
+ server = self ._wait_for_state_change (
670
+ self .admin_api , server , 'VERIFY_RESIZE' )
671
+ # Ensure the server is on the other host in the same aggregate.
672
+ expected_host = 'host3' if original_host == 'host2' else 'host2'
673
+ self .assertEqual (expected_host , server ['OS-EXT-SRV-ATTR:host' ])
0 commit comments