Exemple #1
0
 def test_resource_request_from_extra_specs_append_request(self):
     extra_specs = {
         'resources:VCPU': '2',
         'resources:MEMORY_MB': '2048',
         'trait:HW_CPU_X86_AVX': 'required',
     }
     existing_req = utils.ResourceRequest()
     existing_req._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False, required_traits={
             'CUSTOM_MAGIC',
         })
     # Build up a ResourceRequest from the inside to compare against.
     expected = utils.ResourceRequest()
     expected._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 2,
             'MEMORY_MB': 2048,
         },
         required_traits={
             # In addition to traits from extra spec, we get traits from a
             # previous existing resource request
             'HW_CPU_X86_AVX',
             'CUSTOM_MAGIC',
         })
     self.assertResourceRequestsEqual(
         expected,
         utils.ResourceRequest.from_extra_specs(extra_specs,
                                                req=existing_req))
Exemple #2
0
    def test_resource_request_from_image_props_append_request(self):
        props = {'trait:CUSTOM_MAGIC': 'required'}
        image_meta_props = objects.ImageMetaProps.from_dict(props)

        existing_req = utils.ResourceRequest()
        existing_req._rg_by_id[None] = objects.RequestGroup(
            use_same_provider=False,
            resources={
                'VCPU': 2,
                'MEMORY_MB': 2048,
            },
            required_traits={
                'HW_CPU_X86_AVX',
            })
        # Build up a ResourceRequest from the inside to compare against.
        expected = utils.ResourceRequest()
        expected._rg_by_id[None] = objects.RequestGroup(
            use_same_provider=False,
            resources={
                'VCPU': 2,
                'MEMORY_MB': 2048,
            },
            required_traits={
                # In addition to information already contained in the existing
                # resource request, we add the traits from image properties
                'HW_CPU_X86_AVX',
                'CUSTOM_MAGIC',
            })
        self.assertResourceRequestsEqual(
            expected,
            utils.ResourceRequest.from_image_props(image_meta_props,
                                                   req=existing_req))
Exemple #3
0
 def test_resources_from_request_spec_flavor_and_image_traits(self):
     image = self._get_image_with_traits()
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=10,
                             ephemeral_gb=5,
                             swap=0,
                             extra_specs={
                                 'trait:CUSTOM_FLAVOR_TRAIT': 'required',
                                 'trait:CUSTOM_IMAGE_TRAIT2': 'required'
                             })
     expected_resources = utils.ResourceRequest()
     expected_resources._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 1,
             'MEMORY_MB': 1024,
             'DISK_GB': 15,
         },
         required_traits={
             # trait:CUSTOM_IMAGE_TRAIT2 is defined in both extra_specs and
             # image metadata. We get a union of both.
             'CUSTOM_IMAGE_TRAIT1',
             'CUSTOM_IMAGE_TRAIT2',
             'CUSTOM_FLAVOR_TRAIT',
         })
     self._test_resources_from_request_spec(expected_resources, flavor,
                                            image)
Exemple #4
0
 def test_resource_request_from_extra_specs(self):
     extra_specs = {
         'resources:VCPU': '2',
         'resources:MEMORY_MB': '2048',
         'trait:HW_CPU_X86_AVX': 'required',
         # Key skipped because no colons
         'nocolons': '42',
         'trait:CUSTOM_MAGIC': 'required',
         # Resource skipped because invalid resource class name
         'resources86:CUTSOM_MISSPELLED': '86',
         'resources1:SRIOV_NET_VF': '1',
         # Resource skipped because non-int-able value
         'resources86:CUSTOM_FOO': 'seven',
         # Resource skipped because negative value
         'resources86:CUSTOM_NEGATIVE': '-7',
         'resources1:IPV4_ADDRESS': '1',
         # Trait skipped because unsupported value
         'trait86:CUSTOM_GOLD': 'preferred',
         'trait1:CUSTOM_PHYSNET_NET1': 'required',
         'resources2:SRIOV_NET_VF': '1',
         'resources2:IPV4_ADDRESS': '2',
         'trait2:CUSTOM_PHYSNET_NET2': 'required',
         'trait2:HW_NIC_ACCEL_SSL': 'required',
         # Groupings that don't quite match the patterns are ignored
         'resources_5:SRIOV_NET_VF': '7',
         'traitFoo:HW_NIC_ACCEL_SSL': 'required',
         # Solo resource, no corresponding traits
         'resources3:DISK_GB': '5',
     }
     # Build up a ResourceRequest from the inside to compare against.
     expected = utils.ResourceRequest()
     expected._rg_by_id[None] = plib.RequestGroup(use_same_provider=False,
                                                  resources={
                                                      'VCPU': 2,
                                                      'MEMORY_MB': 2048,
                                                  },
                                                  required_traits={
                                                      'HW_CPU_X86_AVX',
                                                      'CUSTOM_MAGIC',
                                                  })
     expected._rg_by_id['1'] = plib.RequestGroup(resources={
         'SRIOV_NET_VF': 1,
         'IPV4_ADDRESS': 1,
     },
                                                 required_traits={
                                                     'CUSTOM_PHYSNET_NET1',
                                                 })
     expected._rg_by_id['2'] = plib.RequestGroup(resources={
         'SRIOV_NET_VF': 1,
         'IPV4_ADDRESS': 2,
     },
                                                 required_traits={
                                                     'CUSTOM_PHYSNET_NET2',
                                                     'HW_NIC_ACCEL_SSL',
                                                 })
     expected._rg_by_id['3'] = plib.RequestGroup(resources={
         'DISK_GB': 5,
     })
     self.assertResourceRequestsEqual(
         expected, utils.ResourceRequest.from_extra_specs(extra_specs))
Exemple #5
0
 def test_process_use_force_hosts(self):
     fake_nodes = objects.ComputeNodeList(objects=[
         objects.ComputeNode(host='fake-host',
                             uuid='12345678-1234-1234-1234-123456789012')
     ])
     self.mock_host_manager.get_compute_nodes_by_host_or_node.\
         return_value = fake_nodes
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=15,
                             ephemeral_gb=0,
                             swap=0)
     fake_spec = objects.RequestSpec(flavor=flavor, force_hosts=['test'])
     expected = utils.ResourceRequest()
     expected._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 1,
             'MEMORY_MB': 1024,
             'DISK_GB': 15,
         },
         in_tree='12345678-1234-1234-1234-123456789012',
     )
     resources = utils.resources_from_request_spec(self.context, fake_spec,
                                                   self.mock_host_manager)
     self.assertResourceRequestsEqual(expected, resources)
     expected_querystring = (
         'in_tree=12345678-1234-1234-1234-123456789012&'
         'limit=1000&resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1')
     self.assertEqual(expected_querystring, resources.to_querystring())
     self.mock_host_manager.get_compute_nodes_by_host_or_node.\
         assert_called_once_with(self.context, 'test', None, cell=None)
 def test_resources_from_request_spec_no_limit_based_on_hint(self, hints):
     """Tests that there is no limit applied to the
     GET /allocation_candidates query string if a given scheduler hint
     is in the request spec.
     """
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=15,
                             ephemeral_gb=0,
                             swap=0)
     fake_spec = objects.RequestSpec(
         flavor=flavor, scheduler_hints=hints)
     expected = utils.ResourceRequest()
     expected._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 1,
             'MEMORY_MB': 1024,
             'DISK_GB': 15,
         },
     )
     expected._limit = None
     resources = utils.resources_from_request_spec(fake_spec)
     self.assertResourceRequestsEqual(expected, resources)
     expected_querystring = (
         'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1'
     )
     self.assertEqual(expected_querystring, resources.to_querystring())
Exemple #7
0
 def test_get_resources_from_request_spec_granular(self):
     flavor = objects.Flavor(
         vcpus=1,
         memory_mb=1024,
         root_gb=10,
         ephemeral_gb=0,
         swap=0,
         extra_specs={
             'resources1:VGPU': '1',
             'resources1:VGPU_DISPLAY_HEAD': '2',
             # Replace
             'resources3:VCPU': '2',
             # Stay separate (don't sum)
             'resources42:SRIOV_NET_VF': '1',
             'resources24:SRIOV_NET_VF': '2',
             # Ignore
             'some:bogus': 'value',
             # Custom in the unnumbered group (merge with DISK_GB)
             'resources:CUSTOM_THING': '123',
             # Traits make it through
             'trait3:CUSTOM_SILVER': 'required',
             'trait3:CUSTOM_GOLD': 'required',
             # Delete standard
             'resources86:MEMORY_MB': '0',
             # Standard and custom zeroes don't make it through
             'resources:IPV4_ADDRESS': '0',
             'resources:CUSTOM_FOO': '0',
             # Bogus values don't make it through
             'resources1:MEMORY_MB': 'bogus'
         })
     expected_resources = utils.ResourceRequest()
     expected_resources._rg_by_id[None] = plib.RequestGroup(
         use_same_provider=False,
         resources={
             'DISK_GB': 10,
             'CUSTOM_THING': 123,
         })
     expected_resources._rg_by_id['1'] = plib.RequestGroup(
         resources={
             'VGPU': 1,
             'VGPU_DISPLAY_HEAD': 2,
         })
     expected_resources._rg_by_id['3'] = plib.RequestGroup(
         resources={
             'VCPU': 2,
         },
         required_traits={
             'CUSTOM_GOLD',
             'CUSTOM_SILVER',
         })
     expected_resources._rg_by_id['24'] = plib.RequestGroup(resources={
         'SRIOV_NET_VF':
         2,
     }, )
     expected_resources._rg_by_id['42'] = plib.RequestGroup(
         resources={
             'SRIOV_NET_VF': 1,
         })
     self._test_resources_from_request_spec(flavor, expected_resources)
Exemple #8
0
 def test_resource_request_add_group_inserts_the_group(self):
     req = utils.ResourceRequest()
     rg1 = objects.RequestGroup()
     req.add_request_group(rg1)
     rg2 = objects.RequestGroup()
     req.add_request_group(rg2)
     self.assertIs(rg1, req.get_request_group(1))
     self.assertIs(rg2, req.get_request_group(2))
Exemple #9
0
def get_flags_by_flavor_specs(flavor):
    req_spec = objects.RequestSpec(flavor=flavor)
    resource_request = scheduler_utils.ResourceRequest(req_spec)
    required_traits = resource_request.all_required_traits

    flags = [TRAITS_CPU_MAPPING.get(trait) for trait in required_traits]

    return set(flags)
Exemple #10
0
def get_flags_by_flavor_specs(flavor: 'objects.Flavor') -> ty.Set[str]:
    req_spec = objects.RequestSpec(flavor=flavor)
    resource_request = scheduler_utils.ResourceRequest(req_spec)
    required_traits = resource_request.all_required_traits

    flags = [TRAITS_CPU_MAPPING[trait] for trait in required_traits
             if trait in TRAITS_CPU_MAPPING]

    return set(flags)
Exemple #11
0
 def test_process_no_force_hosts_or_force_nodes(self):
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=15,
                             ephemeral_gb=0,
                             swap=0)
     fake_spec = objects.RequestSpec(flavor=flavor)
     expected = utils.ResourceRequest()
     resources = utils.resources_from_request_spec(fake_spec)
     self.assertEqual(expected._limit, resources._limit)
Exemple #12
0
    def test_resource_request_from_image_props(self):
        props = {'trait:CUSTOM_TRUSTED': 'required'}
        image_meta_props = objects.ImageMetaProps.from_dict(props)

        # Build up a ResourceRequest from the inside to compare against.
        expected = utils.ResourceRequest()
        expected._rg_by_id[None] = objects.RequestGroup(
            use_same_provider=False, required_traits={
                'CUSTOM_TRUSTED',
            })
        self.assertResourceRequestsEqual(
            expected, utils.ResourceRequest.from_image_props(image_meta_props))
Exemple #13
0
    def test_resource_request_add_group_inserts_the_group_implicit_group(self):
        req = utils.ResourceRequest()
        # this implicitly creates the unnumbered group
        unnumbered_rg = req.get_request_group(None)

        rg1 = objects.RequestGroup()
        req.add_request_group(rg1)
        rg2 = objects.RequestGroup()
        req.add_request_group(rg2)

        self.assertIs(rg1, req.get_request_group(1))
        self.assertIs(rg2, req.get_request_group(2))
        self.assertIs(unnumbered_rg, req.get_request_group(None))
Exemple #14
0
 def test_resources_from_request_spec_with_no_disk(self):
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=0,
                             ephemeral_gb=0,
                             swap=0)
     expected_resources = utils.ResourceRequest()
     expected_resources._rg_by_id[None] = plib.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 1,
             'MEMORY_MB': 1024,
         })
     self._test_resources_from_request_spec(flavor, expected_resources)
Exemple #15
0
 def test_resources_from_request_spec_flavor_only(self):
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=10,
                             ephemeral_gb=5,
                             swap=0)
     expected_resources = utils.ResourceRequest()
     expected_resources._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 1,
             'MEMORY_MB': 1024,
             'DISK_GB': 15,
         })
     self._test_resources_from_request_spec(expected_resources, flavor)
Exemple #16
0
 def test_get_resources_from_request_spec_remove_flavor_amounts(self):
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=10,
                             ephemeral_gb=5,
                             swap=0,
                             extra_specs={
                                 "resources:VCPU": 0,
                                 "resources:DISK_GB": 0
                             })
     expected_resources = utils.ResourceRequest()
     expected_resources._rg_by_id[None] = plib.RequestGroup(
         use_same_provider=False, resources={
             "MEMORY_MB": 1024,
         })
     self._test_resources_from_request_spec(flavor, expected_resources)
Exemple #17
0
 def test_get_resources_from_request_spec_custom_resource_class(self):
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=10,
                             ephemeral_gb=5,
                             swap=0,
                             extra_specs={"resources:CUSTOM_TEST_CLASS": 1})
     expected_resources = utils.ResourceRequest()
     expected_resources._rg_by_id[None] = plib.RequestGroup(
         use_same_provider=False,
         resources={
             "VCPU": 1,
             "MEMORY_MB": 1024,
             "DISK_GB": 15,
             "CUSTOM_TEST_CLASS": 1,
         })
     self._test_resources_from_request_spec(flavor, expected_resources)
Exemple #18
0
 def test_resources_from_request_spec_flavor_forbidden_trait(self):
     flavor = objects.Flavor(
         vcpus=1,
         memory_mb=1024,
         root_gb=10,
         ephemeral_gb=5,
         swap=0,
         extra_specs={'trait:CUSTOM_FLAVOR_TRAIT': 'forbidden'})
     expected_resources = utils.ResourceRequest()
     expected_resources._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 1,
             'MEMORY_MB': 1024,
             'DISK_GB': 15,
         },
         forbidden_traits={
             'CUSTOM_FLAVOR_TRAIT',
         })
     self._test_resources_from_request_spec(expected_resources, flavor)
Exemple #19
0
 def test_process_no_force_hosts_or_force_nodes(self):
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=15,
                             ephemeral_gb=0,
                             swap=0)
     expected = utils.ResourceRequest()
     expected._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 1,
             'MEMORY_MB': 1024,
             'DISK_GB': 15,
         },
     )
     rr = self._test_resources_from_request_spec(expected, flavor)
     expected_querystring = (
         'limit=1000&'
         'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1')
     self.assertEqual(expected_querystring, rr.to_querystring())
Exemple #20
0
 def test_get_resources_from_request_spec_vgpu(self):
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=10,
                             ephemeral_gb=0,
                             swap=0,
                             extra_specs={
                                 "resources:VGPU": 1,
                                 "resources:VGPU_DISPLAY_HEAD": 1
                             })
     expected_resources = utils.ResourceRequest()
     expected_resources._rg_by_id[None] = plib.RequestGroup(
         use_same_provider=False,
         resources={
             "VCPU": 1,
             "MEMORY_MB": 1024,
             "DISK_GB": 10,
             "VGPU": 1,
             "VGPU_DISPLAY_HEAD": 1,
         })
     self._test_resources_from_request_spec(flavor, expected_resources)
Exemple #21
0
def isolate_aggregates(ctxt, request_spec):
    """Prepare list of aggregates that should be isolated.

    This filter will prepare the list of aggregates that should be
    ignored by the placement service. It checks if aggregates has metadata
    'trait:<trait_name>='required' and if <trait_name> is not present in
    either of flavor extra specs or image properties, then those aggregates
    will be included in the list of isolated aggregates.

    Precisely this filter gets the trait request form the image and
    flavor and unions them. Then it accumulates the set of aggregates that
    request traits are "non_matching_by_metadata_keys" and uses that to
    produce the list of isolated aggregates.
    """

    if not CONF.scheduler.enable_isolated_aggregate_filtering:
        return False

    # Get required traits set in flavor and image
    res_req = utils.ResourceRequest(request_spec)
    required_traits = res_req.all_required_traits

    keys = ['trait:%s' % trait for trait in required_traits]

    isolated_aggregates = (
        objects.aggregate.AggregateList.get_non_matching_by_metadata_keys(
            ctxt, keys, 'trait:', value='required'))

    # Set list of isolated aggregates to destination object of request_spec
    if isolated_aggregates:
        if ('requested_destination' not in request_spec
                or request_spec.requested_destination is None):
            request_spec.requested_destination = objects.Destination()

        destination = request_spec.requested_destination
        destination.append_forbidden_aggregates(agg.uuid
                                                for agg in isolated_aggregates)

    return True
Exemple #22
0
 def test_process_use_force_hosts(self):
     flavor = objects.Flavor(vcpus=1,
                             memory_mb=1024,
                             root_gb=15,
                             ephemeral_gb=0,
                             swap=0)
     fake_spec = objects.RequestSpec(flavor=flavor, force_hosts=['test'])
     expected = utils.ResourceRequest()
     expected._rg_by_id[None] = objects.RequestGroup(
         use_same_provider=False,
         resources={
             'VCPU': 1,
             'MEMORY_MB': 1024,
             'DISK_GB': 15,
         },
     )
     expected._limit = None
     resources = utils.resources_from_request_spec(fake_spec)
     self.assertResourceRequestsEqual(expected, resources)
     expected_querystring = (
         'resources=DISK_GB%3A15%2CMEMORY_MB%3A1024%2CVCPU%3A1')
     self.assertEqual(expected_querystring, resources.to_querystring())
Exemple #23
0
 def test_alloc_cands_smoke(self):
     """Simple call to get_allocation_candidates for version checking."""
     with self._interceptor():
         self.client.get_allocation_candidates(self.context,
                                               utils.ResourceRequest())
Exemple #24
0
    def test_get_resources_from_request_spec_granular(self):
        flavor = objects.Flavor(
            vcpus=1,
            memory_mb=1024,
            root_gb=10,
            ephemeral_gb=0,
            swap=0,
            extra_specs={
                'resources1:VGPU': '1',
                'resources1:VGPU_DISPLAY_HEAD': '2',
                # Replace
                'resources3:VCPU': '2',
                # Stay separate (don't sum)
                'resources42:SRIOV_NET_VF': '1',
                'resources24:SRIOV_NET_VF': '2',
                # Ignore
                'some:bogus': 'value',
                # Custom in the unnumbered group (merge with DISK_GB)
                'resources:CUSTOM_THING': '123',
                # Traits make it through
                'trait3:CUSTOM_SILVER': 'required',
                'trait3:CUSTOM_GOLD': 'required',
                # Delete standard
                'resources86:MEMORY_MB': '0',
                # Standard and custom zeroes don't make it through
                'resources:IPV4_ADDRESS': '0',
                'resources:CUSTOM_FOO': '0',
                # Bogus values don't make it through
                'resources1:MEMORY_MB': 'bogus',
                'group_policy': 'none'
            })
        expected_resources = utils.ResourceRequest()
        expected_resources._group_policy = 'none'
        expected_resources._rg_by_id[None] = objects.RequestGroup(
            use_same_provider=False,
            resources={
                'DISK_GB': 10,
                'CUSTOM_THING': 123,
            })
        expected_resources._rg_by_id['1'] = objects.RequestGroup(
            resources={
                'VGPU': 1,
                'VGPU_DISPLAY_HEAD': 2,
            })
        expected_resources._rg_by_id['3'] = objects.RequestGroup(
            resources={
                'VCPU': 2,
            },
            required_traits={
                'CUSTOM_GOLD',
                'CUSTOM_SILVER',
            })
        expected_resources._rg_by_id['24'] = objects.RequestGroup(resources={
            'SRIOV_NET_VF':
            2,
        }, )
        expected_resources._rg_by_id['42'] = objects.RequestGroup(
            resources={
                'SRIOV_NET_VF': 1,
            })

        rr = self._test_resources_from_request_spec(expected_resources, flavor)
        expected_querystring = ('group_policy=none&'
                                'limit=1000&'
                                'required3=CUSTOM_GOLD%2CCUSTOM_SILVER&'
                                'resources=CUSTOM_THING%3A123%2CDISK_GB%3A10&'
                                'resources1=VGPU%3A1%2CVGPU_DISPLAY_HEAD%3A2&'
                                'resources24=SRIOV_NET_VF%3A2&'
                                'resources3=VCPU%3A2&'
                                'resources42=SRIOV_NET_VF%3A1')
        self.assertEqual(expected_querystring, rr.to_querystring())
Exemple #25
0
    def test_resource_request_from_extra_specs(self):
        extra_specs = {
            'resources:VCPU': '2',
            'resources:MEMORY_MB': '2048',
            'trait:HW_CPU_X86_AVX': 'required',
            # Key skipped because no colons
            'nocolons': '42',
            'trait:CUSTOM_MAGIC': 'required',
            'trait:CUSTOM_BRONZE': 'forbidden',
            # Resource skipped because invalid resource class name
            'resources86:CUTSOM_MISSPELLED': '86',
            'resources1:SRIOV_NET_VF': '1',
            # Resource skipped because non-int-able value
            'resources86:CUSTOM_FOO': 'seven',
            # Resource skipped because negative value
            'resources86:CUSTOM_NEGATIVE': '-7',
            'resources1:IPV4_ADDRESS': '1',
            # Trait skipped because unsupported value
            'trait86:CUSTOM_GOLD': 'preferred',
            'trait1:CUSTOM_PHYSNET_NET1': 'required',
            'trait1:CUSTOM_PHYSNET_NET2': 'forbidden',
            'resources2:SRIOV_NET_VF': '1',
            'resources2:IPV4_ADDRESS': '2',
            'trait2:CUSTOM_PHYSNET_NET2': 'required',
            'trait2:HW_NIC_ACCEL_SSL': 'required',
            # Groupings that don't quite match the patterns are ignored
            'resources_5:SRIOV_NET_VF': '7',
            'traitFoo:HW_NIC_ACCEL_SSL': 'required',
            # Solo resource, no corresponding traits
            'resources3:DISK_GB': '5',
            'group_policy': 'isolate',
        }
        # Build up a ResourceRequest from the inside to compare against.
        expected = utils.ResourceRequest()
        expected._group_policy = 'isolate'
        expected._rg_by_id[None] = objects.RequestGroup(
            use_same_provider=False,
            resources={
                'VCPU': 2,
                'MEMORY_MB': 2048,
            },
            required_traits={
                'HW_CPU_X86_AVX',
                'CUSTOM_MAGIC',
            },
            forbidden_traits={
                'CUSTOM_BRONZE',
            },
        )
        expected._rg_by_id['1'] = objects.RequestGroup(
            resources={
                'SRIOV_NET_VF': 1,
                'IPV4_ADDRESS': 1,
            },
            required_traits={
                'CUSTOM_PHYSNET_NET1',
            },
            forbidden_traits={
                'CUSTOM_PHYSNET_NET2',
            },
        )
        expected._rg_by_id['2'] = objects.RequestGroup(
            resources={
                'SRIOV_NET_VF': 1,
                'IPV4_ADDRESS': 2,
            },
            required_traits={
                'CUSTOM_PHYSNET_NET2',
                'HW_NIC_ACCEL_SSL',
            })
        expected._rg_by_id['3'] = objects.RequestGroup(resources={
            'DISK_GB': 5,
        })

        rr = utils.ResourceRequest.from_extra_specs(extra_specs)
        self.assertResourceRequestsEqual(expected, rr)
        expected_querystring = (
            'group_policy=isolate&'
            'limit=1000&'
            'required=CUSTOM_MAGIC%2CHW_CPU_X86_AVX%2C%21CUSTOM_BRONZE&'
            'required1=CUSTOM_PHYSNET_NET1%2C%21CUSTOM_PHYSNET_NET2&'
            'required2=CUSTOM_PHYSNET_NET2%2CHW_NIC_ACCEL_SSL&'
            'resources=MEMORY_MB%3A2048%2CVCPU%3A2&'
            'resources1=IPV4_ADDRESS%3A1%2CSRIOV_NET_VF%3A1&'
            'resources2=IPV4_ADDRESS%3A2%2CSRIOV_NET_VF%3A1&'
            'resources3=DISK_GB%3A5')
        self.assertEqual(expected_querystring, rr.to_querystring())