Пример #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))
Пример #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))
Пример #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)
Пример #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))
Пример #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)
Пример #6
0
 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())
Пример #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)
Пример #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))
Пример #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)
Пример #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)
Пример #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)
Пример #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))
Пример #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))
Пример #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)
Пример #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)
Пример #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)
Пример #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)
Пример #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)
Пример #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())
Пример #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)
Пример #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
Пример #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())
Пример #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())
Пример #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())
Пример #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())