Skip to content

Commit d93724d

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Add placement request pre-filter compute_status_filter"
2 parents 383c254 + 168d34c commit d93724d

File tree

5 files changed

+86
-5
lines changed

5 files changed

+86
-5
lines changed

doc/source/admin/configuration/schedulers.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,27 @@ non-ceph backed computes), enabling this feature will ensure that the
7575
scheduler does not send requests to boot a ``qcow2`` image to computes
7676
backed by ceph.
7777

78+
Compute Disabled Status Support
79+
-------------------------------
80+
81+
Starting in the Train release, there is a mandatory `pre-filter
82+
<https://specs.openstack.org/openstack/nova-specs/specs/train/approved/pre-filter-disabled-computes.html>`_
83+
which will exclude disabled compute nodes similar to the `ComputeFilter`_.
84+
Compute node resource providers with the ``COMPUTE_STATUS_DISABLED`` trait will
85+
be excluded as scheduling candidates. The trait is managed by the
86+
``nova-compute`` service and should mirror the ``disabled`` status on the
87+
related compute service record in the `os-services`_ API. For example, if a
88+
compute service's status is ``disabled``, the related compute node resource
89+
provider(s) for that service should have the ``COMPUTE_STATUS_DISABLED`` trait.
90+
When the service status is ``enabled`` the ``COMPUTE_STATUS_DISABLED`` trait
91+
shall be removed.
92+
93+
If the compute service is down when the status is changed, the trait will be
94+
synchronized by the compute service when it is restarted.
95+
96+
.. _os-services: https://developer.openstack.org/api-ref/compute/#compute-services-os-services
97+
98+
7899
Filter scheduler
79100
~~~~~~~~~~~~~~~~
80101

nova/scheduler/request_filter.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,32 @@ def require_image_type_support(ctxt, request_spec):
154154
return True
155155

156156

157+
@trace_request_filter
158+
def compute_status_filter(ctxt, request_spec):
159+
"""Pre-filter compute node resource providers using COMPUTE_STATUS_DISABLED
160+
161+
The ComputeFilter filters out hosts for compute services that are
162+
disabled. Compute node resource providers managed by a disabled compute
163+
service should have the COMPUTE_STATUS_DISABLED trait set and be excluded
164+
by this mandatory pre-filter.
165+
"""
166+
# We're called before scheduler utils resources_from_request_spec builds
167+
# the RequestGroup stuff which gets used to form the
168+
# GET /allocation_candidates call, so mutate the flavor for that call but
169+
# don't persist the change.
170+
trait_name = os_traits.COMPUTE_STATUS_DISABLED
171+
request_spec.flavor.extra_specs['trait:%s' % trait_name] = 'forbidden'
172+
request_spec.obj_reset_changes(fields=['flavor'], recursive=True)
173+
LOG.debug('compute_status_filter request filter added forbidden '
174+
'trait %s', trait_name)
175+
return True
176+
177+
157178
ALL_REQUEST_FILTERS = [
158179
require_tenant_aggregate,
159180
map_az_to_placement_aggregate,
160181
require_image_type_support,
182+
compute_status_filter,
161183
]
162184

163185

nova/tests/unit/scheduler/test_request_filter.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ def test_with_tenant_and_az(self, getmd):
170170
]
171171
reqspec = objects.RequestSpec(project_id='owner',
172172
availability_zone='myaz')
173+
# flavor is needed for the compute_status_filter
174+
reqspec.flavor = objects.Flavor(extra_specs={})
173175
request_filter.process_reqspec(self.context, reqspec)
174176
self.assertEqual(
175177
','.join(sorted([uuids.agg1, uuids.agg2])),
@@ -231,3 +233,18 @@ def test_require_image_type_support_adds_trait(self, mock_log):
231233
log_lines = [c[0][0] for c in mock_log.debug.call_args_list]
232234
self.assertIn('added required trait', log_lines[0])
233235
self.assertIn('took %.1f seconds', log_lines[1])
236+
237+
@mock.patch.object(request_filter, 'LOG')
238+
def test_compute_status_filter(self, mock_log):
239+
reqspec = objects.RequestSpec(flavor=objects.Flavor(extra_specs={}))
240+
request_filter.compute_status_filter(self.context, reqspec)
241+
# The forbidden trait should be added to the RequestSpec.flavor.
242+
self.assertEqual({'trait:COMPUTE_STATUS_DISABLED': 'forbidden'},
243+
reqspec.flavor.extra_specs)
244+
# The RequestSpec.flavor changes should be reset so they are not
245+
# persisted.
246+
self.assertEqual(set(), reqspec.flavor.obj_what_changed())
247+
# Assert both the in-method logging and trace decorator.
248+
log_lines = [c[0][0] for c in mock_log.debug.call_args_list]
249+
self.assertIn('added forbidden trait', log_lines[0])
250+
self.assertIn('took %.1f seconds', log_lines[1])

nova/tests/unit/scheduler/test_scheduler.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -81,10 +81,11 @@ def test_1_correct_init(self):
8181
manager = self.manager
8282
self.assertIsInstance(manager.driver, self.driver_cls)
8383

84+
@mock.patch('nova.scheduler.request_filter.process_reqspec')
8485
@mock.patch('nova.scheduler.utils.resources_from_request_spec')
8586
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
8687
'get_allocation_candidates')
87-
def test_select_destination(self, mock_get_ac, mock_rfrs):
88+
def test_select_destination(self, mock_get_ac, mock_rfrs, mock_process):
8889
fake_spec = objects.RequestSpec()
8990
fake_spec.instance_uuid = uuids.instance
9091
fake_version = "9.42"
@@ -98,6 +99,7 @@ def test_select_destination(self, mock_get_ac, mock_rfrs):
9899
) as select_destinations:
99100
self.manager.select_destinations(self.context, spec_obj=fake_spec,
100101
instance_uuids=[fake_spec.instance_uuid])
102+
mock_process.assert_called_once_with(self.context, fake_spec)
101103
select_destinations.assert_called_once_with(
102104
self.context, fake_spec,
103105
[fake_spec.instance_uuid], expected_alloc_reqs_by_rp_uuid,
@@ -115,11 +117,12 @@ def test_select_destination(self, mock_get_ac, mock_rfrs):
115117
[fake_spec.instance_uuid], expected_alloc_reqs_by_rp_uuid,
116118
mock.sentinel.p_sums, fake_version, True)
117119

120+
@mock.patch('nova.scheduler.request_filter.process_reqspec')
118121
@mock.patch('nova.scheduler.utils.resources_from_request_spec')
119122
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
120123
'get_allocation_candidates')
121124
def test_select_destination_return_objects(self, mock_get_ac,
122-
mock_rfrs):
125+
mock_rfrs, mock_process):
123126
fake_spec = objects.RequestSpec()
124127
fake_spec.instance_uuid = uuids.instance
125128
fake_version = "9.42"
@@ -141,6 +144,7 @@ def test_select_destination_return_objects(self, mock_get_ac,
141144
return_objects=True, return_alternates=True)
142145
sel_host = dests[0][0]
143146
self.assertIsInstance(sel_host, objects.Selection)
147+
mock_process.assert_called_once_with(None, fake_spec)
144148
# Since both return_objects and return_alternates are True, the
145149
# driver should have been called with True for return_alternates.
146150
select_destinations.assert_called_once_with(None, fake_spec,
@@ -163,11 +167,12 @@ def test_select_destination_return_objects(self, mock_get_ac,
163167
[fake_spec.instance_uuid], expected_alloc_reqs_by_rp_uuid,
164168
mock.sentinel.p_sums, fake_version, False)
165169

170+
@mock.patch('nova.scheduler.request_filter.process_reqspec')
166171
@mock.patch('nova.scheduler.utils.resources_from_request_spec')
167172
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
168173
'get_allocation_candidates')
169174
def _test_select_destination(self, get_allocation_candidates_response,
170-
mock_get_ac, mock_rfrs):
175+
mock_get_ac, mock_rfrs, mock_process):
171176
fake_spec = objects.RequestSpec()
172177
fake_spec.instance_uuid = uuids.instance
173178
place_res = get_allocation_candidates_response
@@ -179,6 +184,7 @@ def _test_select_destination(self, get_allocation_candidates_response,
179184
spec_obj=fake_spec,
180185
instance_uuids=[fake_spec.instance_uuid])
181186
select_destinations.assert_not_called()
187+
mock_process.assert_called_once_with(self.context, fake_spec)
182188
mock_get_ac.assert_called_once_with(
183189
self.context, mock_rfrs.return_value)
184190

@@ -227,10 +233,12 @@ def test_select_destination_is_rebuild(self, mock_get_ac, mock_rfrs,
227233
mock_get_ac.assert_not_called()
228234
mock_process.assert_not_called()
229235

236+
@mock.patch('nova.scheduler.request_filter.process_reqspec')
230237
@mock.patch('nova.scheduler.utils.resources_from_request_spec')
231238
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
232239
'get_allocation_candidates')
233-
def test_select_destination_with_4_3_client(self, mock_get_ac, mock_rfrs):
240+
def test_select_destination_with_4_3_client(self, mock_get_ac, mock_rfrs,
241+
mock_process):
234242
fake_spec = objects.RequestSpec()
235243
place_res = (fakes.ALLOC_REQS, mock.sentinel.p_sums, "42.0")
236244
mock_get_ac.return_value = place_res
@@ -241,19 +249,21 @@ def test_select_destination_with_4_3_client(self, mock_get_ac, mock_rfrs):
241249
with mock.patch.object(self.manager.driver, 'select_destinations'
242250
) as select_destinations:
243251
self.manager.select_destinations(self.context, spec_obj=fake_spec)
252+
mock_process.assert_called_once_with(self.context, fake_spec)
244253
select_destinations.assert_called_once_with(self.context,
245254
fake_spec, None, expected_alloc_reqs_by_rp_uuid,
246255
mock.sentinel.p_sums, "42.0", False)
247256
mock_get_ac.assert_called_once_with(
248257
self.context, mock_rfrs.return_value)
249258

250259
# TODO(sbauza): Remove that test once the API v4 is removed
260+
@mock.patch('nova.scheduler.request_filter.process_reqspec')
251261
@mock.patch('nova.scheduler.utils.resources_from_request_spec')
252262
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
253263
'get_allocation_candidates')
254264
@mock.patch.object(objects.RequestSpec, 'from_primitives')
255265
def test_select_destination_with_old_client(self, from_primitives,
256-
mock_get_ac, mock_rfrs):
266+
mock_get_ac, mock_rfrs, mock_process):
257267
fake_spec = objects.RequestSpec()
258268
fake_spec.instance_uuid = uuids.instance
259269
from_primitives.return_value = fake_spec
@@ -269,6 +279,7 @@ def test_select_destination_with_old_client(self, from_primitives,
269279
self.context, request_spec='fake_spec',
270280
filter_properties='fake_props',
271281
instance_uuids=[fake_spec.instance_uuid])
282+
mock_process.assert_called_once_with(self.context, fake_spec)
272283
select_destinations.assert_called_once_with(
273284
self.context, fake_spec,
274285
[fake_spec.instance_uuid], expected_alloc_reqs_by_rp_uuid,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
features:
3+
- |
4+
A mandatory scheduling pre-filter has been added which will exclude
5+
disabled compute nodes where the related ``nova-compute`` service status
6+
is mirrored with a ``COMPUTE_STATUS_DISABLED`` trait on the compute node
7+
resource provider(s) for that service in Placement. See the
8+
`admin scheduler configuration docs`__ for details.
9+
10+
__ https://docs.openstack.org/nova/latest/admin/configuration/schedulers.html#compute-disabled-status-support

0 commit comments

Comments
 (0)