Allow updating flavor:instance lease start/end times

Allows users to update the start and end time for a lease with
flavor:instance reservations. Previously, this was unsupported.

This implementation and tests are based on the instance plugin.

Change-Id: I897f8819a320b1a802a5536281100d489e798196
Signed-off-by: Mark Powers <markpowers@uchicago.edu>
This commit is contained in:
Mark Powers authored and Pierre Riteau committed 2026-08-28 18:37:47 +02:00
1 parent bbce338f28
commit 4bff791ea8
5 files changed
+176 -16

No files matched your search

+17 -4
View File
@@ -470,8 +470,9 @@ are in the ``reservation`` object.
Parameters for Flavor-based Instance Reservation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Lease updates are not currently supported for flavor-based instance
reservations.
Only the lease ``start_date`` and ``end_date`` can be updated for
flavor-based instance reservations. The ``flavor_id`` and ``amount``
parameters cannot be changed.
**Example of Update Lease Request**
@@ -552,8 +553,20 @@ are in the ``reservation`` object.
Parameters for Flavor-based Instance Reservation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Lease updates are not currently supported for flavor-based instance
reservations.
The following parameters are returned for flavor-based instance reservation.
All parameters are in the ``reservation`` object.
.. rest_parameters:: parameters.yaml
- reservation.amount: reservation_amount
- reservation.vcpus: reservation_vcpus
- reservation.memory_mb: reservation_memory_mb
- reservation.disk_gb: reservation_disk_gb
- reservation.affinity : reservation_affinity
- reservation.resource_properties: reservation_resource_properties
- reservation.flavor_id: reservation_flavor_id
- reservation.server_group_id: reservation_server_group_id
- reservation.aggregate_id: reservation_aggregate_id
**Example of Update Lease Response**
+5 -6
View File
@@ -471,12 +471,12 @@ class ManagerService(service_utils.RPCServer):
return reservations
def _handle_resource_type_exception(self, lease):
for reservation in lease['reservations']:
def _handle_resource_type_exception(self, reservations):
for reservation in reservations:
if reservation['resource_type'] == 'flavor:instance':
raise exceptions.NotImplemented(
error="Updating leases for 'flavor:instance' type "
"reservations is not yet supported.")
error="Updating parameters for 'flavor:instance' "
"type reservations is not yet supported.")
@status.lease.lease_status(
transition=status.lease.UPDATING,
@@ -505,8 +505,6 @@ class ManagerService(service_utils.RPCServer):
lease = db_api.lease_get(lease_id)
self._handle_resource_type_exception(lease)
start_date = values.get(
'start_date',
datetime.datetime.strftime(lease['start_date'], LEASE_DATE_FORMAT))
@@ -563,6 +561,7 @@ class ManagerService(service_utils.RPCServer):
if reservations:
new_reservations = reservations
self._handle_resource_type_exception(reservations)
new_allocs = self._allocation_candidates(values,
existing_reservations)
else:
+61 -5
View File
@@ -110,8 +110,10 @@ class FlavorPlugin(base.BasePlugin):
except ValueError as e:
raise mgr_exceptions.MalformedParameter(str(e))
def _query_available_hosts(self, start_date, end_date,
resource_request, resource_traits):
def _query_available_hosts(
self, start_date, end_date, resource_request, resource_traits,
excludes=[]
):
# TODO(johngarbutt): offload more of this to the db
# we should be able to exclude hosts that don't match the
# resource requests, e.g. baremetal vs virtual
@@ -124,7 +126,7 @@ class FlavorPlugin(base.BasePlugin):
hosts,
start_date - datetime.timedelta(minutes=CONF.cleaning_time),
end_date + datetime.timedelta(minutes=CONF.cleaning_time),
[])
excludes)
placement_rps_matching_traits = None
# Only query placement if we have traits to match
@@ -443,8 +445,62 @@ class FlavorPlugin(base.BasePlugin):
self._instance_plugin.cleanup_resources(instance_reservation)
def update_reservation(self, reservation_id, values):
raise mgr_exceptions.NotImplemented(
error="Flavor-based reservation update not yet supported")
"""Only supports updating lease start and end date."""
reservation = db_api.reservation_get(reservation_id)
instance_reservation = db_api.instance_reservation_get(
reservation["resource_id"]
)
if (
"flavor_id" in values
and values.get("flavor_id") != instance_reservation["flavor_id"]
):
raise mgr_exceptions.CantUpdateParameter(param="flavor_id")
if (
"amount" in values
and values.get("amount") != instance_reservation["amount"]
):
raise mgr_exceptions.CantUpdateParameter(param="amount")
flavor_id = instance_reservation["flavor_id"]
resource_request, resource_traits, _ = \
self._get_flavor_details(flavor_id)
# The flavor has this reservation as a resource for it. This
# information isn't in the host inventory DB, and so we need
# to remove it in order to query available hosts, or else none
# match our request.
rsv_id_rc_format = reservation_id.upper().replace("-", "_")
reservation_rc = "CUSTOM_RESERVATION_" + rsv_id_rc_format
if reservation_rc in resource_request:
resource_request.pop(reservation_rc)
existing_allocations = db_api.host_allocation_get_all_by_values(
reservation_id=reservation_id
)
candidates = self._query_available_hosts(
values["start_date"],
values["end_date"],
resource_request,
resource_traits,
[reservation_id],
)
# Ensure for every allocation in this reservation, there is a candidate
alloc_count = collections.Counter(
[
alloc["compute_host_id"]
for alloc in existing_allocations
]
)
candidate_count = collections.Counter(
[can["id"] for can in candidates]
)
if not all(
alloc_count[key] <= candidate_count.get(key, 0)
for key in alloc_count
):
raise mgr_exceptions.NotEnoughHostsAvailable()
def on_start(self, resource_id):
self._instance_plugin.on_start(resource_id)
@@ -17,7 +17,7 @@ from unittest import mock
from novaclient.v2 import flavors
from blazar import context
from blazar.db.sqlalchemy import api as db_api
from blazar.db import api as db_api
from blazar.db import utils as db_utils
from blazar.manager import exceptions as mgr_exceptions
from blazar.plugins.flavor import flavor_plugin
@@ -467,3 +467,90 @@ class TestFlavorPlugin(tests.DBTestCase):
self.assertEqual(3, len(ret))
for host in ret:
self.assertEqual(host["id"], '456')
def test_update_reservation(self):
plugin = flavor_plugin.FlavorPlugin()
old_reservation = {
"id": "reservation-id1",
"resource_id": "instance-reservation-id1",
}
instance_reservation = {"flavor_id": "flavor-id1", "amount": 2}
mock_reservation_get = self.patch(db_api, "reservation_get")
mock_reservation_get.return_value = old_reservation
mock_instance_reservation_get = self.patch(
db_api, "instance_reservation_get")
mock_instance_reservation_get.return_value = instance_reservation
mock_get_flavor_details = self.patch(plugin, "_get_flavor_details")
mock_get_flavor_details.return_value = ({"VCPU": 2}, {}, None)
mock_host_allocation_get = self.patch(
db_api, "host_allocation_get_all_by_values"
)
mock_host_allocation_get.return_value = [
{"compute_host_id": "host-id1"}
]
mock_query_available_hosts = \
self.patch(plugin, "_query_available_hosts")
mock_query_available_hosts.return_value = [{"id": "host-id1"}]
# Test successful update (extending lease)
new_values = {
"start_date": datetime.datetime(2020, 7, 7, 18, 0),
"end_date": datetime.datetime(2020, 7, 7, 19, 0),
}
plugin.update_reservation("reservation-id1", new_values)
# Test that updating flavor_id raises exception
with self.assertRaises(mgr_exceptions.CantUpdateParameter):
plugin.update_reservation(
"reservation-id1", {"flavor_id": "different-flavor"}
)
# Test that updating amount raises exception
with self.assertRaises(mgr_exceptions.CantUpdateParameter):
plugin.update_reservation("reservation-id1", {"amount": 3})
def test_update_reservation_cant_extend(self):
plugin = flavor_plugin.FlavorPlugin()
old_reservation = {
"id": "reservation-id1",
"resource_id": "instance-reservation-id1",
}
instance_reservation = {"flavor_id": "flavor-id1", "amount": 2}
mock_reservation_get = self.patch(db_api, "reservation_get")
mock_reservation_get.return_value = old_reservation
mock_instance_reservation_get = \
self.patch(db_api, "instance_reservation_get")
mock_instance_reservation_get.return_value = instance_reservation
mock_get_flavor_details = self.patch(plugin, "_get_flavor_details")
mock_get_flavor_details.return_value = ({"VCPU": 2}, {}, None)
# Existing allocation on host-id1
mock_host_allocation_get = self.patch(
db_api, "host_allocation_get_all_by_values"
)
mock_host_allocation_get.return_value = [
{"compute_host_id": "host-id1"}
]
# But no candidates available (other reservations block it)
mock_query_available_hosts = self.patch(
plugin, "_query_available_hosts")
mock_query_available_hosts.return_value = []
new_values = {
"start_date": datetime.datetime(2020, 7, 7, 18, 0),
"end_date": datetime.datetime(2020, 7, 7, 19, 0),
}
with self.assertRaises(mgr_exceptions.NotEnoughHostsAvailable):
plugin.update_reservation("reservation-id1", new_values)
@@ -0,0 +1,5 @@
---
features:
- |
Adds support for updating the start and end dates of a lease with
``flavor:instance`` reservations.