예제 #1
0
def _delete_inventory_from_provider(conn, rp, to_delete):
    """Deletes any inventory records from the supplied provider and set() of
    resource class identifiers.

    If there are allocations for any of the inventories to be deleted raise
    InventoryInUse exception.

    :param conn: DB connection to use.
    :param rp: Resource provider from which to delete inventory.
    :param to_delete: set() containing resource class IDs for records to
                      delete.
    """
    allocation_query = sa.select(
        [_ALLOC_TBL.c.resource_class_id.label('resource_class')]).where(
             sa.and_(_ALLOC_TBL.c.resource_provider_id == rp.id,
                     _ALLOC_TBL.c.resource_class_id.in_(to_delete))
         ).group_by(_ALLOC_TBL.c.resource_class_id)
    allocations = conn.execute(allocation_query).fetchall()
    if allocations:
        resource_classes = ', '.join([_RC_CACHE.string_from_id(alloc[0])
                                      for alloc in allocations])
        raise exception.InventoryInUse(resource_classes=resource_classes,
                                       resource_provider=rp.uuid)

    del_stmt = _INV_TBL.delete().where(sa.and_(
            _INV_TBL.c.resource_provider_id == rp.id,
            _INV_TBL.c.resource_class_id.in_(to_delete)))
    res = conn.execute(del_stmt)
    return res.rowcount
예제 #2
0
    def test_delete_inventory_inventory_in_use(self, mock_get, mock_put,
                                               mock_warn):
        cn = self.compute_node
        rp = objects.ResourceProvider(uuid=cn.uuid, generation=42)
        # Make sure the ResourceProvider exists for preventing to call the API
        self.client._resource_providers[cn.uuid] = rp

        mock_get.return_value.json.return_value = {
            'resource_provider_generation': 1,
            'inventories': {
                'VCPU': {
                    'total': 16
                },
                'MEMORY_MB': {
                    'total': 1024
                },
                'DISK_GB': {
                    'total': 10
                },
            }
        }
        mock_put.return_value.status_code = 409
        rc_str = "VCPU, MEMORY_MB"
        in_use_exc = exception.InventoryInUse(
            resource_classes=rc_str,
            resource_provider=cn.uuid,
        )
        fault_text = """
409 Conflict

There was a conflict when trying to complete your request.

 update conflict: %s
 """ % six.text_type(in_use_exc)
        mock_put.return_value.text = fault_text
        mock_put.return_value.json.return_value = {
            'resource_provider_generation': 44,
            'inventories': {}
        }
        result = self.client._delete_inventory(cn.uuid)
        self.assertIsNone(result)
        self.assertTrue(mock_warn.called)
예제 #3
0
    def _update_inventory_attempt(self, rp_uuid, inv_data):
        """Update the inventory for this resource provider if needed.

        :param rp_uuid: The resource provider UUID for the operation
        :param inv_data: The new inventory for the resource provider
        :returns: True if the inventory was updated (or did not need to be),
                  False otherwise.
        """
        curr = self._get_inventory_and_update_provider_generation(rp_uuid)

        # Check to see if we need to update placement's view
        if inv_data == curr.get('inventories', {}):
            return True

        cur_rp_gen = self._resource_providers[rp_uuid]['generation']
        payload = {
            'resource_provider_generation': cur_rp_gen,
            'inventories': inv_data,
        }
        url = '/resource_providers/%s/inventories' % rp_uuid
        result = self.put(url, payload)
        if result.status_code == 409:
            LOG.info(
                _LI('[%(placement_req_id)s] Inventory update conflict '
                    'for %(resource_provider_uuid)s with generation ID '
                    '%(generation_id)s'), {
                        'placement_req_id': get_placement_request_id(result),
                        'resource_provider_uuid': rp_uuid,
                        'generation_id': cur_rp_gen
                    })
            # NOTE(jaypipes): There may be cases when we try to set a
            # provider's inventory that results in attempting to delete an
            # inventory record for a resource class that has an active
            # allocation. We need to catch this particular case and raise an
            # exception here instead of returning False, since we should not
            # re-try the operation in this case.
            #
            # A use case for where this can occur is the following:
            #
            # 1) Provider created for each Ironic baremetal node in Newton
            # 2) Inventory records for baremetal node created for VCPU,
            #    MEMORY_MB and DISK_GB
            # 3) A Nova instance consumes the baremetal node and allocation
            #    records are created for VCPU, MEMORY_MB and DISK_GB matching
            #    the total amount of those resource on the baremetal node.
            # 3) Upgrade to Ocata and now resource tracker wants to set the
            #    provider's inventory to a single record of resource class
            #    CUSTOM_IRON_SILVER (or whatever the Ironic node's
            #    "resource_class" attribute is)
            # 4) Scheduler report client sends the inventory list containing a
            #    single CUSTOM_IRON_SILVER record and placement service
            #    attempts to delete the inventory records for VCPU, MEMORY_MB
            #    and DISK_GB. An exception is raised from the placement service
            #    because allocation records exist for those resource classes,
            #    and a 409 Conflict is returned to the compute node. We need to
            #    trigger a delete of the old allocation records and then set
            #    the new inventory, and then set the allocation record to the
            #    new CUSTOM_IRON_SILVER record.
            match = _RE_INV_IN_USE.search(result.text)
            if match:
                rc = match.group(1)
                raise exception.InventoryInUse(
                    resource_classes=rc,
                    resource_provider=rp_uuid,
                )

            # Invalidate our cache and re-fetch the resource provider
            # to be sure to get the latest generation.
            del self._resource_providers[rp_uuid]
            # NOTE(jaypipes): We don't need to pass a name parameter to
            # _ensure_resource_provider() because we know the resource provider
            # record already exists. We're just reloading the record here.
            self._ensure_resource_provider(rp_uuid)
            return False
        elif not result:
            placement_req_id = get_placement_request_id(result)
            LOG.warning(
                _LW('[%(placement_req_id)s] Failed to update '
                    'inventory for resource provider '
                    '%(uuid)s: %(status)i %(text)s'), {
                        'placement_req_id': placement_req_id,
                        'uuid': rp_uuid,
                        'status': result.status_code,
                        'text': result.text
                    })
            # log the body at debug level
            LOG.debug(
                '[%(placement_req_id)s] Failed inventory update request '
                'for resource provider %(uuid)s with body: %(payload)s', {
                    'placement_req_id': placement_req_id,
                    'uuid': rp_uuid,
                    'payload': payload
                })
            return False

        if result.status_code != 200:
            placement_req_id = get_placement_request_id(result)
            LOG.info(
                _LI('[%(placement_req_id)s] Received unexpected response code '
                    '%(code)i while trying to update inventory for resource '
                    'provider %(uuid)s: %(text)s'), {
                        'placement_req_id': placement_req_id,
                        'uuid': rp_uuid,
                        'code': result.status_code,
                        'text': result.text
                    })
            return False

        # Update our view of the generation for next time
        updated_inventories_result = result.json()
        new_gen = updated_inventories_result['resource_provider_generation']

        self._resource_providers[rp_uuid]['generation'] = new_gen
        LOG.debug('Updated inventory for %s at generation %i', rp_uuid,
                  new_gen)
        return True