# Copyright 2019 Intel, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from cyborg.common import exception from cyborg.common.i18n import _ from cyborg.common import utils from oslo_log import log as logging LOG = logging.getLogger(__name__) class NovaAPI(object): def __init__(self): self.nova_client = utils.get_sdk_adapter('compute') self.nova_client.default_microversion = '2.82' def _get_acc_changed_events(self, instance_uuid, arq_bind_statuses): return [{'name': 'accelerator-request-bound', 'server_uuid': instance_uuid, 'tag': arq_uuid, 'status': arq_bind_status, } for (arq_uuid, arq_bind_status) in arq_bind_statuses] def _send_events(self, events): """Send events to Nova external events API. :param events: List of events to send to Nova. :raises: exception.InvalidAPIResponse, on unexpected error """ url = "/os-server-external-events" body = {"events": events} response = self.nova_client.post(url, json=body) # NOTE(Sundar): Response status should always be 200/207. See # https://review.opendev.org/#/c/698037/ if response.status_code == 200: LOG.info("Sucessfully sent events to Nova, events: %(events)s", {"events": events}) elif response.status_code == 207: # NOTE(Sundar): If Nova returns per-event code of 422, that # is due to a race condition where Nova has not associated # the instance with a host yet. See # https://bugs.launchpad.net/nova/+bug/1855752 events = [ev for ev in response.json()['events']] event_codes = {ev['code'] for ev in events} if len(event_codes) == 1: # all events have same event code if event_codes == {422}: LOG.info('Ignoring Nova notification error that the ' 'instance %s is not yet associated with a host.', events[0]['server_uuid']) else: msg = _('Unexpected event code %(code)s ' 'for instance %(inst)s') msg = msg % {'code': event_codes[0], 'inst': events[0]["server_uuid"]} raise exception.InvalidAPIResponse( service='Nova', api=url[1:], msg=msg) else: msg = _('All event responses are expected to ' 'have the same event code. Instance: %(inst)s') msg = msg % {'inst': events[0]['server_uuid']} raise exception.InvalidAPIResponse( service='Nova', api=url[1:], msg=msg) else: # Unexpected return code from Nova msg = _('Failed to send events %(ev)s: HTTP %(code)s: %(txt)s') msg = msg % {'ev': events, 'code': response.status_code, 'txt': response.text} raise exception.InvalidAPIResponse( service='Nova', api=url[1:], msg=msg) def notify_binding(self, instance_uuid, arq_bind_statuses): """Notify Nova that ARQ bindings are resolved for a given instance. :param instance_uuid: UUID of the instance whose ARQs are resolved :param arq_bind_statuses: List of (arq_uuid, arq_bind_status) tuples :returns: None """ events = self._get_acc_changed_events(instance_uuid, arq_bind_statuses) self._send_events(events)