Browse Source

Refactor ResourceRequest constructor

This refactor changes ResourceRequest __init__ to make only an empty
request and moves the ResourceReqeust creation from a RequestSpec to a
static factory method. This is a preparation to introduce another
factory method later that will generate a ResourceRequest from a single
ResourceGroup instead of a full RequestSpec.

Blueprint: support-interface-attach-with-qos-ports

Change-Id: Idd58298a6b01775f962b9bf0a0835f762c8e0ed2
changes/20/769720/5
Balazs Gibizer 7 months ago
parent
commit
c3804efd42
  1. 2
      nova/scheduler/request_filter.py
  2. 77
      nova/scheduler/utils.py
  3. 6
      nova/tests/functional/test_report_client.py
  4. 6
      nova/tests/unit/scheduler/client/test_report.py
  5. 104
      nova/tests/unit/scheduler/test_utils.py
  6. 3
      nova/virt/libvirt/utils.py

2
nova/scheduler/request_filter.py

@ -66,7 +66,7 @@ def isolate_aggregates(ctxt, request_spec):
return False
# Get required traits set in flavor and image
res_req = utils.ResourceRequest(request_spec)
res_req = utils.ResourceRequest.from_request_spec(request_spec)
required_traits = res_req.all_required_traits
keys = ['trait:%s' % trait for trait in required_traits]

77
nova/scheduler/utils.py

@ -57,7 +57,30 @@ class ResourceRequest(object):
XS_KEYPAT = re.compile(r"^(%s)([a-zA-Z0-9_-]{1,64})?:(.*)$" %
'|'.join((XS_RES_PREFIX, XS_TRAIT_PREFIX)))
def __init__(self, request_spec, enable_pinning_translate=True):
def __init__(self):
"""Create an empty ResourceRequest
Do not call this directly, use the existing static factory methods
from_*()
"""
self._rg_by_id: ty.Dict[str, objects.RequestGroup] = {}
self._group_policy: ty.Optional[str] = None
# Default to the configured limit but _limit can be
# set to None to indicate "no limit".
self._limit = CONF.scheduler.max_placement_results
self._root_required: ty.Set[str] = set()
self._root_forbidden: ty.Set[str] = set()
self.suffixed_groups_from_flavor = 0
# TODO(stephenfin): Remove this parameter once we drop support for
# 'vcpu_pin_set'
self.cpu_pinning_requested = False
@classmethod
def from_request_spec(
cls,
request_spec: 'objects.RequestSpec',
enable_pinning_translate: bool = True
) -> 'ResourceRequest':
"""Create a new instance of ResourceRequest from a RequestSpec.
Examines the flavor, flavor extra specs, (optional) image metadata,
@ -102,17 +125,13 @@ class ResourceRequest(object):
:param request_spec: An instance of ``objects.RequestSpec``.
:param enable_pinning_translate: True if the CPU policy extra specs
should be translated to placement resources and traits.
:return: a ResourceRequest instance
"""
# { ident: RequestGroup }
self._rg_by_id = {}
self._group_policy = None
res_req = cls()
# root_required+=these
self._root_required = request_spec.root_required
res_req._root_required = request_spec.root_required
# root_required+=!these
self._root_forbidden = request_spec.root_forbidden
# Default to the configured limit but _limit can be
# set to None to indicate "no limit".
self._limit = CONF.scheduler.max_placement_results
res_req._root_forbidden = request_spec.root_forbidden
# TODO(efried): Handle member_of[$S], which will need to be reconciled
# with destination.aggregates handling in resources_from_request_spec
@ -124,36 +143,35 @@ class ResourceRequest(object):
image = objects.ImageMeta(properties=objects.ImageMetaProps())
# Parse the flavor extra specs
self._process_extra_specs(request_spec.flavor)
res_req._process_extra_specs(request_spec.flavor)
self.suffixed_groups_from_flavor = self.get_num_of_suffixed_groups()
# NOTE(gibi): this assumes that _process_extra_specs() was already
# called but _process_requested_resources() hasn't called it yet.
res_req.suffixed_groups_from_flavor = (
res_req.get_num_of_suffixed_groups())
# Now parse the (optional) image metadata
self._process_image_meta(image)
# TODO(stephenfin): Remove this parameter once we drop support for
# 'vcpu_pin_set'
self.cpu_pinning_requested = False
res_req._process_image_meta(image)
if enable_pinning_translate:
# Next up, let's handle those pesky CPU pinning policies
self._translate_pinning_policies(request_spec.flavor, image)
res_req._translate_pinning_policies(request_spec.flavor, image)
# Add on any request groups that came from outside of the flavor/image,
# e.g. from ports or device profiles.
self._process_requested_resources(request_spec)
res_req._process_requested_resources(request_spec)
# Parse the flavor itself, though we'll only use these fields if they
# don't conflict with something already provided by the flavor extra
# specs. These are all added to the unsuffixed request group.
merged_resources = self.merged_resources()
merged_resources = res_req.merged_resources()
if (orc.VCPU not in merged_resources and
orc.PCPU not in merged_resources):
self._add_resource(orc.VCPU, request_spec.vcpus)
res_req._add_resource(orc.VCPU, request_spec.vcpus)
if orc.MEMORY_MB not in merged_resources:
self._add_resource(orc.MEMORY_MB, request_spec.memory_mb)
res_req._add_resource(orc.MEMORY_MB, request_spec.memory_mb)
if orc.DISK_GB not in merged_resources:
disk = request_spec.ephemeral_gb
@ -162,15 +180,17 @@ class ResourceRequest(object):
disk += request_spec.root_gb
if disk:
self._add_resource(orc.DISK_GB, disk)
res_req._add_resource(orc.DISK_GB, disk)
res_req._translate_memory_encryption(request_spec.flavor, image)
self._translate_memory_encryption(request_spec.flavor, image)
res_req._translate_vpmems_request(request_spec.flavor)
self._translate_vpmems_request(request_spec.flavor)
res_req._translate_vtpm_request(request_spec.flavor, image)
self._translate_vtpm_request(request_spec.flavor, image)
res_req.strip_zeros()
self.strip_zeros()
return res_req
def _process_requested_resources(self, request_spec):
requested_resources = (request_spec.requested_resources
@ -554,7 +574,7 @@ def resources_from_flavor(instance, flavor):
# just merge together all the resources specified in the flavor and pass
# them along. This will need to be adjusted when nested and/or shared RPs
# are in play.
res_req = ResourceRequest(req_spec)
res_req = ResourceRequest.from_request_spec(req_spec)
return res_req.merged_resources()
@ -573,7 +593,8 @@ def resources_from_request_spec(ctxt, spec_obj, host_manager,
:return: A ResourceRequest object.
:raises NoValidHost: If the specified host/node is not found in the DB.
"""
res_req = ResourceRequest(spec_obj, enable_pinning_translate)
res_req = ResourceRequest.from_request_spec(
spec_obj, enable_pinning_translate)
# values to get the destination target compute uuid
target_host = None

6
nova/tests/functional/test_report_client.py

@ -1006,7 +1006,7 @@ class SchedulerReportClientTests(test.TestCase):
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
req_spec = objects.RequestSpec(flavor=flavor, is_bfv=False)
self.client.get_allocation_candidates(
self.context, utils.ResourceRequest(req_spec))
self.context, utils.ResourceRequest.from_request_spec(req_spec))
def _set_up_provider_tree(self):
r"""Create two compute nodes in placement ("this" one, and another one)
@ -1221,7 +1221,7 @@ class SchedulerReportClientTests(test.TestCase):
req_spec = objects.RequestSpec(flavor=flavor, is_bfv=False)
self._set_up_provider_tree()
acs = self.client.get_allocation_candidates(
self.context, utils.ResourceRequest(req_spec))[0]
self.context, utils.ResourceRequest.from_request_spec(req_spec))[0]
# We're not going to validate all the allocations - Placement has
# tests for that - just make sure they're there.
self.assertEqual(3, len(acs))
@ -1284,7 +1284,7 @@ class SchedulerReportClientTests(test.TestCase):
(ot.COMPUTE_STATUS_DISABLED, ot.COMPUTE_VOLUME_EXTEND,
'CUSTOM_FOO'))
acs, _, ver = self.client.get_allocation_candidates(
self.context, utils.ResourceRequest(req_spec))
self.context, utils.ResourceRequest.from_request_spec(req_spec))
self.assertEqual('1.35', ver)
# This prints which ddt permutation we're using if it fails.
self.assertEqual(data['expected_acs'], len(acs), data)

6
nova/tests/unit/scheduler/client/test_report.py

@ -2087,7 +2087,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'group_policy3': 'none',
})
req_spec = objects.RequestSpec(flavor=flavor, is_bfv=False)
resources = scheduler_utils.ResourceRequest(req_spec)
resources = scheduler_utils.ResourceRequest.from_request_spec(req_spec)
resources.get_request_group(None).aggregates = [
['agg1', 'agg2', 'agg3'], ['agg1', 'agg2']]
forbidden_aggs = set(['agg1', 'agg5', 'agg6'])
@ -2145,7 +2145,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
'group_policy': 'bogus',
})
req_spec = objects.RequestSpec(flavor=flavor, is_bfv=False)
resources = scheduler_utils.ResourceRequest(req_spec)
resources = scheduler_utils.ResourceRequest.from_request_spec(req_spec)
expected_path = '/allocation_candidates'
expected_query = [
('limit', '42'),
@ -2189,7 +2189,7 @@ class TestProviderOperations(SchedulerReportClientTestCase):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
req_spec = objects.RequestSpec(flavor=flavor, is_bfv=False)
resources = scheduler_utils.ResourceRequest(req_spec)
resources = scheduler_utils.ResourceRequest.from_request_spec(req_spec)
res = self.client.get_allocation_candidates(self.context, resources)

104
nova/tests/unit/scheduler/test_utils.py

@ -834,7 +834,7 @@ class TestUtils(TestUtilsBase):
actual = utils.resources_from_flavor(instance, flavor)
self.assertEqual(expected, actual)
def test_resource_request_init(self):
def test_resource_request_from_request_spec(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
@ -848,10 +848,10 @@ class TestUtils(TestUtilsBase):
},
)
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_init_with_extra_specs(self):
def test_resource_request_from_request_spec_with_extra_specs(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={
@ -934,7 +934,7 @@ class TestUtils(TestUtilsBase):
)
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
expected_querystring = (
'group_policy=isolate&'
@ -949,7 +949,7 @@ class TestUtils(TestUtilsBase):
)
self.assertEqual(expected_querystring, rr.to_querystring())
def _test_resource_request_init_with_legacy_extra_specs(self):
def _test_resource_request_from_rs_with_legacy_extra_specs(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={
@ -960,7 +960,7 @@ class TestUtils(TestUtilsBase):
return objects.RequestSpec(flavor=flavor, is_bfv=False)
def test_resource_request_init_with_legacy_extra_specs(self):
def test_resource_request_from_request_spec_with_legacy_extra_specs(self):
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
use_same_provider=False,
@ -976,12 +976,14 @@ class TestUtils(TestUtilsBase):
'HW_CPU_HYPERTHREADING',
},
)
rs = self._test_resource_request_init_with_legacy_extra_specs()
rr = utils.ResourceRequest(rs)
rs = self._test_resource_request_from_rs_with_legacy_extra_specs()
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
self.assertTrue(rr.cpu_pinning_requested)
def test_resource_request_init_with_legacy_extra_specs_no_translate(self):
def test_resource_request_from_rs_with_legacy_extra_specs_no_translate(
self
):
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
use_same_provider=False,
@ -996,12 +998,13 @@ class TestUtils(TestUtilsBase):
# because enable_pinning_translate=False
forbidden_traits=set(),
)
rs = self._test_resource_request_init_with_legacy_extra_specs()
rr = utils.ResourceRequest(rs, enable_pinning_translate=False)
rs = self._test_resource_request_from_rs_with_legacy_extra_specs()
rr = utils.ResourceRequest.from_request_spec(
rs, enable_pinning_translate=False)
self.assertResourceRequestsEqual(expected, rr)
self.assertFalse(rr.cpu_pinning_requested)
def test_resource_request_init_with_image_props(self):
def test_resource_request_from_request_spec_with_image_props(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
image = objects.ImageMeta.from_dict({
@ -1024,10 +1027,10 @@ class TestUtils(TestUtilsBase):
}
)
rs = objects.RequestSpec(flavor=flavor, image=image, is_bfv=False)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def _test_resource_request_init_with_legacy_image_props(self):
def _test_resource_request_from_rs_with_legacy_image_props(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
image = objects.ImageMeta.from_dict({
@ -1039,7 +1042,7 @@ class TestUtils(TestUtilsBase):
})
return objects.RequestSpec(flavor=flavor, image=image, is_bfv=False)
def test_resource_request_init_with_legacy_image_props(self):
def test_resource_request_from_request_spec_with_legacy_image_props(self):
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
use_same_provider=False,
@ -1054,12 +1057,14 @@ class TestUtils(TestUtilsBase):
'HW_CPU_HYPERTHREADING',
},
)
rs = self._test_resource_request_init_with_legacy_image_props()
rr = utils.ResourceRequest(rs)
rs = self._test_resource_request_from_rs_with_legacy_image_props()
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
self.assertTrue(rr.cpu_pinning_requested)
def test_resource_request_init_with_legacy_image_props_no_translate(self):
def test_resource_request_from_rs_with_legacy_image_props_no_translate(
self
):
expected = FakeResourceRequest()
expected._rg_by_id[None] = objects.RequestGroup(
use_same_provider=False,
@ -1074,12 +1079,15 @@ class TestUtils(TestUtilsBase):
# because enable_pinning_translate=False
required_traits=set(),
)
rs = self._test_resource_request_init_with_legacy_image_props()
rr = utils.ResourceRequest(rs, enable_pinning_translate=False)
rs = self._test_resource_request_from_rs_with_legacy_image_props()
rr = utils.ResourceRequest.from_request_spec(
rs, enable_pinning_translate=False)
self.assertResourceRequestsEqual(expected, rr)
self.assertFalse(rr.cpu_pinning_requested)
def _test_resource_request_init_with_mixed_cpus(self, extra_specs):
def _test_resource_request_from_request_spec_with_mixed_cpus(
self, extra_specs
):
flavor = objects.Flavor(
vcpus=4, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs=extra_specs)
@ -1095,10 +1103,12 @@ class TestUtils(TestUtilsBase):
},
required_traits=set(),
)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_init_with_mixed_cpus_dedicated(self):
def test_resource_request_from_request_spec_with_mixed_cpus_dedicated(
self
):
"""Ensure the mixed instance, which is generated through
'hw:cpu_dedicated_mask' extra spec, properly requests the PCPU, VCPU,
MEMORY_MB and DISK_GB resources.
@ -1107,9 +1117,10 @@ class TestUtils(TestUtilsBase):
'hw:cpu_policy': 'mixed',
'hw:cpu_dedicated_mask': '2,3'
}
self._test_resource_request_init_with_mixed_cpus(extra_specs)
self._test_resource_request_from_request_spec_with_mixed_cpus(
extra_specs)
def test_resource_request_init_with_mixed_cpus_realtime(self):
def test_resource_request_from_request_spec_with_mixed_cpus_realtime(self):
"""Ensure the mixed instance, which is generated through real-time CPU
interface, properly requests the PCPU, VCPU, MEMORY_BM and DISK_GB
resources.
@ -1119,9 +1130,12 @@ class TestUtils(TestUtilsBase):
"hw:cpu_realtime": "yes",
"hw:cpu_realtime_mask": '2,3'
}
self._test_resource_request_init_with_mixed_cpus(extra_specs)
self._test_resource_request_from_request_spec_with_mixed_cpus(
extra_specs)
def _test_resource_request_init_with_mixed_cpus_iso_emu(self, extra_specs):
def _test_resource_request_from_request_spec_with_mixed_cpus_iso_emu(
self, extra_specs
):
flavor = objects.Flavor(
vcpus=4, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs=extra_specs)
@ -1139,10 +1153,10 @@ class TestUtils(TestUtilsBase):
},
required_traits=set(),
)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_init_with_mixed_cpus_iso_emu_realtime(self):
def test_resource_request_from_rswith_mixed_cpus_iso_emu_realtime(self):
"""Ensure the mixed instance, which is generated through the
'hw:cpu_dedicated_mask' extra spec, specs, properly requests the PCPU,
VCPU, MEMORY_MB, DISK_GB resources, ensure an extra PCPU resource is
@ -1153,9 +1167,10 @@ class TestUtils(TestUtilsBase):
'hw:cpu_dedicated_mask': '2,3',
'hw:emulator_threads_policy': 'isolate',
}
self._test_resource_request_init_with_mixed_cpus_iso_emu(extra_specs)
self._test_resource_request_from_request_spec_with_mixed_cpus_iso_emu(
extra_specs)
def test_resource_request_init_with_mixed_cpus_iso_emu_dedicated(self):
def test_resource_request_from_rs_with_mixed_cpus_iso_emu_dedicated(self):
"""Ensure the mixed instance, which is generated through realtime extra
specs, properly requests the PCPU, VCPU, MEMORY_MB, DISK_GB resources,
ensure an extra PCPU resource is requested due to a ISOLATE emulator
@ -1167,9 +1182,10 @@ class TestUtils(TestUtilsBase):
"hw:cpu_realtime_mask": '2,3',
'hw:emulator_threads_policy': 'isolate',
}
self._test_resource_request_init_with_mixed_cpus_iso_emu(extra_specs)
self._test_resource_request_from_request_spec_with_mixed_cpus_iso_emu(
extra_specs)
def test_resource_request_init_is_bfv(self):
def test_resource_request_from_request_spec_is_bfv(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=1555)
@ -1185,10 +1201,10 @@ class TestUtils(TestUtilsBase):
},
)
rs = objects.RequestSpec(flavor=flavor, is_bfv=True)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_with_vpmems(self):
def test_resource_request_from_request_spec_with_vpmems(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={'hw:pmem': '4GB, 4GB,SMALL'})
@ -1205,10 +1221,10 @@ class TestUtils(TestUtilsBase):
},
)
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_with_vtpm_1_2(self):
def test_resource_request_from_request_spec_with_vtpm_1_2(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={'hw:tpm_version': '1.2', 'hw:tpm_model': 'tpm-tis'},
@ -1230,10 +1246,10 @@ class TestUtils(TestUtilsBase):
},
)
rs = objects.RequestSpec(flavor=flavor, image=image, is_bfv=False)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_with_vtpm_2_0(self):
def test_resource_request_from_request_spec_with_vtpm_2_0(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
extra_specs={'hw:tpm_version': '2.0', 'hw:tpm_model': 'tpm-crb'},
@ -1255,14 +1271,14 @@ class TestUtils(TestUtilsBase):
},
)
rs = objects.RequestSpec(flavor=flavor, image=image, is_bfv=False)
rr = utils.ResourceRequest(rs)
rr = utils.ResourceRequest.from_request_spec(rs)
self.assertResourceRequestsEqual(expected, rr)
def test_resource_request_add_group_inserts_the_group(self):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
req = utils.ResourceRequest(rs)
req = utils.ResourceRequest.from_request_spec(rs)
rg1 = objects.RequestGroup(requester_id='foo',
required_traits={'CUSTOM_FOO'})
req._add_request_group(rg1)
@ -1279,7 +1295,7 @@ class TestUtils(TestUtilsBase):
flavor = objects.Flavor(
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0)
rs = objects.RequestSpec(flavor=flavor, is_bfv=False)
req = utils.ResourceRequest(rs)
req = utils.ResourceRequest.from_request_spec(rs)
rg = objects.RequestGroup(requester_id='foo')
self.assertRaises(ValueError, req._add_request_group, rg)
@ -1620,7 +1636,7 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
def _get_resource_request(self, extra_specs, image):
reqspec = self._get_request_spec(extra_specs, image)
return utils.ResourceRequest(reqspec)
return utils.ResourceRequest.from_request_spec(reqspec)
def _get_expected_resource_request(self, mem_encryption_context):
expected_resources = {
@ -1707,7 +1723,7 @@ class TestEncryptedMemoryTranslation(TestUtilsBase):
)
exc = self.assertRaises(
exception.FlavorImageConflict,
utils.ResourceRequest, reqspec
utils.ResourceRequest.from_request_spec, reqspec
)
error_data = {
'flavor_name': self.flavor_name,

3
nova/virt/libvirt/utils.py

@ -655,7 +655,8 @@ def mdev_uuid2name(mdev_uuid: str) -> str:
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)
resource_request = scheduler_utils.ResourceRequest.from_request_spec(
req_spec)
required_traits = resource_request.all_required_traits
flags = [TRAITS_CPU_MAPPING[trait] for trait in required_traits

Loading…
Cancel
Save