Merge "Prevent the host update API from updating in-use extra capabilities"
This commit is contained in:
commit
11ed6d96c2
|
@ -19,8 +19,12 @@ import sys
|
|||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from blazar.db.sqlalchemy import api
|
||||
from blazar.db.sqlalchemy import facade_wrapper
|
||||
from blazar.db.sqlalchemy import models
|
||||
from blazar.manager import exceptions as mgr_exceptions
|
||||
from blazar.plugins import instances as instance_plugin
|
||||
from blazar.plugins import oshosts as host_plugin
|
||||
|
||||
get_session = facade_wrapper.get_session
|
||||
|
||||
|
@ -84,6 +88,15 @@ def get_reservations_by_host_ids(host_ids, start_date, end_date):
|
|||
return query.all()
|
||||
|
||||
|
||||
def get_plugin_reservation(resource_type, resource_id):
|
||||
if resource_type == host_plugin.RESOURCE_TYPE:
|
||||
return api.host_reservation_get(resource_id)
|
||||
elif resource_type == instance_plugin.RESOURCE_TYPE:
|
||||
return api.instance_reservation_get(resource_id)
|
||||
else:
|
||||
raise mgr_exceptions.UnsupportedResourceType(resource_type)
|
||||
|
||||
|
||||
def get_free_periods(resource_id, start_date, end_date, duration):
|
||||
"""Returns a list of free periods."""
|
||||
reserved_periods = get_reserved_periods(resource_id,
|
||||
|
|
|
@ -111,6 +111,10 @@ def get_reservations_by_host_ids(host_ids, start_date, end_date):
|
|||
return IMPL.get_reservations_by_host_ids(host_ids, start_date, end_date)
|
||||
|
||||
|
||||
def get_plugin_reservation(resource_type, resource_id):
|
||||
return IMPL.get_plugin_reservation(resource_type, resource_id)
|
||||
|
||||
|
||||
def get_free_periods(resource_id, start_date, end_date, duration):
|
||||
"""Returns a list of free periods."""
|
||||
return IMPL.get_free_periods(resource_id, start_date, end_date, duration)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
# Copyright (c) 2018 NTT.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
RESOURCE_TYPE = u'virtual:instance'
|
|
@ -25,6 +25,7 @@ from blazar.db import utils as db_utils
|
|||
from blazar import exceptions
|
||||
from blazar.manager import exceptions as mgr_exceptions
|
||||
from blazar.plugins import base
|
||||
from blazar.plugins import instances as plugin
|
||||
from blazar.plugins import oshosts
|
||||
from blazar import status
|
||||
from blazar.utils.openstack import nova
|
||||
|
@ -33,7 +34,6 @@ from blazar.utils import plugins as plugins_utils
|
|||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
RESOURCE_TYPE = u'virtual:instance'
|
||||
RESERVATION_PREFIX = 'reservation'
|
||||
FLAVOR_EXTRA_SPEC = "aggregate_instance_extra_specs:" + RESERVATION_PREFIX
|
||||
|
||||
|
@ -41,7 +41,7 @@ FLAVOR_EXTRA_SPEC = "aggregate_instance_extra_specs:" + RESERVATION_PREFIX
|
|||
class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper):
|
||||
"""Plugin for virtual instance resources."""
|
||||
|
||||
resource_type = RESOURCE_TYPE
|
||||
resource_type = plugin.RESOURCE_TYPE
|
||||
title = 'Virtual Instance Plugin'
|
||||
|
||||
def __init__(self):
|
||||
|
@ -495,7 +495,7 @@ class VirtualInstancePlugin(base.BasePlugin, nova.NovaClientWrapper):
|
|||
interval_end)
|
||||
|
||||
for reservation in reservations:
|
||||
if reservation['resource_type'] != RESOURCE_TYPE:
|
||||
if reservation['resource_type'] != plugin.RESOURCE_TYPE:
|
||||
continue
|
||||
|
||||
for allocation in [alloc for alloc
|
||||
|
|
|
@ -376,41 +376,76 @@ class PhysicalHostPlugin(base.BasePlugin, nova.NovaClientWrapper):
|
|||
else:
|
||||
return None
|
||||
|
||||
def is_updatable_extra_capability(self, capability):
|
||||
reservations = db_utils.get_reservations_by_host_id(
|
||||
capability['computehost_id'], datetime.datetime.utcnow(),
|
||||
datetime.date.max)
|
||||
|
||||
for r in reservations:
|
||||
plugin_reservation = db_utils.get_plugin_reservation(
|
||||
r['resource_type'], r['resource_id'])
|
||||
|
||||
requirements_queries = plugins_utils.convert_requirements(
|
||||
plugin_reservation['resource_properties'])
|
||||
|
||||
# TODO(masahito): If all the reservations using the
|
||||
# extra_capability can be re-allocated it's okay to update
|
||||
# the extra_capability.
|
||||
for requirement in requirements_queries:
|
||||
# A requirement is of the form "key op value" as string
|
||||
if requirement.split(" ")[0] == capability['capability_name']:
|
||||
return False
|
||||
return True
|
||||
|
||||
def update_computehost(self, host_id, values):
|
||||
if values:
|
||||
cant_update_extra_capability = []
|
||||
for value in values:
|
||||
capabilities = db_api.host_extra_capability_get_all_per_name(
|
||||
host_id,
|
||||
value,
|
||||
)
|
||||
if capabilities:
|
||||
for raw_capability in capabilities:
|
||||
capability = {
|
||||
'capability_name': value,
|
||||
'capability_value': values[value],
|
||||
}
|
||||
try:
|
||||
db_api.host_extra_capability_update(
|
||||
raw_capability['id'], capability)
|
||||
except (db_ex.BlazarDBException, RuntimeError):
|
||||
cant_update_extra_capability.append(
|
||||
raw_capability['capability_name'])
|
||||
else:
|
||||
new_capability = {
|
||||
'computehost_id': host_id,
|
||||
'capability_name': value,
|
||||
'capability_value': values[value],
|
||||
}
|
||||
try:
|
||||
db_api.host_extra_capability_create(new_capability)
|
||||
except (db_ex.BlazarDBException, RuntimeError):
|
||||
cant_update_extra_capability.append(
|
||||
new_capability['capability_name'])
|
||||
if cant_update_extra_capability:
|
||||
raise manager_ex.CantAddExtraCapability(
|
||||
host=host_id,
|
||||
keys=cant_update_extra_capability)
|
||||
# nothing to update
|
||||
if not values:
|
||||
return self.get_computehost(host_id)
|
||||
|
||||
cant_update_extra_capability = []
|
||||
previous_capabilities = self._get_extra_capabilities(host_id)
|
||||
updated_keys = set(values.keys()) & set(previous_capabilities.keys())
|
||||
new_keys = set(values.keys()) - set(previous_capabilities.keys())
|
||||
|
||||
for key in updated_keys:
|
||||
raw_capability = next(iter(
|
||||
db_api.host_extra_capability_get_all_per_name(host_id, key)))
|
||||
capability = {
|
||||
'capability_name': key,
|
||||
'capability_value': values[key],
|
||||
}
|
||||
if self.is_updatable_extra_capability(raw_capability):
|
||||
try:
|
||||
db_api.host_extra_capability_update(
|
||||
raw_capability['id'], capability)
|
||||
except (db_ex.BlazarDBException, RuntimeError):
|
||||
cant_update_extra_capability.append(
|
||||
raw_capability['capability_name'])
|
||||
else:
|
||||
LOG.info("Capability %s can't be updated because "
|
||||
"existing reservations require it.",
|
||||
raw_capability['capability_name'])
|
||||
cant_update_extra_capability.append(
|
||||
raw_capability['capability_name'])
|
||||
|
||||
for key in new_keys:
|
||||
new_capability = {
|
||||
'computehost_id': host_id,
|
||||
'capability_name': key,
|
||||
'capability_value': values[key],
|
||||
}
|
||||
try:
|
||||
db_api.host_extra_capability_create(new_capability)
|
||||
except (db_ex.BlazarDBException, RuntimeError):
|
||||
cant_update_extra_capability.append(
|
||||
new_capability['capability_name'])
|
||||
|
||||
if cant_update_extra_capability:
|
||||
raise manager_ex.CantAddExtraCapability(
|
||||
host=host_id, keys=cant_update_extra_capability)
|
||||
|
||||
LOG.info('Extra capabilities on compute host %s updated with %s',
|
||||
host_id, values)
|
||||
return self.get_computehost(host_id)
|
||||
|
||||
def delete_computehost(self, host_id):
|
||||
|
|
|
@ -22,6 +22,7 @@ import six
|
|||
|
||||
from blazar.db.sqlalchemy import api as db_api
|
||||
from blazar.db.sqlalchemy import utils as db_utils
|
||||
from blazar.manager import exceptions as mgr_exceptions
|
||||
from blazar import tests
|
||||
|
||||
|
||||
|
@ -35,12 +36,19 @@ def _get_fake_lease_uuid():
|
|||
|
||||
|
||||
def _get_fake_phys_reservation_values(lease_id=_get_fake_lease_uuid(),
|
||||
resource_id=None):
|
||||
resource_id='1234'):
|
||||
return {'lease_id': lease_id,
|
||||
'resource_id': '1234' if not resource_id else resource_id,
|
||||
'resource_id': resource_id,
|
||||
'resource_type': 'physical:host'}
|
||||
|
||||
|
||||
def _get_fake_inst_reservation_values(lease_id=_get_fake_lease_uuid(),
|
||||
resource_id='5678'):
|
||||
return {'lease_id': lease_id,
|
||||
'resource_id': resource_id,
|
||||
'resource_type': 'virtual:instance'}
|
||||
|
||||
|
||||
def _get_datetime(value='2030-01-01 00:00'):
|
||||
return datetime.datetime.strptime(value, '%Y-%m-%d %H:%M')
|
||||
|
||||
|
@ -399,5 +407,28 @@ class SQLAlchemyDBUtilsTestCase(tests.DBTestCase):
|
|||
self.check_reservation([], ['r4'],
|
||||
'2030-01-01 07:00', '2030-01-01 15:00')
|
||||
|
||||
def test_get_plugin_reservation_with_host(self):
|
||||
patch_host_reservation_get = self.patch(db_api, 'host_reservation_get')
|
||||
patch_host_reservation_get.return_value = {
|
||||
'id': 'id',
|
||||
'reservation_id': 'reservation-id',
|
||||
}
|
||||
db_utils.get_plugin_reservation('physical:host', 'id-1')
|
||||
patch_host_reservation_get.assert_called_once_with('id-1')
|
||||
|
||||
def test_get_plugin_reservation_with_instance(self):
|
||||
patch_inst_reservation_get = self.patch(db_api,
|
||||
'instance_reservation_get')
|
||||
patch_inst_reservation_get.return_value = {
|
||||
'id': 'id',
|
||||
'reservation_id': 'reservation-id',
|
||||
}
|
||||
db_utils.get_plugin_reservation('virtual:instance', 'id-1')
|
||||
patch_inst_reservation_get.assert_called_once_with('id-1')
|
||||
|
||||
def test_get_plugin_reservation_with_invalid(self):
|
||||
self.assertRaises(mgr_exceptions.UnsupportedResourceType,
|
||||
db_utils.get_plugin_reservation, 'invalid', 'id1')
|
||||
|
||||
# TODO(frossigneux) longest_availability
|
||||
# TODO(frossigneux) shortest_availability
|
||||
|
|
|
@ -24,6 +24,7 @@ from blazar.db import api as db_api
|
|||
from blazar.db import utils as db_utils
|
||||
from blazar import exceptions
|
||||
from blazar.manager import exceptions as mgr_exceptions
|
||||
from blazar.plugins import instances
|
||||
from blazar.plugins.instances import instance_plugin
|
||||
from blazar.plugins import oshosts
|
||||
from blazar import tests
|
||||
|
@ -136,7 +137,7 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
if host_id == 'host-1':
|
||||
return [
|
||||
{'id': '1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE}]
|
||||
'resource_type': instances.RESOURCE_TYPE}]
|
||||
else:
|
||||
return []
|
||||
|
||||
|
@ -168,8 +169,8 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
|
||||
def fake_get_reservation_by_host(host_id, start, end):
|
||||
return [
|
||||
{'id': '1', 'resource_type': instance_plugin.RESOURCE_TYPE},
|
||||
{'id': '2', 'resource_type': instance_plugin.RESOURCE_TYPE}]
|
||||
{'id': '1', 'resource_type': instances.RESOURCE_TYPE},
|
||||
{'id': '2', 'resource_type': instances.RESOURCE_TYPE}]
|
||||
|
||||
plugin = instance_plugin.VirtualInstancePlugin()
|
||||
|
||||
|
@ -258,9 +259,9 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
if host_id in ['host-1', 'host-3']:
|
||||
return [
|
||||
{'id': '1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE},
|
||||
'resource_type': instances.RESOURCE_TYPE},
|
||||
{'id': '2',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE}
|
||||
'resource_type': instances.RESOURCE_TYPE}
|
||||
]
|
||||
else:
|
||||
return []
|
||||
|
@ -315,14 +316,14 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
{'id': '1',
|
||||
'resource_type': oshosts.RESOURCE_TYPE},
|
||||
{'id': '2',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE}
|
||||
'resource_type': instances.RESOURCE_TYPE}
|
||||
]
|
||||
else:
|
||||
return [
|
||||
{'id': '1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE},
|
||||
'resource_type': instances.RESOURCE_TYPE},
|
||||
{'id': '2',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE}
|
||||
'resource_type': instances.RESOURCE_TYPE}
|
||||
]
|
||||
|
||||
plugin = instance_plugin.VirtualInstancePlugin()
|
||||
|
@ -870,7 +871,7 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
failed_host = {'id': '1'}
|
||||
dummy_reservation = {
|
||||
'id': 'rsrv-1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE,
|
||||
'resource_type': instances.RESOURCE_TYPE,
|
||||
'lease_id': 'lease-1',
|
||||
'status': 'pending',
|
||||
'vcpus': 2,
|
||||
|
@ -904,7 +905,7 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
failed_host = {'id': '1'}
|
||||
dummy_reservation = {
|
||||
'id': 'rsrv-1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE,
|
||||
'resource_type': instances.RESOURCE_TYPE,
|
||||
'lease_id': 'lease-1',
|
||||
'status': 'pending',
|
||||
'vcpus': 2,
|
||||
|
@ -940,7 +941,7 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
failed_host = {'id': '1'}
|
||||
dummy_reservation = {
|
||||
'id': 'rsrv-1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE,
|
||||
'resource_type': instances.RESOURCE_TYPE,
|
||||
'lease_id': 'lease-1',
|
||||
'status': 'active',
|
||||
'vcpus': 2,
|
||||
|
@ -975,7 +976,7 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
failed_host = {'id': '1'}
|
||||
dummy_reservation = {
|
||||
'id': 'rsrv-1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE,
|
||||
'resource_type': instances.RESOURCE_TYPE,
|
||||
'lease_id': 'lease-1',
|
||||
'status': 'active',
|
||||
'vcpus': 2,
|
||||
|
@ -1016,7 +1017,7 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
}
|
||||
dummy_reservation = {
|
||||
'id': 'rsrv-1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE,
|
||||
'resource_type': instances.RESOURCE_TYPE,
|
||||
'lease_id': 'lease-1',
|
||||
'status': 'pending',
|
||||
'vcpus': 2,
|
||||
|
@ -1066,7 +1067,7 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
}
|
||||
dummy_reservation = {
|
||||
'id': 'rsrv-1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE,
|
||||
'resource_type': instances.RESOURCE_TYPE,
|
||||
'lease_id': 'lease-1',
|
||||
'status': 'active',
|
||||
'vcpus': 2,
|
||||
|
@ -1125,7 +1126,7 @@ class TestVirtualInstancePlugin(tests.TestCase):
|
|||
}
|
||||
dummy_reservation = {
|
||||
'id': 'rsrv-1',
|
||||
'resource_type': instance_plugin.RESOURCE_TYPE,
|
||||
'resource_type': instances.RESOURCE_TYPE,
|
||||
'lease_id': 'lease-1',
|
||||
'status': 'pending',
|
||||
'vcpus': 2,
|
||||
|
|
|
@ -241,22 +241,32 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
host_values = {'foo': 'baz'}
|
||||
|
||||
self.db_host_extra_capability_get_all_per_name.return_value = [
|
||||
{'id': '1',
|
||||
{'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_name': 'foo',
|
||||
'capability_value': 'bar'
|
||||
},
|
||||
]
|
||||
|
||||
self.get_reservations_by_host = self.patch(
|
||||
self.db_utils, 'get_reservations_by_host_id')
|
||||
self.get_reservations_by_host.return_value = []
|
||||
|
||||
self.fake_phys_plugin.update_computehost(self.fake_host_id,
|
||||
host_values)
|
||||
self.db_host_extra_capability_update.assert_called_once_with(
|
||||
'1', {'capability_name': 'foo', 'capability_value': 'baz'})
|
||||
'extra_id1', {'capability_name': 'foo', 'capability_value': 'baz'})
|
||||
|
||||
def test_update_host_having_issue_when_storing_extra_capability(self):
|
||||
def fake_db_host_extra_capability_update(*args, **kwargs):
|
||||
raise RuntimeError
|
||||
host_values = {'foo': 'baz'}
|
||||
self.get_reservations_by_host = self.patch(
|
||||
self.db_utils, 'get_reservations_by_host_id')
|
||||
self.get_reservations_by_host.return_value = []
|
||||
self.db_host_extra_capability_get_all_per_name.return_value = [
|
||||
{'id': '1',
|
||||
{'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_name': 'foo',
|
||||
'capability_value': 'bar'
|
||||
},
|
||||
|
@ -268,17 +278,47 @@ class PhysicalHostPluginTestCase(tests.TestCase):
|
|||
self.fake_host_id, host_values)
|
||||
|
||||
def test_update_host_with_new_extra_capability(self):
|
||||
host_values = {'buzz': 'word'}
|
||||
host_values = {'qux': 'word'}
|
||||
|
||||
self.db_host_extra_capability_get_all_per_name.return_value = []
|
||||
self.db_host_extra_capability_get_all_per_host.return_value = []
|
||||
self.fake_phys_plugin.update_computehost(self.fake_host_id,
|
||||
host_values)
|
||||
self.db_host_extra_capability_create.assert_called_once_with({
|
||||
'computehost_id': '1',
|
||||
'capability_name': 'buzz',
|
||||
'capability_name': 'qux',
|
||||
'capability_value': 'word'
|
||||
})
|
||||
|
||||
def test_update_host_with_used_capability(self):
|
||||
host_values = {'foo': 'buzz'}
|
||||
|
||||
self.db_host_extra_capability_get_all_per_name.return_value = [
|
||||
{'id': 'extra_id1',
|
||||
'computehost_id': self.fake_host_id,
|
||||
'capability_name': 'foo',
|
||||
'capability_value': 'bar'
|
||||
},
|
||||
]
|
||||
fake_phys_reservation = {
|
||||
'resource_type': plugin.RESOURCE_TYPE,
|
||||
'resource_id': 'resource-1',
|
||||
}
|
||||
|
||||
fake_get_reservations = self.patch(self.db_utils,
|
||||
'get_reservations_by_host_id')
|
||||
fake_get_reservations.return_value = [fake_phys_reservation]
|
||||
|
||||
fake_get_plugin_reservation = self.patch(self.db_utils,
|
||||
'get_plugin_reservation')
|
||||
fake_get_plugin_reservation.return_value = {
|
||||
'resource_properties': '["==", "$foo", "bar"]'
|
||||
}
|
||||
self.assertRaises(manager_exceptions.CantAddExtraCapability,
|
||||
self.fake_phys_plugin.update_computehost,
|
||||
self.fake_host_id, host_values)
|
||||
fake_get_plugin_reservation.assert_called_once_with(
|
||||
plugin.RESOURCE_TYPE, 'resource-1')
|
||||
|
||||
def test_delete_host(self):
|
||||
host_allocation_get_all = self.patch(
|
||||
self.db_api,
|
||||
|
|
Loading…
Reference in New Issue