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:
1 parent
bbce338f28
commit
4bff791ea8
5 files changed
+176
-16
No files matched your search
@@ -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**
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
Reference in new issue
Block a user