Пример #1
0
    def test_create(self, mock_ensure_cache):
        rp = resource_provider.ResourceProvider(context=self.context,
                                                uuid=_RESOURCE_PROVIDER_UUID,
                                                name=_RESOURCE_PROVIDER_NAME)
        rp.create()
        inv = resource_provider.Inventory(context=self.context,
                                          resource_provider=rp,
                                          resource_class=_RESOURCE_CLASS_NAME,
                                          total=16,
                                          reserved=2,
                                          min_unit=1,
                                          max_unit=8,
                                          step_size=1,
                                          allocation_ratio=1.0)
        inv_list = resource_provider.InventoryList(context=self.context,
                                                   objects=[inv])
        rp.set_inventory(inv_list)
        obj = resource_provider.Allocation(context=self.context,
                                           resource_provider=rp,
                                           resource_class=_RESOURCE_CLASS_NAME,
                                           consumer_id=uuids.fake_instance,
                                           used=8)
        alloc_list = resource_provider.AllocationList(self.context,
                                                      objects=[obj])
        alloc_list.create_all()

        rp_al = resource_provider.AllocationList
        saved_allocations = rp_al.get_all_by_resource_provider(
            self.context, rp)
        self.assertEqual(1, len(saved_allocations))
        self.assertEqual(obj.used, saved_allocations[0].used)
Пример #2
0
 def test_create_with_id_fails(self):
     rp = resource_provider.ResourceProvider(context=self.context,
                                             uuid=_RESOURCE_PROVIDER_UUID,
                                             name=_RESOURCE_PROVIDER_NAME)
     rp.create()
     inv = resource_provider.Inventory(context=self.context,
                                       resource_provider=rp,
                                       resource_class=_RESOURCE_CLASS_NAME,
                                       total=16,
                                       reserved=2,
                                       min_unit=1,
                                       max_unit=8,
                                       step_size=1,
                                       allocation_ratio=1.0)
     inv_list = resource_provider.InventoryList(context=self.context,
                                                objects=[inv])
     rp.set_inventory(inv_list)
     obj = resource_provider.Allocation(context=self.context,
                                        id=99,
                                        resource_provider=rp,
                                        resource_class=_RESOURCE_CLASS_NAME,
                                        consumer_id=uuids.fake_instance,
                                        used=8)
     alloc_list = resource_provider.AllocationList(self.context,
                                                   objects=[obj])
     self.assertRaises(exception.ObjectActionError, alloc_list.create_all)
Пример #3
0
def create_allocation_list(context, data, consumers):
    """Create an AllocationList based on provided data.

    :param context: The placement context.
    :param data: A dictionary of multiple allocations by consumer uuid.
    :param consumers: A dictionary, keyed by consumer UUID, of Consumer objects
    :return: An AllocationList.
    :raises: `webob.exc.HTTPBadRequest` if a resource provider included in the
             allocations does not exist.
    """
    allocation_objects = []

    for consumer_uuid in data:
        allocations = data[consumer_uuid]['allocations']
        consumer = consumers[consumer_uuid]
        if allocations:
            rp_objs = _resource_providers_by_uuid(context, allocations.keys())
            for resource_provider_uuid in allocations:
                resource_provider = rp_objs[resource_provider_uuid]
                resources = allocations[resource_provider_uuid]['resources']
                new_allocations = _new_allocations(context, resource_provider,
                                                   consumer, resources)
                allocation_objects.extend(new_allocations)
        else:
            # The allocations are empty, which means wipe them out.
            # Internal to the allocation object this is signalled by a
            # used value of 0.
            allocations = rp_obj.AllocationList.get_all_by_consumer_id(
                context, consumer_uuid)
            for allocation in allocations:
                allocation.used = 0
                allocation_objects.append(allocation)

    return rp_obj.AllocationList(context, objects=allocation_objects)
Пример #4
0
def _set_allocations_for_consumer(req, schema):
    context = req.environ['placement.context']
    consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid')
    data = util.extract_json(req.body, schema)
    allocation_data = data['allocations']

    # Normalize allocation data to dict.
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if not want_version.matches((1, 12)):
        allocations_dict = {}
        # Allocation are list-ish, transform to dict-ish
        for allocation in allocation_data:
            resource_provider_uuid = allocation['resource_provider']['uuid']
            allocations_dict[resource_provider_uuid] = {
                'resources': allocation['resources']
            }
        allocation_data = allocations_dict

    # If the body includes an allocation for a resource provider
    # that does not exist, raise a 400.
    allocation_objects = []
    for resource_provider_uuid, allocation in allocation_data.items():
        new_allocations = _new_allocations(context, resource_provider_uuid,
                                           consumer_uuid,
                                           allocation['resources'],
                                           data.get('project_id'),
                                           data.get('user_id'))
        allocation_objects.extend(new_allocations)

    allocations = rp_obj.AllocationList(context, objects=allocation_objects)

    try:
        allocations.create_all()
        LOG.debug("Successfully wrote allocations %s", allocations)
    # InvalidInventory is a parent for several exceptions that
    # indicate either that Inventory is not present, or that
    # capacity limits have been exceeded.
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest(
            _("Unable to allocate inventory for consumer "
              "%(consumer_uuid)s: %(error)s") % {
                  'consumer_uuid': consumer_uuid,
                  'error': exc
              })
    except exception.InvalidInventory as exc:
        raise webob.exc.HTTPConflict(
            _('Unable to allocate inventory: %(error)s') % {'error': exc})
    except exception.ConcurrentUpdateDetected as exc:
        raise webob.exc.HTTPConflict(
            _('Inventory changed while attempting to allocate: %(error)s') %
            {'error': exc})

    req.response.status = 204
    req.response.content_type = None
    return req.response
Пример #5
0
def set_allocations(req):
    context = req.environ['placement.context']
    data = util.extract_json(req.body, schema.POST_ALLOCATIONS_V1_13)

    # Create a sequence of allocation objects to be used in an
    # AllocationList.create_all() call, which will mean all the changes
    # happen within a single transaction and with resource provider
    # generations check all in one go.
    allocation_objects = []

    for consumer_uuid in data:
        project_id = data[consumer_uuid]['project_id']
        user_id = data[consumer_uuid]['user_id']
        allocations = data[consumer_uuid]['allocations']
        if allocations:
            for resource_provider_uuid in allocations:
                resources = allocations[resource_provider_uuid]['resources']
                new_allocations = _new_allocations(context,
                                                   resource_provider_uuid,
                                                   consumer_uuid, resources,
                                                   project_id, user_id)
                allocation_objects.extend(new_allocations)
        else:
            # The allocations are empty, which means wipe them out.
            # Internal to the allocation object this is signalled by a
            # used value of 0.
            allocations = rp_obj.AllocationList.get_all_by_consumer_id(
                context, consumer_uuid)
            for allocation in allocations:
                allocation.used = 0
                allocation_objects.append(allocation)

    allocations = rp_obj.AllocationList(context, objects=allocation_objects)

    try:
        allocations.create_all()
        LOG.debug("Successfully wrote allocations %s", allocations)
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest(
            _("Unable to allocate inventory %(error)s") % {'error': exc})
    except exception.InvalidInventory as exc:
        # InvalidInventory is a parent for several exceptions that
        # indicate either that Inventory is not present, or that
        # capacity limits have been exceeded.
        raise webob.exc.HTTPConflict(
            _('Unable to allocate inventory: %(error)s') % {'error': exc})
    except exception.ConcurrentUpdateDetected as exc:
        raise webob.exc.HTTPConflict(
            _('Inventory changed while attempting to allocate: %(error)s') %
            {'error': exc})

    req.response.status = 204
    req.response.content_type = None
    return req.response
Пример #6
0
def set_allocation(ctx, rp, consumer, rc_used_dict):
    alloc = [
        rp_obj.Allocation(ctx,
                          resource_provider=rp,
                          resource_class=rc,
                          consumer=consumer,
                          used=used) for rc, used in rc_used_dict.items()
    ]
    alloc_list = rp_obj.AllocationList(ctx, objects=alloc)
    alloc_list.replace_all()
    return alloc_list
Пример #7
0
 def _make_allocation(self, inv_dict, alloc_dict):
     rp = self._create_provider('allocation_resource_provider')
     disk_inv = rp_obj.Inventory(context=self.ctx,
                                 resource_provider=rp,
                                 **inv_dict)
     inv_list = rp_obj.InventoryList(objects=[disk_inv])
     rp.set_inventory(inv_list)
     consumer_id = alloc_dict['consumer_id']
     consumer = ensure_consumer(self.ctx, self.user_obj, self.project_obj,
                                consumer_id)
     alloc = rp_obj.Allocation(self.ctx,
                               resource_provider=rp,
                               consumer=consumer,
                               **alloc_dict)
     alloc_list = rp_obj.AllocationList(self.ctx, objects=[alloc])
     alloc_list.replace_all()
     return rp, alloc
Пример #8
0
    def test_reshape(self):
        """We set up the following scenario:

        BEFORE: single compute node setup

          A single compute node with:
            - VCPU, MEMORY_MB, DISK_GB inventory
            - Two instances consuming CPU, RAM and DISK from that compute node

        AFTER: hierarchical + shared storage setup

          A compute node parent provider with:
            - MEMORY_MB
          Two NUMA node child providers containing:
            - VCPU
          Shared storage provider with:
            - DISK_GB
          Both instances have their resources split among the providers and
          shared storage accordingly
        """
        # First create our consumers
        i1_uuid = uuids.instance1
        i1_consumer = consumer_obj.Consumer(
            self.ctx, uuid=i1_uuid, user=self.user_obj,
            project=self.project_obj)
        i1_consumer.create()

        i2_uuid = uuids.instance2
        i2_consumer = consumer_obj.Consumer(
            self.ctx, uuid=i2_uuid, user=self.user_obj,
            project=self.project_obj)
        i2_consumer.create()

        cn1 = self._create_provider('cn1')
        tb.add_inventory(cn1, 'VCPU', 16)
        tb.add_inventory(cn1, 'MEMORY_MB', 32768)
        tb.add_inventory(cn1, 'DISK_GB', 1000)

        # Allocate both instances against the single compute node
        for consumer in (i1_consumer, i2_consumer):
            allocs = [
                rp_obj.Allocation(
                    self.ctx, resource_provider=cn1,
                    resource_class='VCPU', consumer=consumer, used=2),
                rp_obj.Allocation(
                    self.ctx, resource_provider=cn1,
                    resource_class='MEMORY_MB', consumer=consumer, used=1024),
                rp_obj.Allocation(
                    self.ctx, resource_provider=cn1,
                    resource_class='DISK_GB', consumer=consumer, used=100),
            ]
            alloc_list = rp_obj.AllocationList(self.ctx, objects=allocs)
            alloc_list.replace_all()

        # Verify we have the allocations we expect for the BEFORE scenario
        before_allocs_i1 = rp_obj.AllocationList.get_all_by_consumer_id(
            self.ctx, i1_uuid)
        self.assertEqual(3, len(before_allocs_i1))
        self.assertEqual(cn1.uuid, before_allocs_i1[0].resource_provider.uuid)
        before_allocs_i2 = rp_obj.AllocationList.get_all_by_consumer_id(
            self.ctx, i2_uuid)
        self.assertEqual(3, len(before_allocs_i2))
        self.assertEqual(cn1.uuid, before_allocs_i2[2].resource_provider.uuid)

        # Before we issue the actual reshape() call, we need to first create
        # the child providers and sharing storage provider. These are actions
        # that the virt driver or external agent is responsible for performing
        # *before* attempting any reshape activity.
        cn1_numa0 = self._create_provider('cn1_numa0', parent=cn1.uuid)
        cn1_numa1 = self._create_provider('cn1_numa1', parent=cn1.uuid)
        ss = self._create_provider('ss')

        # OK, now emulate the call to POST /reshaper that will be triggered by
        # a virt driver wanting to replace the world and change its modeling
        # from a single provider to a nested provider tree along with a sharing
        # storage provider.
        after_inventories = {
            # cn1 keeps the RAM only
            cn1.uuid: rp_obj.InventoryList(self.ctx, objects=[
                rp_obj.Inventory(
                    self.ctx, resource_provider=cn1,
                    resource_class='MEMORY_MB', total=32768, reserved=0,
                    max_unit=32768, min_unit=1, step_size=1,
                    allocation_ratio=1.0),
            ]),
            # each NUMA node gets half of the CPUs
            cn1_numa0.uuid: rp_obj.InventoryList(self.ctx, objects=[
                rp_obj.Inventory(
                    self.ctx, resource_provider=cn1_numa0,
                    resource_class='VCPU', total=8, reserved=0,
                    max_unit=8, min_unit=1, step_size=1,
                    allocation_ratio=1.0),
            ]),
            cn1_numa1.uuid: rp_obj.InventoryList(self.ctx, objects=[
                rp_obj.Inventory(
                    self.ctx, resource_provider=cn1_numa1,
                    resource_class='VCPU', total=8, reserved=0,
                    max_unit=8, min_unit=1, step_size=1,
                    allocation_ratio=1.0),
            ]),
            # The sharing provider gets a bunch of disk
            ss.uuid: rp_obj.InventoryList(self.ctx, objects=[
                rp_obj.Inventory(
                    self.ctx, resource_provider=ss,
                    resource_class='DISK_GB', total=100000, reserved=0,
                    max_unit=1000, min_unit=1, step_size=1,
                    allocation_ratio=1.0),
            ]),
        }
        # We do a fetch from the DB for each instance to get its latest
        # generation. This would be done by the resource tracker or scheduler
        # report client before issuing the call to reshape() because the
        # consumers representing the two instances above will have had their
        # generations incremented in the original call to PUT
        # /allocations/{consumer_uuid}
        i1_consumer = consumer_obj.Consumer.get_by_uuid(self.ctx, i1_uuid)
        i2_consumer = consumer_obj.Consumer.get_by_uuid(self.ctx, i2_uuid)
        after_allocs = rp_obj.AllocationList(self.ctx, objects=[
            # instance1 gets VCPU from NUMA0, MEMORY_MB from cn1 and DISK_GB
            # from the sharing storage provider
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1_numa0, resource_class='VCPU',
                consumer=i1_consumer, used=2),
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1, resource_class='MEMORY_MB',
                consumer=i1_consumer, used=1024),
            rp_obj.Allocation(
                self.ctx, resource_provider=ss, resource_class='DISK_GB',
                consumer=i1_consumer, used=100),
            # instance2 gets VCPU from NUMA1, MEMORY_MB from cn1 and DISK_GB
            # from the sharing storage provider
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1_numa1, resource_class='VCPU',
                consumer=i2_consumer, used=2),
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1, resource_class='MEMORY_MB',
                consumer=i2_consumer, used=1024),
            rp_obj.Allocation(
                self.ctx, resource_provider=ss, resource_class='DISK_GB',
                consumer=i2_consumer, used=100),
        ])
        rp_obj.reshape(self.ctx, after_inventories, after_allocs)

        # Verify that the inventories have been moved to the appropriate
        # providers in the AFTER scenario

        # The root compute node should only have MEMORY_MB, nothing else
        cn1_inv = rp_obj.InventoryList.get_all_by_resource_provider(
            self.ctx, cn1)
        self.assertEqual(1, len(cn1_inv))
        self.assertEqual('MEMORY_MB', cn1_inv[0].resource_class)
        self.assertEqual(32768, cn1_inv[0].total)
        # Each NUMA node should only have half the original VCPU, nothing else
        numa0_inv = rp_obj.InventoryList.get_all_by_resource_provider(
            self.ctx, cn1_numa0)
        self.assertEqual(1, len(numa0_inv))
        self.assertEqual('VCPU', numa0_inv[0].resource_class)
        self.assertEqual(8, numa0_inv[0].total)
        numa1_inv = rp_obj.InventoryList.get_all_by_resource_provider(
            self.ctx, cn1_numa1)
        self.assertEqual(1, len(numa1_inv))
        self.assertEqual('VCPU', numa1_inv[0].resource_class)
        self.assertEqual(8, numa1_inv[0].total)
        # The sharing storage provider should only have DISK_GB, nothing else
        ss_inv = rp_obj.InventoryList.get_all_by_resource_provider(
            self.ctx, ss)
        self.assertEqual(1, len(ss_inv))
        self.assertEqual('DISK_GB', ss_inv[0].resource_class)
        self.assertEqual(100000, ss_inv[0].total)

        # Verify we have the allocations we expect for the AFTER scenario
        after_allocs_i1 = rp_obj.AllocationList.get_all_by_consumer_id(
            self.ctx, i1_uuid)
        self.assertEqual(3, len(after_allocs_i1))
        # Our VCPU allocation should be in the NUMA0 node
        vcpu_alloc = alloc_for_rc(after_allocs_i1, 'VCPU')
        self.assertIsNotNone(vcpu_alloc)
        self.assertEqual(cn1_numa0.uuid, vcpu_alloc.resource_provider.uuid)
        # Our DISK_GB allocation should be in the sharing provider
        disk_alloc = alloc_for_rc(after_allocs_i1, 'DISK_GB')
        self.assertIsNotNone(disk_alloc)
        self.assertEqual(ss.uuid, disk_alloc.resource_provider.uuid)
        # And our MEMORY_MB should remain on the root compute node
        ram_alloc = alloc_for_rc(after_allocs_i1, 'MEMORY_MB')
        self.assertIsNotNone(ram_alloc)
        self.assertEqual(cn1.uuid, ram_alloc.resource_provider.uuid)

        after_allocs_i2 = rp_obj.AllocationList.get_all_by_consumer_id(
            self.ctx, i2_uuid)
        self.assertEqual(3, len(after_allocs_i2))
        # Our VCPU allocation should be in the NUMA1 node
        vcpu_alloc = alloc_for_rc(after_allocs_i2, 'VCPU')
        self.assertIsNotNone(vcpu_alloc)
        self.assertEqual(cn1_numa1.uuid, vcpu_alloc.resource_provider.uuid)
        # Our DISK_GB allocation should be in the sharing provider
        disk_alloc = alloc_for_rc(after_allocs_i2, 'DISK_GB')
        self.assertIsNotNone(disk_alloc)
        self.assertEqual(ss.uuid, disk_alloc.resource_provider.uuid)
        # And our MEMORY_MB should remain on the root compute node
        ram_alloc = alloc_for_rc(after_allocs_i2, 'MEMORY_MB')
        self.assertIsNotNone(ram_alloc)
        self.assertEqual(cn1.uuid, ram_alloc.resource_provider.uuid)
Пример #9
0
    def test_reshape_concurrent_inventory_update(self):
        """Valid failure scenario for reshape(). We test a situation where the
        virt driver has constructed it's "after inventories and allocations"
        and sent those to the POST /reshape endpoint. The reshape POST handler
        does a quick check of the resource provider generations sent in the
        payload and they all check out.

        However, right before the call to resource_provider.reshape(), another
        thread legitimately changes the inventory of one of the providers
        involved in the reshape transaction. We should get a
        ConcurrentUpdateDetected in this case.
        """
        # First create our consumers
        i1_uuid = uuids.instance1
        i1_consumer = consumer_obj.Consumer(
            self.ctx, uuid=i1_uuid, user=self.user_obj,
            project=self.project_obj)
        i1_consumer.create()

        # then all our original providers
        cn1 = self._create_provider('cn1')
        tb.add_inventory(cn1, 'VCPU', 16)
        tb.add_inventory(cn1, 'MEMORY_MB', 32768)
        tb.add_inventory(cn1, 'DISK_GB', 1000)

        # Allocate an instance on our compute node
        allocs = [
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1,
                resource_class='VCPU', consumer=i1_consumer, used=2),
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1,
                resource_class='MEMORY_MB', consumer=i1_consumer, used=1024),
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1,
                resource_class='DISK_GB', consumer=i1_consumer, used=100),
        ]
        alloc_list = rp_obj.AllocationList(self.ctx, objects=allocs)
        alloc_list.replace_all()

        # Before we issue the actual reshape() call, we need to first create
        # the child providers and sharing storage provider. These are actions
        # that the virt driver or external agent is responsible for performing
        # *before* attempting any reshape activity.
        cn1_numa0 = self._create_provider('cn1_numa0', parent=cn1.uuid)
        cn1_numa1 = self._create_provider('cn1_numa1', parent=cn1.uuid)
        ss = self._create_provider('ss')

        # OK, now emulate the call to POST /reshaper that will be triggered by
        # a virt driver wanting to replace the world and change its modeling
        # from a single provider to a nested provider tree along with a sharing
        # storage provider.
        after_inventories = {
            # cn1 keeps the RAM only
            cn1.uuid: rp_obj.InventoryList(self.ctx, objects=[
                rp_obj.Inventory(
                    self.ctx, resource_provider=cn1,
                    resource_class='MEMORY_MB', total=32768, reserved=0,
                    max_unit=32768, min_unit=1, step_size=1,
                    allocation_ratio=1.0),
            ]),
            # each NUMA node gets half of the CPUs
            cn1_numa0.uuid: rp_obj.InventoryList(self.ctx, objects=[
                rp_obj.Inventory(
                    self.ctx, resource_provider=cn1_numa0,
                    resource_class='VCPU', total=8, reserved=0,
                    max_unit=8, min_unit=1, step_size=1,
                    allocation_ratio=1.0),
            ]),
            cn1_numa1.uuid: rp_obj.InventoryList(self.ctx, objects=[
                rp_obj.Inventory(
                    self.ctx, resource_provider=cn1_numa1,
                    resource_class='VCPU', total=8, reserved=0,
                    max_unit=8, min_unit=1, step_size=1,
                    allocation_ratio=1.0),
            ]),
            # The sharing provider gets a bunch of disk
            ss.uuid: rp_obj.InventoryList(self.ctx, objects=[
                rp_obj.Inventory(
                    self.ctx, resource_provider=ss,
                    resource_class='DISK_GB', total=100000, reserved=0,
                    max_unit=1000, min_unit=1, step_size=1,
                    allocation_ratio=1.0),
            ]),
        }
        # We do a fetch from the DB for each instance to get its latest
        # generation. This would be done by the resource tracker or scheduler
        # report client before issuing the call to reshape() because the
        # consumers representing the two instances above will have had their
        # generations incremented in the original call to PUT
        # /allocations/{consumer_uuid}
        i1_consumer = consumer_obj.Consumer.get_by_uuid(self.ctx, i1_uuid)
        after_allocs = rp_obj.AllocationList(self.ctx, objects=[
            # instance1 gets VCPU from NUMA0, MEMORY_MB from cn1 and DISK_GB
            # from the sharing storage provider
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1_numa0, resource_class='VCPU',
                consumer=i1_consumer, used=2),
            rp_obj.Allocation(
                self.ctx, resource_provider=cn1, resource_class='MEMORY_MB',
                consumer=i1_consumer, used=1024),
            rp_obj.Allocation(
                self.ctx, resource_provider=ss, resource_class='DISK_GB',
                consumer=i1_consumer, used=100),
        ])

        # OK, now before we call reshape(), here we emulate another thread
        # changing the inventory for the sharing storage provider in between
        # the time in the REST handler when the sharing storage provider's
        # generation was validated and the actual call to reshape()
        ss_threadB = rp_obj.ResourceProvider.get_by_uuid(self.ctx, ss.uuid)
        # Reduce the amount of storage to 2000, from 100000.
        new_ss_inv = rp_obj.InventoryList(self.ctx, objects=[
            rp_obj.Inventory(
                self.ctx, resource_provider=ss_threadB,
                resource_class='DISK_GB', total=2000, reserved=0,
                    max_unit=1000, min_unit=1, step_size=1,
                    allocation_ratio=1.0)])
        ss_threadB.set_inventory(new_ss_inv)
        # Double check our storage provider's generation is now greater than
        # the original storage provider record being sent to reshape()
        self.assertGreater(ss_threadB.generation, ss.generation)

        # And we should legitimately get a failure now to reshape() due to
        # another thread updating one of the involved provider's generations
        self.assertRaises(
            exception.ConcurrentUpdateDetected,
            rp_obj.reshape, self.ctx, after_inventories, after_allocs)
Пример #10
0
    def test_delete_consumer_if_no_allocs(self):
        """AllocationList.replace_all() should attempt to delete consumers that
        no longer have any allocations. Due to the REST API not having any way
        to query for consumers directly (only via the GET
        /allocations/{consumer_uuid} endpoint which returns an empty dict even
        when no consumer record exists for the {consumer_uuid}) we need to do
        this functional test using only the object layer.
        """
        # We will use two consumers in this test, only one of which will get
        # all of its allocations deleted in a transaction (and we expect that
        # consumer record to be deleted)
        c1 = consumer_obj.Consumer(self.ctx,
                                   uuid=uuids.consumer1,
                                   user=self.user_obj,
                                   project=self.project_obj)
        c1.create()
        c2 = consumer_obj.Consumer(self.ctx,
                                   uuid=uuids.consumer2,
                                   user=self.user_obj,
                                   project=self.project_obj)
        c2.create()

        # Create some inventory that we will allocate
        cn1 = self._create_provider('cn1')
        tb.add_inventory(cn1, fields.ResourceClass.VCPU, 8)
        tb.add_inventory(cn1, fields.ResourceClass.MEMORY_MB, 2048)
        tb.add_inventory(cn1, fields.ResourceClass.DISK_GB, 2000)

        # Now allocate some of that inventory to two different consumers
        allocs = [
            rp_obj.Allocation(self.ctx,
                              consumer=c1,
                              resource_provider=cn1,
                              resource_class=fields.ResourceClass.VCPU,
                              used=1),
            rp_obj.Allocation(self.ctx,
                              consumer=c1,
                              resource_provider=cn1,
                              resource_class=fields.ResourceClass.MEMORY_MB,
                              used=512),
            rp_obj.Allocation(self.ctx,
                              consumer=c2,
                              resource_provider=cn1,
                              resource_class=fields.ResourceClass.VCPU,
                              used=1),
            rp_obj.Allocation(self.ctx,
                              consumer=c2,
                              resource_provider=cn1,
                              resource_class=fields.ResourceClass.MEMORY_MB,
                              used=512),
        ]
        alloc_list = rp_obj.AllocationList(self.ctx, objects=allocs)
        alloc_list.replace_all()

        # Validate that we have consumer records for both consumers
        for c_uuid in (uuids.consumer1, uuids.consumer2):
            c_obj = consumer_obj.Consumer.get_by_uuid(self.ctx, c_uuid)
            self.assertIsNotNone(c_obj)

        # OK, now "remove" the allocation for consumer2 by setting the used
        # value for both allocated resources to 0 and re-running the
        # AllocationList.replace_all(). This should end up deleting the
        # consumer record for consumer2
        allocs = [
            rp_obj.Allocation(self.ctx,
                              consumer=c2,
                              resource_provider=cn1,
                              resource_class=fields.ResourceClass.VCPU,
                              used=0),
            rp_obj.Allocation(self.ctx,
                              consumer=c2,
                              resource_provider=cn1,
                              resource_class=fields.ResourceClass.MEMORY_MB,
                              used=0),
        ]
        alloc_list = rp_obj.AllocationList(self.ctx, objects=allocs)
        alloc_list.replace_all()

        # consumer1 should still exist...
        c_obj = consumer_obj.Consumer.get_by_uuid(self.ctx, uuids.consumer1)
        self.assertIsNotNone(c_obj)

        # but not consumer2...
        self.assertRaises(exception.NotFound,
                          consumer_obj.Consumer.get_by_uuid, self.ctx,
                          uuids.consumer2)

        # DELETE /allocations/{consumer_uuid} is the other place where we
        # delete all allocations for a consumer. Let's delete all for consumer1
        # and check that the consumer record is deleted
        alloc_list = rp_obj.AllocationList.get_all_by_consumer_id(
            self.ctx, uuids.consumer1)
        alloc_list.delete_all()

        # consumer1 should no longer exist in the DB since we just deleted all
        # of its allocations
        self.assertRaises(exception.NotFound,
                          consumer_obj.Consumer.get_by_uuid, self.ctx,
                          uuids.consumer1)
Пример #11
0
def _set_allocations_for_consumer(req, schema):
    context = req.environ['placement.context']
    context.can(policies.ALLOC_UPDATE)
    consumer_uuid = util.wsgi_path_item(req.environ, 'consumer_uuid')
    if not uuidutils.is_uuid_like(consumer_uuid):
        raise webob.exc.HTTPBadRequest(
            _('Malformed consumer_uuid: %(consumer_uuid)s') %
            {'consumer_uuid': consumer_uuid})
    consumer_uuid = str(uuid.UUID(consumer_uuid))
    data = util.extract_json(req.body, schema)
    allocation_data = data['allocations']

    # Normalize allocation data to dict.
    want_version = req.environ[microversion.MICROVERSION_ENVIRON]
    if not want_version.matches((1, 12)):
        allocations_dict = {}
        # Allocation are list-ish, transform to dict-ish
        for allocation in allocation_data:
            resource_provider_uuid = allocation['resource_provider']['uuid']
            allocations_dict[resource_provider_uuid] = {
                'resources': allocation['resources']
            }
        allocation_data = allocations_dict

    allocation_objects = []
    # Consumer object saved in case we need to delete the auto-created consumer
    # record
    consumer = None
    # Whether we created a new consumer record
    created_new_consumer = False
    if not allocation_data:
        # The allocations are empty, which means wipe them out. Internal
        # to the allocation object this is signalled by a used value of 0.
        # We still need to verify the consumer's generation, though, which
        # we do in _ensure_consumer()
        # NOTE(jaypipes): This will only occur 1.28+. The JSONSchema will
        # prevent an empty allocations object from being passed when there is
        # no consumer generation, so this is safe to do.
        util.ensure_consumer(context, consumer_uuid, data.get('project_id'),
             data.get('user_id'), data.get('consumer_generation'),
             want_version)
        allocations = rp_obj.AllocationList.get_all_by_consumer_id(
            context, consumer_uuid)
        for allocation in allocations:
            allocation.used = 0
            allocation_objects.append(allocation)
    else:
        # If the body includes an allocation for a resource provider
        # that does not exist, raise a 400.
        rp_objs = _resource_providers_by_uuid(context, allocation_data.keys())
        consumer, created_new_consumer = util.ensure_consumer(
            context, consumer_uuid, data.get('project_id'),
            data.get('user_id'), data.get('consumer_generation'),
            want_version)
        for resource_provider_uuid, allocation in allocation_data.items():
            resource_provider = rp_objs[resource_provider_uuid]
            new_allocations = _new_allocations(context,
                                               resource_provider,
                                               consumer,
                                               allocation['resources'])
            allocation_objects.extend(new_allocations)

    allocations = rp_obj.AllocationList(
        context, objects=allocation_objects)

    def _create_allocations(alloc_list):
        try:
            alloc_list.replace_all()
            LOG.debug("Successfully wrote allocations %s", alloc_list)
        except Exception:
            if created_new_consumer:
                delete_consumers([consumer])
            raise

    try:
        _create_allocations(allocations)
    # InvalidInventory is a parent for several exceptions that
    # indicate either that Inventory is not present, or that
    # capacity limits have been exceeded.
    except exception.NotFound as exc:
        raise webob.exc.HTTPBadRequest(
                _("Unable to allocate inventory for consumer "
                  "%(consumer_uuid)s: %(error)s") %
            {'consumer_uuid': consumer_uuid, 'error': exc})
    except exception.InvalidInventory as exc:
        raise webob.exc.HTTPConflict(
            _('Unable to allocate inventory: %(error)s') % {'error': exc})
    except exception.ConcurrentUpdateDetected as exc:
        raise webob.exc.HTTPConflict(
            _('Inventory and/or allocations changed while attempting to '
              'allocate: %(error)s') % {'error': exc},
              comment=errors.CONCURRENT_UPDATE)

    req.response.status = 204
    req.response.content_type = None
    return req.response
Пример #12
0
    def start_fixture(self):
        super(AllocationFixture, self).start_fixture()
        self.context = context.get_admin_context()

        # For use creating and querying allocations/usages
        os.environ['ALT_USER_ID'] = uuidutils.generate_uuid()
        project_id = os.environ['PROJECT_ID']
        user_id = os.environ['USER_ID']
        alt_user_id = os.environ['ALT_USER_ID']

        # Stealing from the super
        rp_name = os.environ['RP_NAME']
        rp_uuid = os.environ['RP_UUID']
        rp = rp_obj.ResourceProvider(
            self.context, name=rp_name, uuid=rp_uuid)
        rp.create()

        # Create some DISK_GB inventory and allocations.
        consumer_id = uuidutils.generate_uuid()
        inventory = rp_obj.Inventory(
            self.context, resource_provider=rp,
            resource_class='DISK_GB', total=2048,
            step_size=10, min_unit=10, max_unit=600)
        inventory.obj_set_defaults()
        rp.add_inventory(inventory)
        alloc1 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='DISK_GB',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=user_id,
            used=500)
        alloc2 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='DISK_GB',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=user_id,
            used=500)
        alloc_list = rp_obj.AllocationList(
            self.context,
            objects=[alloc1, alloc2]
        )
        alloc_list.create_all()

        # Create some VCPU inventory and allocations.
        consumer_id = uuidutils.generate_uuid()
        os.environ['CONSUMER_ID'] = consumer_id
        inventory = rp_obj.Inventory(
            self.context, resource_provider=rp,
            resource_class='VCPU', total=10,
            max_unit=4)
        inventory.obj_set_defaults()
        rp.add_inventory(inventory)
        alloc1 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='VCPU',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=user_id,
            used=2)
        alloc2 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='VCPU',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=user_id,
            used=4)
        alloc_list = rp_obj.AllocationList(
                self.context,
                objects=[alloc1, alloc2])
        alloc_list.create_all()

        # Create a couple of allocations for a different user.
        consumer_id = uuidutils.generate_uuid()
        alloc1 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='DISK_GB',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=alt_user_id,
            used=20)
        alloc2 = rp_obj.Allocation(
            self.context, resource_provider=rp,
            resource_class='VCPU',
            consumer_id=consumer_id,
            project_id=project_id,
            user_id=alt_user_id,
            used=1)
        alloc_list = rp_obj.AllocationList(
                self.context,
                objects=[alloc1, alloc2])
        alloc_list.create_all()

        # The ALT_RP_XXX variables are for a resource provider that has
        # not been created in the Allocation fixture
        os.environ['ALT_RP_UUID'] = uuidutils.generate_uuid()
        os.environ['ALT_RP_NAME'] = uuidutils.generate_uuid()