Merge "Prevent the host update API from updating in-use extra capabilities"

This commit is contained in:
Zuul 2018-06-21 01:47:52 +00:00 committed by Gerrit Code Review
commit 11ed6d96c2
8 changed files with 199 additions and 60 deletions

View File

@ -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,

View File

@ -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)

View File

@ -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'

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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,

View File

@ -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,