|
11 | 11 | # under the License.
|
12 | 12 |
|
13 | 13 | import os_resource_classes as orc
|
| 14 | +import os_traits |
14 | 15 | import six
|
15 | 16 |
|
16 | 17 | from nova import context as nova_context
|
17 | 18 | from nova import exception
|
18 | 19 | from nova import objects
|
19 | 20 | from nova.tests.functional.api import client as api_client
|
20 | 21 | from nova.tests.functional import integrated_helpers
|
| 22 | +from nova.tests.unit.image import fake as fake_image |
| 23 | +from nova import utils |
21 | 24 |
|
22 | 25 |
|
23 | 26 | class TestServicesAPI(integrated_helpers.ProviderUsageBaseTestCase):
|
@@ -171,3 +174,132 @@ def test_evacuate_then_delete_compute_service(self):
|
171 | 174 | log_output = self.stdlog.logger.output
|
172 | 175 | self.assertIn('Error updating resources for node host1.', log_output)
|
173 | 176 | self.assertIn('Failed to create resource provider host1', log_output)
|
| 177 | + |
| 178 | + |
| 179 | +class ComputeStatusFilterTest(integrated_helpers.ProviderUsageBaseTestCase): |
| 180 | + """Tests the API, compute service and Placement interaction with the |
| 181 | + COMPUTE_STATUS_DISABLED trait when a compute service is enable/disabled. |
| 182 | +
|
| 183 | + This version of the test uses the 2.latest microversion for testing the |
| 184 | + 2.53+ behavior of the PUT /os-services/{service_id} API. |
| 185 | + """ |
| 186 | + compute_driver = 'fake.SmallFakeDriver' |
| 187 | + |
| 188 | + def _update_service(self, service, disabled, forced_down=None): |
| 189 | + """Update the service using the 2.53 request schema. |
| 190 | +
|
| 191 | + :param service: dict representing the service resource in the API |
| 192 | + :param disabled: True if the service should be disabled, False if the |
| 193 | + service should be enabled |
| 194 | + :param forced_down: Optionally change the forced_down value. |
| 195 | + """ |
| 196 | + status = 'disabled' if disabled else 'enabled' |
| 197 | + req = {'status': status} |
| 198 | + if forced_down is not None: |
| 199 | + req['forced_down'] = forced_down |
| 200 | + self.admin_api.put_service(service['id'], req) |
| 201 | + |
| 202 | + def test_compute_status_filter(self): |
| 203 | + """Tests the compute_status_filter placement request filter""" |
| 204 | + # Start a compute service so a compute node and resource provider is |
| 205 | + # created. |
| 206 | + compute = self._start_compute('host1') |
| 207 | + # Get the UUID of the resource provider that was created. |
| 208 | + rp_uuid = self._get_provider_uuid_by_host('host1') |
| 209 | + # Get the service from the compute API. |
| 210 | + services = self.admin_api.get_services(binary='nova-compute', |
| 211 | + host='host1') |
| 212 | + self.assertEqual(1, len(services)) |
| 213 | + service = services[0] |
| 214 | + |
| 215 | + # At this point, the service should be enabled and the |
| 216 | + # COMPUTE_STATUS_DISABLED trait should not be set on the |
| 217 | + # resource provider in placement. |
| 218 | + self.assertEqual('enabled', service['status']) |
| 219 | + rp_traits = self._get_provider_traits(rp_uuid) |
| 220 | + trait = os_traits.COMPUTE_STATUS_DISABLED |
| 221 | + self.assertNotIn(trait, rp_traits) |
| 222 | + |
| 223 | + # Now disable the compute service via the API. |
| 224 | + self._update_service(service, disabled=True) |
| 225 | + |
| 226 | + # The update to placement should be synchronous so check the provider |
| 227 | + # traits and COMPUTE_STATUS_DISABLED should be set. |
| 228 | + rp_traits = self._get_provider_traits(rp_uuid) |
| 229 | + self.assertIn(trait, rp_traits) |
| 230 | + |
| 231 | + # Try creating a server which should fail because nothing is available. |
| 232 | + networks = [{'port': self.neutron.port_1['id']}] |
| 233 | + server_req = self._build_minimal_create_server_request( |
| 234 | + self.api, 'test_compute_status_filter', |
| 235 | + image_uuid=fake_image.get_valid_image_id(), networks=networks) |
| 236 | + server = self.api.post_server({'server': server_req}) |
| 237 | + server = self._wait_for_state_change(self.api, server, 'ERROR') |
| 238 | + # There should be a NoValidHost fault recorded. |
| 239 | + self.assertIn('fault', server) |
| 240 | + self.assertIn('No valid host', server['fault']['message']) |
| 241 | + |
| 242 | + # Now enable the service and the trait should be gone. |
| 243 | + self._update_service(service, disabled=False) |
| 244 | + rp_traits = self._get_provider_traits(rp_uuid) |
| 245 | + self.assertNotIn(trait, rp_traits) |
| 246 | + |
| 247 | + # Try creating another server and it should be OK. |
| 248 | + server = self.api.post_server({'server': server_req}) |
| 249 | + self._wait_for_state_change(self.api, server, 'ACTIVE') |
| 250 | + |
| 251 | + # Stop, force-down and disable the service so the API cannot call |
| 252 | + # the compute service to sync the trait. |
| 253 | + compute.stop() |
| 254 | + self._update_service(service, disabled=True, forced_down=True) |
| 255 | + # The API should have logged a message about the service being down. |
| 256 | + self.assertIn('Compute service on host host1 is down. The ' |
| 257 | + 'COMPUTE_STATUS_DISABLED trait will be synchronized ' |
| 258 | + 'when the service is restarted.', |
| 259 | + self.stdlog.logger.output) |
| 260 | + # The trait should not be on the provider even though the node is |
| 261 | + # disabled. |
| 262 | + rp_traits = self._get_provider_traits(rp_uuid) |
| 263 | + self.assertNotIn(trait, rp_traits) |
| 264 | + # Restart the compute service which should sync and set the trait on |
| 265 | + # the provider in placement. |
| 266 | + self.restart_compute_service(compute) |
| 267 | + rp_traits = self._get_provider_traits(rp_uuid) |
| 268 | + self.assertIn(trait, rp_traits) |
| 269 | + |
| 270 | + |
| 271 | +class ComputeStatusFilterTest211(ComputeStatusFilterTest): |
| 272 | + """Extends ComputeStatusFilterTest and uses the 2.11 API for the |
| 273 | + legacy os-services disable/enable/force-down API behavior |
| 274 | + """ |
| 275 | + microversion = '2.11' |
| 276 | + |
| 277 | + def _update_service(self, service, disabled, forced_down=None): |
| 278 | + """Update the service using the 2.11 request schema. |
| 279 | +
|
| 280 | + :param service: dict representing the service resource in the API |
| 281 | + :param disabled: True if the service should be disabled, False if the |
| 282 | + service should be enabled |
| 283 | + :param forced_down: Optionally change the forced_down value. |
| 284 | + """ |
| 285 | + # Before 2.53 the service is uniquely identified by host and binary. |
| 286 | + body = { |
| 287 | + 'host': service['host'], |
| 288 | + 'binary': service['binary'] |
| 289 | + } |
| 290 | + # Handle forced_down first if provided since the enable/disable |
| 291 | + # behavior in the API depends on it. |
| 292 | + if forced_down is not None: |
| 293 | + body['forced_down'] = forced_down |
| 294 | + self.admin_api.api_put('/os-services/force-down', body) |
| 295 | + |
| 296 | + if disabled: |
| 297 | + self.admin_api.api_put('/os-services/disable', body) |
| 298 | + else: |
| 299 | + self.admin_api.api_put('/os-services/enable', body) |
| 300 | + |
| 301 | + def _get_provider_uuid_by_host(self, host): |
| 302 | + # We have to temporarily mutate to 2.53 to get the hypervisor UUID. |
| 303 | + with utils.temporary_mutation(self.admin_api, microversion='2.53'): |
| 304 | + return super(ComputeStatusFilterTest211, |
| 305 | + self)._get_provider_uuid_by_host(host) |
0 commit comments