Skip to content

Commit cea01e5

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Defaults missing group_policy to 'none'"
2 parents 0a62d97 + ad4f798 commit cea01e5

File tree

4 files changed

+221
-34
lines changed

4 files changed

+221
-34
lines changed

doc/source/user/flavors.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,16 @@ Numbered groupings of resource classes and traits
799799
* ``none``: Different numbered request groups may be satisfied
800800
by different providers *or* common providers.
801801

802+
.. note::
803+
804+
If more than one group is specified then the ``group_policy`` is
805+
mandatory in the request. However such groups might come from other
806+
sources than flavor extra_spec (e.g. from Neutron ports with QoS
807+
minimum bandwidth policy). If the flavor does not specify any groups
808+
and ``group_policy`` but more than one group is coming from other
809+
sources then nova will default the ``group_policy`` to ``none`` to
810+
avoid scheduler failure.
811+
802812
For example, to create a server with the following VFs:
803813

804814
* One SR-IOV virtual function (VF) on NET1 with bandwidth 10000 bytes/sec

nova/scheduler/utils.py

Lines changed: 39 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ def __str__(self):
6666
return ', '.join(sorted(
6767
list(str(rg) for rg in list(self._rg_by_id.values()))))
6868

69+
@property
70+
def group_policy(self):
71+
return self._group_policy
72+
73+
@group_policy.setter
74+
def group_policy(self, value):
75+
self._group_policy = value
76+
6977
def get_request_group(self, ident):
7078
if ident not in self._rg_by_id:
7179
rq_grp = objects.RequestGroup(use_same_provider=bool(ident))
@@ -211,6 +219,10 @@ def resource_groups(self):
211219
for rg in self._rg_by_id.values():
212220
yield rg.resources
213221

222+
def get_num_of_numbered_groups(self):
223+
return len([ident for ident in self._rg_by_id.keys()
224+
if ident is not None])
225+
214226
def merged_resources(self, flavor_resources=None):
215227
"""Returns a merge of {resource_class: amount} for all resource groups.
216228
@@ -300,31 +312,14 @@ def to_queryparams(request_group, suffix):
300312
qparams = []
301313
if self._group_policy is not None:
302314
qparams.append(('group_policy', self._group_policy))
303-
nr_of_numbered_groups = 0
315+
304316
for ident, rg in self._rg_by_id.items():
305317
# [('resourcesN', 'rclass:amount,rclass:amount,...'),
306318
# ('requiredN', 'trait_name,!trait_name,...'),
307319
# ('member_ofN', 'in:uuid,uuid,...'),
308320
# ('member_ofN', 'in:uuid,uuid,...')]
309321
qparams.extend(to_queryparams(rg, ident or ''))
310-
if ident:
311-
nr_of_numbered_groups += 1
312-
if nr_of_numbered_groups >= 2 and not self._group_policy:
313-
# we know this will fail in placement so help the troubleshooting
314-
LOG.warning(
315-
"There is more than one numbered request group in the "
316-
"allocation candidate query but the flavor did not specify "
317-
"any group policy. This query will fail in placement due to "
318-
"the missing group policy. If you specified more than one "
319-
"numbered request group in the flavor extra_spec or booted "
320-
"with more than one neutron port that has resource request "
321-
"(i.e. the port has a QoS minimum bandwidth policy rule "
322-
"attached) then you have to specify the group policy in the "
323-
"flavor extra_spec. If it is OK to let these groups be "
324-
"satisfied by overlapping resource providers then use "
325-
"'group_policy': 'None'. If you want each group to be "
326-
"satisfied from a separate resource provider then use "
327-
"'group_policy': 'isolate'.")
322+
328323
return parse.urlencode(sorted(qparams))
329324

330325

@@ -468,9 +463,12 @@ def resources_from_request_spec(ctxt, spec_obj, host_manager):
468463
if rclass not in res_req.merged_resources()}
469464
# Now we don't need (or want) any remaining zero entries - remove them.
470465
res_req.strip_zeros()
466+
467+
numbered_groups_from_flavor = res_req.get_num_of_numbered_groups()
471468
else:
472469
# Start with an empty one
473470
res_req = ResourceRequest()
471+
numbered_groups_from_flavor = 0
474472

475473
# Process any image properties
476474
if 'image' in spec_obj and 'properties' in spec_obj.image:
@@ -556,6 +554,28 @@ def resources_from_request_spec(ctxt, spec_obj, host_manager):
556554
for key in spec_obj.scheduler_hints)):
557555
res_req._limit = None
558556

557+
if res_req.get_num_of_numbered_groups() >= 2 and not res_req.group_policy:
558+
LOG.warning(
559+
"There is more than one numbered request group in the "
560+
"allocation candidate query but the flavor did not specify "
561+
"any group policy. This query would fail in placement due to "
562+
"the missing group policy. If you specified more than one "
563+
"numbered request group in the flavor extra_spec then you need to "
564+
"specify the group policy in the flavor extra_spec. If it is OK "
565+
"to let these groups be satisfied by overlapping resource "
566+
"providers then use 'group_policy': 'none'. If you want each "
567+
"group to be satisfied from a separate resource provider then "
568+
"use 'group_policy': 'isolate'.")
569+
570+
if numbered_groups_from_flavor <= 1:
571+
LOG.info(
572+
"At least one numbered request group is defined outside of "
573+
"the flavor (e.g. in a port that has a QoS minimum bandwidth "
574+
"policy rule attached) but the flavor did not specify any "
575+
"group policy. To avoid the placement failure nova defaults "
576+
"the group policy to 'none'.")
577+
res_req.group_policy = 'none'
578+
559579
return res_req
560580

561581

nova/tests/unit/scheduler/test_utils.py

Lines changed: 153 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,8 @@ def test_resources_from_request_spec_no_aggregates(self):
362362
self.context, reqspec, self.mock_host_manager)
363363
self.assertEqual([], req.get_request_group(None).aggregates)
364364

365-
@mock.patch("nova.scheduler.utils.ResourceRequest.from_extra_specs")
365+
@mock.patch("nova.scheduler.utils.ResourceRequest.from_extra_specs",
366+
return_value=utils.ResourceRequest())
366367
def test_process_extra_specs_granular_called(self, mock_proc):
367368
flavor = objects.Flavor(vcpus=1,
368369
memory_mb=1024,
@@ -794,20 +795,6 @@ def test_resource_request_from_extra_specs(self):
794795
)
795796
self.assertEqual(expected_querystring, rr.to_querystring())
796797

797-
def test_more_than_one_resource_request_without_group_policy_warns(self):
798-
extra_specs = {
799-
'resources:VCPU': '2',
800-
'resources1:CUSTOM_FOO': '1'
801-
}
802-
rr = utils.ResourceRequest.from_extra_specs(extra_specs)
803-
rr.add_request_group(objects.RequestGroup(resources={'CUSTOM_BAR': 5}))
804-
805-
rr.to_querystring()
806-
self.assertIn(
807-
"There is more than one numbered request group in the allocation "
808-
"candidate query but the flavor did not specify any group policy.",
809-
self.stdlog.logger.output)
810-
811798
def test_resource_request_from_extra_specs_append_request(self):
812799
extra_specs = {
813800
'resources:VCPU': '2',
@@ -1138,3 +1125,154 @@ def test_get_weight_multiplier(self):
11381125
1.8,
11391126
utils.get_weight_multiplier(host1, 'cpu_weight_multiplier', 1.0)
11401127
)
1128+
1129+
1130+
class TestResourcesFromRequestGroupDefaultPolicy(test.NoDBTestCase):
1131+
"""These test cases assert what happens when the group policy is missing
1132+
from the flavor but more than one numbered request group is requested from
1133+
various sources. Note that while image can provide required traits for the
1134+
resource request those traits are always added to the unnumbered group so
1135+
image cannot be a source of additional numbered groups.
1136+
"""
1137+
1138+
def setUp(self):
1139+
super(TestResourcesFromRequestGroupDefaultPolicy, self).setUp()
1140+
self.context = nova_context.get_admin_context()
1141+
self.port_group1 = objects.RequestGroup.from_port_request(
1142+
self.context, uuids.port1,
1143+
port_resource_request={
1144+
"resources": {
1145+
"NET_BW_IGR_KILOBIT_PER_SEC": 1000,
1146+
"NET_BW_EGR_KILOBIT_PER_SEC": 1000},
1147+
"required": ["CUSTOM_PHYSNET_2",
1148+
"CUSTOM_VNIC_TYPE_NORMAL"]
1149+
})
1150+
self.port_group2 = objects.RequestGroup.from_port_request(
1151+
self.context, uuids.port2,
1152+
port_resource_request={
1153+
"resources": {
1154+
"NET_BW_IGR_KILOBIT_PER_SEC": 2000,
1155+
"NET_BW_EGR_KILOBIT_PER_SEC": 2000},
1156+
"required": ["CUSTOM_PHYSNET_3",
1157+
"CUSTOM_VNIC_TYPE_DIRECT"]
1158+
})
1159+
1160+
def test_one_group_from_flavor_dont_warn(self):
1161+
flavor = objects.Flavor(
1162+
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
1163+
extra_specs={
1164+
'resources1:CUSTOM_BAR': '2',
1165+
})
1166+
request_spec = objects.RequestSpec(
1167+
flavor=flavor, image=objects.ImageMeta(), requested_resources=[])
1168+
1169+
rr = utils.resources_from_request_spec(
1170+
self.context, request_spec, host_manager=mock.Mock())
1171+
1172+
log = self.stdlog.logger.output
1173+
self.assertNotIn(
1174+
"There is more than one numbered request group in the allocation "
1175+
"candidate query but the flavor did not specify any group policy.",
1176+
log)
1177+
self.assertNotIn(
1178+
"To avoid the placement failure nova defaults the group policy to "
1179+
"'none'.",
1180+
log)
1181+
self.assertIsNone(rr.group_policy)
1182+
self.assertNotIn('group_policy=none', rr.to_querystring())
1183+
1184+
def test_one_group_from_port_dont_warn(self):
1185+
flavor = objects.Flavor(
1186+
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
1187+
extra_specs={})
1188+
request_spec = objects.RequestSpec(
1189+
flavor=flavor, image=objects.ImageMeta(),
1190+
requested_resources=[self.port_group1])
1191+
1192+
rr = utils.resources_from_request_spec(
1193+
self.context, request_spec, host_manager=mock.Mock())
1194+
1195+
log = self.stdlog.logger.output
1196+
self.assertNotIn(
1197+
"There is more than one numbered request group in the allocation "
1198+
"candidate query but the flavor did not specify any group policy.",
1199+
log)
1200+
self.assertNotIn(
1201+
"To avoid the placement failure nova defaults the group policy to "
1202+
"'none'.",
1203+
log)
1204+
self.assertIsNone(rr.group_policy)
1205+
self.assertNotIn('group_policy=none', rr.to_querystring())
1206+
1207+
def test_two_groups_from_flavor_only_warns(self):
1208+
flavor = objects.Flavor(
1209+
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
1210+
extra_specs={
1211+
'resources1:CUSTOM_BAR': '2',
1212+
'resources2:CUSTOM_FOO': '1'
1213+
})
1214+
request_spec = objects.RequestSpec(
1215+
flavor=flavor, image=objects.ImageMeta(), requested_resources=[])
1216+
1217+
rr = utils.resources_from_request_spec(
1218+
self.context, request_spec, host_manager=mock.Mock())
1219+
1220+
log = self.stdlog.logger.output
1221+
self.assertIn(
1222+
"There is more than one numbered request group in the allocation "
1223+
"candidate query but the flavor did not specify any group policy.",
1224+
log)
1225+
self.assertNotIn(
1226+
"To avoid the placement failure nova defaults the group policy to "
1227+
"'none'.",
1228+
log)
1229+
self.assertIsNone(rr.group_policy)
1230+
self.assertNotIn('group_policy', rr.to_querystring())
1231+
1232+
def test_one_group_from_flavor_one_from_port_policy_defaulted(self):
1233+
flavor = objects.Flavor(
1234+
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
1235+
extra_specs={
1236+
'resources1:CUSTOM_BAR': '2',
1237+
})
1238+
request_spec = objects.RequestSpec(
1239+
flavor=flavor, image=objects.ImageMeta(),
1240+
requested_resources=[self.port_group1])
1241+
1242+
rr = utils.resources_from_request_spec(
1243+
self.context, request_spec, host_manager=mock.Mock())
1244+
1245+
log = self.stdlog.logger.output
1246+
self.assertIn(
1247+
"There is more than one numbered request group in the allocation "
1248+
"candidate query but the flavor did not specify any group policy.",
1249+
log)
1250+
self.assertIn(
1251+
"To avoid the placement failure nova defaults the group policy to "
1252+
"'none'.",
1253+
log)
1254+
self.assertEqual('none', rr.group_policy)
1255+
self.assertIn('group_policy=none', rr.to_querystring())
1256+
1257+
def test_two_groups_from_ports_policy_defaulted(self):
1258+
flavor = objects.Flavor(
1259+
vcpus=1, memory_mb=1024, root_gb=10, ephemeral_gb=5, swap=0,
1260+
extra_specs={})
1261+
request_spec = objects.RequestSpec(
1262+
flavor=flavor, image=objects.ImageMeta(),
1263+
requested_resources=[self.port_group1, self.port_group2])
1264+
1265+
rr = utils.resources_from_request_spec(
1266+
self.context, request_spec, host_manager=mock.Mock())
1267+
1268+
log = self.stdlog.logger.output
1269+
self.assertIn(
1270+
"There is more than one numbered request group in the allocation "
1271+
"candidate query but the flavor did not specify any group policy.",
1272+
log)
1273+
self.assertIn(
1274+
"To avoid the placement failure nova defaults the group policy to "
1275+
"'none'.",
1276+
log)
1277+
self.assertEqual('none', rr.group_policy)
1278+
self.assertIn('group_policy=none', rr.to_querystring())
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
other:
3+
- |
4+
`Numbered request groups`_ can be defined in the flavor extra_spec
5+
but they can come from other sources as well (e.g. neutron ports).
6+
If there is more than one numbered request group in the
7+
allocation candidate query and the flavor does not specify any
8+
group policy then the query will fail in placement as group_policy
9+
is mandatory in this case. Nova previously printed a warning to the
10+
scheduler logs but let the request fail. However the creator of
11+
the flavor cannot know if the flavor later on will be used in a boot
12+
request that has other numbered request groups. So nova will start
13+
defaulting the group_policy to 'none' which means that the resource
14+
providers fulfilling the numbered request groups can overlap.
15+
Nova will only default the group_policy if it is not provided in the flavor
16+
extra_spec, and there is more than one numbered request group present in
17+
the final request, and the flavor only provided one or zero of such groups.
18+
19+
.. _`Numbered request groups`: https://docs.openstack.org/nova/latest/user/flavors.html#extra-specs-numbered-resource-groupings

0 commit comments

Comments
 (0)