Merge "docs: Remove errant indentation, mark up literals"
This commit is contained in:
commit
23a2c980e2
@ -43,7 +43,7 @@ limits are currently not enforced on RPC interfaces listening on the AMQP
|
|||||||
bus.
|
bus.
|
||||||
|
|
||||||
Plugin and ML2 drivers are not supposed to enforce quotas for resources they
|
Plugin and ML2 drivers are not supposed to enforce quotas for resources they
|
||||||
manage. However, the subnet_allocation [1]_ extension is an exception and will
|
manage. However, the ``subnet_allocation`` [1]_ extension is an exception and will
|
||||||
be discussed below.
|
be discussed below.
|
||||||
|
|
||||||
The quota management and enforcement mechanisms discussed here apply to every
|
The quota management and enforcement mechanisms discussed here apply to every
|
||||||
@ -55,14 +55,14 @@ High Level View
|
|||||||
|
|
||||||
There are two main components in the Neutron quota system:
|
There are two main components in the Neutron quota system:
|
||||||
|
|
||||||
* The Quota API extensions.
|
* The Quota API extensions.
|
||||||
* The Quota Engine.
|
* The Quota Engine.
|
||||||
|
|
||||||
Both components rely on a quota driver. The neutron codebase currently defines
|
Both components rely on a quota driver. The neutron codebase currently defines
|
||||||
three quota drivers:
|
three quota drivers:
|
||||||
|
|
||||||
* neutron.db.quota.driver.DbQuotaDriver
|
* ``neutron.db.quota.driver.DbQuotaDriver``
|
||||||
* neutron.db.quota.driver_nolock.DbQuotaNoLockDriver (default)
|
* ``neutron.db.quota.driver_nolock.DbQuotaNoLockDriver`` (default)
|
||||||
|
|
||||||
The ``DbQuotaNoLockDriver`` is the default quota driver, defined in the
|
The ``DbQuotaNoLockDriver`` is the default quota driver, defined in the
|
||||||
configuration option ``quota_driver``.
|
configuration option ``quota_driver``.
|
||||||
@ -107,23 +107,23 @@ delete operations are implemented by the usual index, show, update and
|
|||||||
delete methods. These method simply call into the quota driver for either
|
delete methods. These method simply call into the quota driver for either
|
||||||
fetching project quotas or updating them.
|
fetching project quotas or updating them.
|
||||||
|
|
||||||
The _update_attributes method is called only once in the controller lifetime.
|
The ``_update_attributes`` method is called only once in the controller lifetime.
|
||||||
This method dynamically updates Neutron's resource attribute map [4]_ so that
|
This method dynamically updates Neutron's resource attribute map [4]_ so that
|
||||||
an attribute is added for every resource managed by the quota engine.
|
an attribute is added for every resource managed by the quota engine.
|
||||||
Request authorisation is performed in this controller, and only 'admin' users
|
Request authorisation is performed in this controller, and only 'admin' users
|
||||||
are allowed to modify quotas for projects. As the neutron policy engine is not
|
are allowed to modify quotas for projects. As the neutron policy engine is not
|
||||||
used, it is not possible to configure which users should be allowed to manage
|
used, it is not possible to configure which users should be allowed to manage
|
||||||
quotas using policy.yaml.
|
quotas using ``policy.yaml``.
|
||||||
|
|
||||||
The driver operations dealing with quota management are:
|
The driver operations dealing with quota management are:
|
||||||
|
|
||||||
* delete_tenant_quota, which simply removes all entries from the 'quotas'
|
* ``delete_tenant_quota``, which simply removes all entries from the 'quotas'
|
||||||
table for a given project identifier;
|
table for a given project identifier;
|
||||||
* update_quota_limit, which adds or updates an entry in the 'quotas' project
|
* ``update_quota_limit``, which adds or updates an entry in the 'quotas' project
|
||||||
for a given project identifier and a given resource name;
|
for a given project identifier and a given resource name;
|
||||||
* _get_quotas, which fetches limits for a set of resource and a given project
|
* ``_get_quotas``, which fetches limits for a set of resource and a given project
|
||||||
identifier
|
identifier
|
||||||
* _get_all_quotas, which behaves like _get_quotas, but for all projects.
|
* ``_get_all_quotas``, which behaves like ``_get_quotas``, but for all projects.
|
||||||
|
|
||||||
|
|
||||||
Resource Usage Info
|
Resource Usage Info
|
||||||
@ -131,29 +131,29 @@ Resource Usage Info
|
|||||||
|
|
||||||
Neutron has two ways of tracking resource usage info:
|
Neutron has two ways of tracking resource usage info:
|
||||||
|
|
||||||
* CountableResource, where resource usage is calculated every time quotas
|
* ``CountableResource``, where resource usage is calculated every time quotas
|
||||||
limits are enforced by counting rows in the resource table or resources
|
limits are enforced by counting rows in the resource table or resources
|
||||||
tables and reservations for that resource.
|
tables and reservations for that resource.
|
||||||
* TrackedResource, depends on the selected driver:
|
* ``TrackedResource``, depends on the selected driver:
|
||||||
|
|
||||||
* DbQuotaDriver: the resource usage relies on a specific table tracking
|
* ``DbQuotaDriver``: the resource usage relies on a specific table tracking
|
||||||
usage data, and performs explicitly counting only when the data in this
|
usage data, and performs explicitly counting only when the data in this
|
||||||
table are not in sync with actual used and reserved resources.
|
table are not in sync with actual used and reserved resources.
|
||||||
* DbQuotaNoLockDriver: the resource usage is counted directly from the
|
* ``DbQuotaNoLockDriver``: the resource usage is counted directly from the
|
||||||
database table associated to the resource. In this new driver,
|
database table associated to the resource. In this new driver,
|
||||||
CountableResource and TrackedResource could look similar but
|
``CountableResource`` and ``TrackedResource`` could look similar but
|
||||||
TrackedResource depends on one single database model (table) and the
|
``TrackedResource`` depends on one single database model (table) and the
|
||||||
resource count is done directly on this table only.
|
resource count is done directly on this table only.
|
||||||
|
|
||||||
Another difference between CountableResource and TrackedResource is that the
|
Another difference between ``CountableResource`` and ``TrackedResource`` is that the
|
||||||
former invokes a plugin method to count resources. CountableResource should be
|
former invokes a plugin method to count resources. ``CountableResource`` should be
|
||||||
therefore employed for plugins which do not leverage the Neutron database.
|
therefore employed for plugins which do not leverage the Neutron database.
|
||||||
The actual class that the Neutron quota engine will use is determined by the
|
The actual class that the Neutron quota engine will use is determined by the
|
||||||
track_quota_usage variable in the quota configuration section. If True,
|
``track_quota_usage`` variable in the quota configuration section. If ``True``,
|
||||||
TrackedResource instances will be created, otherwise the quota engine will
|
``TrackedResource`` instances will be created, otherwise the quota engine will
|
||||||
use CountableResource instances.
|
use ``CountableResource`` instances.
|
||||||
Resource creation is performed by the create_resource_instance factory method
|
Resource creation is performed by the ``create_resource_instance`` factory method
|
||||||
in the neutron.quota.resource module.
|
in the ``neutron.quota.resource`` module.
|
||||||
|
|
||||||
DbQuotaDriver description
|
DbQuotaDriver description
|
||||||
-------------------------
|
-------------------------
|
||||||
@ -161,10 +161,10 @@ DbQuotaDriver description
|
|||||||
From a performance perspective, having a table tracking resource usage
|
From a performance perspective, having a table tracking resource usage
|
||||||
has some advantages, albeit not fundamental. Indeed the time required for
|
has some advantages, albeit not fundamental. Indeed the time required for
|
||||||
executing queries to explicitly count objects will increase with the number of
|
executing queries to explicitly count objects will increase with the number of
|
||||||
records in the table. On the other hand, using TrackedResource will fetch a
|
records in the table. On the other hand, using ``TrackedResource`` will fetch a
|
||||||
single record, but has the drawback of having to execute an UPDATE statement
|
single record, but has the drawback of having to execute an UPDATE statement
|
||||||
once the operation is completed.
|
once the operation is completed.
|
||||||
Nevertheless, CountableResource instances do not simply perform a SELECT query
|
Nevertheless, ``CountableResource`` instances do not simply perform a SELECT query
|
||||||
on the relevant table for a resource, but invoke a plugin method, which might
|
on the relevant table for a resource, but invoke a plugin method, which might
|
||||||
execute several statements and sometimes even interacts with the backend
|
execute several statements and sometimes even interacts with the backend
|
||||||
before returning.
|
before returning.
|
||||||
@ -179,7 +179,7 @@ While a RESTful API request is the most common one, resources can be created
|
|||||||
by RPC handlers listing on the AMQP bus, such as those which create DHCP
|
by RPC handlers listing on the AMQP bus, such as those which create DHCP
|
||||||
ports, or by plugin operations, such as those which create router ports.
|
ports, or by plugin operations, such as those which create router ports.
|
||||||
|
|
||||||
To this aim, TrackedResource instances are initialised with a reference to
|
To this aim, ``TrackedResource`` instances are initialised with a reference to
|
||||||
the model class for the resource for which they track usage data. During
|
the model class for the resource for which they track usage data. During
|
||||||
object initialisation, SqlAlchemy event handlers are installed for this class.
|
object initialisation, SqlAlchemy event handlers are installed for this class.
|
||||||
The event handler is executed after a record is inserted or deleted.
|
The event handler is executed after a record is inserted or deleted.
|
||||||
@ -189,10 +189,10 @@ it will be synchronised counting resource usage from the database.
|
|||||||
Even if this solution has some drawbacks, listed in the 'exceptions and
|
Even if this solution has some drawbacks, listed in the 'exceptions and
|
||||||
caveats' section, it is more reliable than solutions such as:
|
caveats' section, it is more reliable than solutions such as:
|
||||||
|
|
||||||
* Updating the usage counters with the new 'correct' value every time an
|
* Updating the usage counters with the new 'correct' value every time an
|
||||||
operation completes.
|
operation completes.
|
||||||
* Having a periodic task synchronising quota usage data with actual data in
|
* Having a periodic task synchronising quota usage data with actual data in
|
||||||
the Neutron DB.
|
the Neutron DB.
|
||||||
|
|
||||||
|
|
||||||
DbQuotaNoLockDriver description
|
DbQuotaNoLockDriver description
|
||||||
@ -201,7 +201,7 @@ DbQuotaNoLockDriver description
|
|||||||
The strategy of this quota driver is the opposite to ``DbQuotaDriver``.
|
The strategy of this quota driver is the opposite to ``DbQuotaDriver``.
|
||||||
Instead of tracking the usage quota of each resource in a specific table,
|
Instead of tracking the usage quota of each resource in a specific table,
|
||||||
this driver retrieves the used resources directly form the database.
|
this driver retrieves the used resources directly form the database.
|
||||||
Each TrackedResource is linked to a database table that stores the tracked
|
Each ``TrackedResource`` is linked to a database table that stores the tracked
|
||||||
resources. This driver claims that a trivial query on the resource table,
|
resources. This driver claims that a trivial query on the resource table,
|
||||||
filtering by project ID, is faster than attending to the DB events and tracking
|
filtering by project ID, is faster than attending to the DB events and tracking
|
||||||
the quota usage in an independent table.
|
the quota usage in an independent table.
|
||||||
@ -210,13 +210,13 @@ This driver relays on the database engine transactionality isolation. Each
|
|||||||
time a new resource is requested, the quota driver opens a database transaction
|
time a new resource is requested, the quota driver opens a database transaction
|
||||||
to:
|
to:
|
||||||
|
|
||||||
* Clean up the expired reservations. The amount of expired reservations is
|
* Clean up the expired reservations. The amount of expired reservations is
|
||||||
always limited because of the short timeout set (2 minutes).
|
always limited because of the short timeout set (2 minutes).
|
||||||
* Retrieve the used resources for a specific project. This query retrieves
|
* Retrieve the used resources for a specific project. This query retrieves
|
||||||
only the "project_id" column of the resource to avoid backref requests; that
|
only the "project_id" column of the resource to avoid backref requests; that
|
||||||
limits the scope of the query and speeds up it.
|
limits the scope of the query and speeds up it.
|
||||||
* Retrieve the reserved resources, created by other concurrent operations.
|
* Retrieve the reserved resources, created by other concurrent operations.
|
||||||
* If there is enough quota, create a new reservation register.
|
* If there is enough quota, create a new reservation register.
|
||||||
|
|
||||||
Those operations, executed in the same transaction, are fast enough to avoid
|
Those operations, executed in the same transaction, are fast enough to avoid
|
||||||
another concurrent resource reservation, exceeding the available quota. At the
|
another concurrent resource reservation, exceeding the available quota. At the
|
||||||
@ -227,33 +227,33 @@ the chances of overcommiting resources over the quota limits are low. Neutron
|
|||||||
does not enforce quota in such way that a quota limit violation could never
|
does not enforce quota in such way that a quota limit violation could never
|
||||||
occur [5]_.
|
occur [5]_.
|
||||||
|
|
||||||
Regardless of whether CountableResource or TrackedResource is used, the quota
|
Regardless of whether ``CountableResource`` or ``TrackedResource`` is used, the quota
|
||||||
engine always invokes its count() method to retrieve resource usage.
|
engine always invokes its ``count()`` method to retrieve resource usage.
|
||||||
Therefore, from the perspective of the Quota engine there is absolutely no
|
Therefore, from the perspective of the Quota engine there is absolutely no
|
||||||
difference between CountableResource and TrackedResource.
|
difference between ``CountableResource`` and ``TrackedResource``.
|
||||||
|
|
||||||
Quota Enforcement in DbQuotaDriver
|
Quota Enforcement in DbQuotaDriver
|
||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
Before dispatching a request to the plugin, the Neutron 'base' controller [6]_
|
Before dispatching a request to the plugin, the Neutron 'base' controller [6]_
|
||||||
attempts to make a reservation for requested resource(s).
|
attempts to make a reservation for requested resource(s).
|
||||||
Reservations are made by calling the make_reservation method in
|
Reservations are made by calling the ``make_reservation`` method in
|
||||||
neutron.quota.QuotaEngine.
|
``neutron.quota.QuotaEngine``.
|
||||||
The process of making a reservation is fairly straightforward:
|
The process of making a reservation is fairly straightforward:
|
||||||
|
|
||||||
* Get current resource usages. This is achieved by invoking the count method
|
* Get current resource usages. This is achieved by invoking the count method
|
||||||
on every requested resource, and then retrieving the amount of reserved
|
on every requested resource, and then retrieving the amount of reserved
|
||||||
resources.
|
resources.
|
||||||
* Fetch current quota limits for requested resources, by invoking the
|
* Fetch current quota limits for requested resources, by invoking the
|
||||||
_get_project_quotas method.
|
``_get_project_quotas`` method.
|
||||||
* Fetch expired reservations for selected resources. This amount will be
|
* Fetch expired reservations for selected resources. This amount will be
|
||||||
subtracted from resource usage. As in most cases there won't be any
|
subtracted from resource usage. As in most cases there won't be any
|
||||||
expired reservation, this approach actually requires less DB operations than
|
expired reservation, this approach actually requires less DB operations than
|
||||||
doing a sum of non-expired, reserved resources for each request.
|
doing a sum of non-expired, reserved resources for each request.
|
||||||
* For each resource calculate its headroom, and verify the requested
|
* For each resource calculate its headroom, and verify the requested
|
||||||
amount of resource is less than the headroom.
|
amount of resource is less than the headroom.
|
||||||
* If the above is true for all resource, the reservation is saved in the DB,
|
* If the above is true for all resource, the reservation is saved in the DB,
|
||||||
otherwise an OverQuotaLimit exception is raised.
|
otherwise an ``OverQuotaLimit`` exception is raised.
|
||||||
|
|
||||||
The quota engine is able to make a reservation for multiple resources.
|
The quota engine is able to make a reservation for multiple resources.
|
||||||
However, it is worth noting that because of the current structure of the
|
However, it is worth noting that because of the current structure of the
|
||||||
@ -266,11 +266,11 @@ In order to ensure correct operations, a row-level lock is acquired in
|
|||||||
the transaction which creates the reservation. The lock is acquired when
|
the transaction which creates the reservation. The lock is acquired when
|
||||||
reading usage data. In case of write-set certification failures,
|
reading usage data. In case of write-set certification failures,
|
||||||
which can occur in active/active clusters such as MySQL galera, the decorator
|
which can occur in active/active clusters such as MySQL galera, the decorator
|
||||||
neutron_lib.db.api.retry_db_errors will retry the transaction if a DBDeadLock
|
``neutron_lib.db.api.retry_db_errors`` will retry the transaction if a DBDeadLock
|
||||||
exception is raised.
|
exception is raised.
|
||||||
While non-locking approaches are possible, it has been found out that, since
|
While non-locking approaches are possible, it has been found out that, since
|
||||||
a non-locking algorithms increases the chances of collision, the cost of
|
a non-locking algorithms increases the chances of collision, the cost of
|
||||||
handling a DBDeadlock is still lower than the cost of retrying the operation
|
handling a ``DBDeadlock`` is still lower than the cost of retrying the operation
|
||||||
when a collision is detected. A study in this direction was conducted for
|
when a collision is detected. A study in this direction was conducted for
|
||||||
IP allocation operations, but the same principles apply here as well [7]_.
|
IP allocation operations, but the same principles apply here as well [7]_.
|
||||||
Nevertheless, moving away for DB-level locks is something that must happen
|
Nevertheless, moving away for DB-level locks is something that must happen
|
||||||
@ -280,11 +280,12 @@ Committing and cancelling a reservation is as simple as deleting the
|
|||||||
reservation itself. When a reservation is committed, the resources which
|
reservation itself. When a reservation is committed, the resources which
|
||||||
were committed are now stored in the database, so the reservation itself
|
were committed are now stored in the database, so the reservation itself
|
||||||
should be deleted. The Neutron quota engine simply removes the record when
|
should be deleted. The Neutron quota engine simply removes the record when
|
||||||
cancelling a reservation (ie: the request failed to complete), and also
|
cancelling a reservation (i.e. the request failed to complete), and also
|
||||||
marks quota usage info as dirty when the reservation is committed (ie:
|
marks quota usage info as dirty when the reservation is committed (i.e.
|
||||||
the request completed correctly).
|
the request completed correctly).
|
||||||
Reservations are committed or cancelled by respectively calling the
|
Reservations are committed or cancelled by respectively calling the
|
||||||
commit_reservation and cancel_reservation methods in neutron.quota.QuotaEngine.
|
``commit_reservation`` and ``cancel_reservation`` methods in
|
||||||
|
``neutron.quota.QuotaEngine``.
|
||||||
|
|
||||||
Reservations are not perennial. Eternal reservation would eventually exhaust
|
Reservations are not perennial. Eternal reservation would eventually exhaust
|
||||||
projects' quotas because they would never be removed when an API worker crashes
|
projects' quotas because they would never be removed when an API worker crashes
|
||||||
@ -314,16 +315,17 @@ argument must be a resource name, and the value of the argument must be
|
|||||||
a DB model class. For example:
|
a DB model class. For example:
|
||||||
|
|
||||||
::
|
::
|
||||||
@resource_registry.tracked_resources(network=models_v2.Network,
|
|
||||||
|
@resource_registry.tracked_resources(network=models_v2.Network,
|
||||||
port=models_v2.Port,
|
port=models_v2.Port,
|
||||||
subnet=models_v2.Subnet,
|
subnet=models_v2.Subnet,
|
||||||
subnetpool=models_v2.SubnetPool)
|
subnetpool=models_v2.SubnetPool)
|
||||||
|
|
||||||
Will ensure network, port, subnet and subnetpool resources are tracked.
|
Will ensure network, port, subnet and subnetpool resources are tracked.
|
||||||
In theory, it is possible to use this decorator multiple times, and not
|
In theory, it is possible to use this decorator multiple times, and not
|
||||||
exclusively to __init__ methods. However, this would eventually lead to
|
exclusively to ``__init__`` methods. However, this would eventually lead to
|
||||||
code readability and maintainability problems, so developers are strongly
|
code readability and maintainability problems, so developers are strongly
|
||||||
encourage to apply this decorator exclusively to the plugin's __init__
|
encourage to apply this decorator exclusively to the plugin's ``__init__``
|
||||||
method (or any other method which is called by the plugin only once
|
method (or any other method which is called by the plugin only once
|
||||||
during its initialization).
|
during its initialization).
|
||||||
|
|
||||||
@ -350,41 +352,41 @@ Exceptions and Caveats
|
|||||||
|
|
||||||
Please be aware of the following limitations of the quota enforcement engine:
|
Please be aware of the following limitations of the quota enforcement engine:
|
||||||
|
|
||||||
* Subnet allocation from subnet pools, in particularly shared pools, is also
|
* Subnet allocation from subnet pools, in particularly shared pools, is also
|
||||||
subject to quota limit checks. However this checks are not enforced by the
|
subject to quota limit checks. However this checks are not enforced by the
|
||||||
quota engine, but trough a mechanism implemented in the
|
quota engine, but trough a mechanism implemented in the
|
||||||
neutron.ipam.subnetalloc module. This is because the Quota engine is not
|
``neutron.ipam.subnetalloc`` module. This is because the quota engine is not
|
||||||
able to satisfy the requirements for quotas on subnet allocation.
|
able to satisfy the requirements for quotas on subnet allocation.
|
||||||
* The quota engine also provides a limit_check routine which enforces quota
|
* The quota engine also provides a ``limit_check`` routine which enforces quota
|
||||||
checks without creating reservations. This way of doing quota enforcement
|
checks without creating reservations. This way of doing quota enforcement
|
||||||
is extremely unreliable and superseded by the reservation mechanism. It
|
is extremely unreliable and superseded by the reservation mechanism. It
|
||||||
has not been removed to ensure off-tree plugins and extensions which leverage
|
has not been removed to ensure off-tree plugins and extensions which leverage
|
||||||
are not broken.
|
are not broken.
|
||||||
* SqlAlchemy events might not be the most reliable way for detecting changes
|
* SqlAlchemy events might not be the most reliable way for detecting changes
|
||||||
in resource usage. Since the event mechanism monitors the data model class,
|
in resource usage. Since the event mechanism monitors the data model class,
|
||||||
it is paramount for a correct quota enforcement, that resources are always
|
it is paramount for a correct quota enforcement, that resources are always
|
||||||
created and deleted using object relational mappings. For instance, deleting
|
created and deleted using object relational mappings. For instance, deleting
|
||||||
a resource with a query.delete call, will not trigger the event. SQLAlchemy
|
a resource with a ``query.delete`` call will not trigger the event. SQLAlchemy
|
||||||
events should be considered as a temporary measure adopted as Neutron lacks
|
events should be considered as a temporary measure adopted as Neutron lacks
|
||||||
persistent API objects.
|
persistent API objects.
|
||||||
* As CountableResource instance do not track usage data, when making a
|
* As ``CountableResource`` instance do not track usage data, when making a
|
||||||
reservation no write-intent lock is acquired. Therefore the quota engine
|
reservation no write-intent lock is acquired. Therefore the quota engine
|
||||||
with CountableResource is not concurrency-safe.
|
with ``CountableResource`` is not concurrency-safe.
|
||||||
* The mechanism for specifying for which resources enable usage tracking
|
* The mechanism for specifying for which resources enable usage tracking
|
||||||
relies on the fact that the plugin is loaded before quota-limited resources
|
relies on the fact that the plugin is loaded before quota-limited resources
|
||||||
are registered. For this reason it is not possible to validate whether a
|
are registered. For this reason it is not possible to validate whether a
|
||||||
resource actually exists or not when enabling tracking for it. Developers
|
resource actually exists or not when enabling tracking for it. Developers
|
||||||
should pay particular attention into ensuring resource names are correctly
|
should pay particular attention into ensuring resource names are correctly
|
||||||
specified.
|
specified.
|
||||||
* The code assumes usage trackers are a trusted source of truth: if they
|
* The code assumes usage trackers are a trusted source of truth: if they
|
||||||
report a usage counter and the dirty bit is not set, that counter is
|
report a usage counter and the dirty bit is not set, that counter is
|
||||||
correct. If it's dirty than surely that counter is out of sync.
|
correct. If it's dirty than surely that counter is out of sync.
|
||||||
This is not very robust, as there might be issues upon restart when toggling
|
This is not very robust, as there might be issues upon restart when toggling
|
||||||
the use_tracked_resources configuration variable, as stale counters might be
|
the use_tracked_resources configuration variable, as stale counters might be
|
||||||
trusted upon for making reservations. Also, the same situation might occur
|
trusted upon for making reservations. Also, the same situation might occur
|
||||||
if a server crashes after the API operation is completed but before the
|
if a server crashes after the API operation is completed but before the
|
||||||
reservation is committed, as the actual resource usage is changed but
|
reservation is committed, as the actual resource usage is changed but
|
||||||
the corresponding usage tracker is not marked as dirty.
|
the corresponding usage tracker is not marked as dirty.
|
||||||
|
|
||||||
References
|
References
|
||||||
----------
|
----------
|
||||||
|
Loading…
x
Reference in New Issue
Block a user