Import Ironic scheduler filters and host manager
This is an import of the Ironic scheduler changes as of commit da967d77894be6f23d81fb5cc948f9d13898ba84 implements bp: add-ironic-driver Co-authored-by: Adam Gandelman <adamg@ubuntu.com> Co-authored-by: ChangBo Guo(gcb) <eric.guo@easystack.cn> Co-authored-by: Chris Behrens <cbehrens@codestud.com> Co-authored-by: Chris Krelle <nobodycam@gmail.com> Co-authored-by: Devananda van der Veen <devananda.vdv@gmail.com> Co-authored-by: Fengqian Gao <fengqian.gao@intel.com> Co-authored-by: Hans Lindgren <hanlind@kth.se> Co-authored-by: Jenkins <jenkins@review.openstack.org> Co-authored-by: Lucas Alvares Gomes <lucasagomes@gmail.com> Co-authored-by: Michael Davies <michael@the-davies.net> Co-authored-by: Rohan Kanade <openstack@rohankanade.com> Co-authored-by: Zhongyue Luo <zhongyue.nah@intel.com> Change-Id: I358d9c0485c5dcf81498871faa9150e3bf167c6b
This commit is contained in:
parent
e6015ce523
commit
53c1794b59
|
@ -18,57 +18,25 @@
|
|||
Manage hosts in the current zone.
|
||||
"""
|
||||
|
||||
from nova.openstack.common import jsonutils
|
||||
import nova.scheduler.base_baremetal_host_manager as bbhm
|
||||
from nova.scheduler import host_manager
|
||||
|
||||
|
||||
class BaremetalNodeState(host_manager.HostState):
|
||||
class BaremetalNodeState(bbhm.BaseBaremetalNodeState):
|
||||
"""Mutable and immutable information tracked for a host.
|
||||
This is an attempt to remove the ad-hoc data structures
|
||||
previously used and lock down access.
|
||||
"""
|
||||
|
||||
def update_from_compute_node(self, compute):
|
||||
"""Update information about a host from its compute_node info."""
|
||||
all_ram_mb = compute['memory_mb']
|
||||
|
||||
free_disk_mb = compute['free_disk_gb'] * 1024
|
||||
free_ram_mb = compute['free_ram_mb']
|
||||
|
||||
self.free_ram_mb = free_ram_mb
|
||||
self.total_usable_ram_mb = all_ram_mb
|
||||
self.free_disk_mb = free_disk_mb
|
||||
self.vcpus_total = compute['vcpus']
|
||||
self.vcpus_used = compute['vcpus_used']
|
||||
|
||||
stats = compute.get('stats', '{}')
|
||||
self.stats = jsonutils.loads(stats)
|
||||
|
||||
def consume_from_instance(self, instance):
|
||||
self.free_ram_mb = 0
|
||||
self.free_disk_mb = 0
|
||||
self.vcpus_used = self.vcpus_total
|
||||
pass
|
||||
|
||||
|
||||
def new_host_state(self, host, node, **kwargs):
|
||||
"""Returns an instance of BaremetalNodeState or HostState according to
|
||||
compute['cpu_info']. If 'cpu_info' equals 'baremetal cpu', it returns an
|
||||
instance of BaremetalNodeState. If not, returns an instance of HostState.
|
||||
"""
|
||||
compute = kwargs.get('compute')
|
||||
|
||||
if compute and compute.get('cpu_info') == 'baremetal cpu':
|
||||
return BaremetalNodeState(host, node, **kwargs)
|
||||
else:
|
||||
return host_manager.HostState(host, node, **kwargs)
|
||||
|
||||
|
||||
class BaremetalHostManager(host_manager.HostManager):
|
||||
class BaremetalHostManager(bbhm.BaseBaremetalHostManager):
|
||||
"""Bare-Metal HostManager class."""
|
||||
|
||||
# Override.
|
||||
# Yes, this is not a class, and it is OK
|
||||
host_state_cls = new_host_state
|
||||
|
||||
def __init__(self):
|
||||
super(BaremetalHostManager, self).__init__()
|
||||
def host_state_cls(self, host, node, **kwargs):
|
||||
"""Factory function/property to create a new HostState."""
|
||||
compute = kwargs.get('compute')
|
||||
if compute and compute.get('cpu_info') == 'baremetal cpu':
|
||||
return BaremetalNodeState(host, node, **kwargs)
|
||||
else:
|
||||
return host_manager.HostState(host, node, **kwargs)
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright (c) 2011 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Manage hosts in the current zone.
|
||||
"""
|
||||
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova.scheduler import host_manager
|
||||
|
||||
|
||||
class BaseBaremetalNodeState(host_manager.HostState):
|
||||
"""Mutable and immutable information tracked for a host.
|
||||
This is an attempt to remove the ad-hoc data structures
|
||||
previously used and lock down access.
|
||||
"""
|
||||
|
||||
def update_from_compute_node(self, compute):
|
||||
"""Update information about a host from its compute_node info."""
|
||||
self.vcpus_total = compute['vcpus']
|
||||
self.vcpus_used = compute['vcpus_used']
|
||||
|
||||
self.free_ram_mb = compute['free_ram_mb']
|
||||
self.total_usable_ram_mb = compute['memory_mb']
|
||||
self.free_disk_mb = compute['free_disk_gb'] * 1024
|
||||
|
||||
stats = compute.get('stats', '{}')
|
||||
self.stats = jsonutils.loads(stats)
|
||||
|
||||
def consume_from_instance(self, instance):
|
||||
"""Consume nodes entire resources regardless of instance request."""
|
||||
self.free_ram_mb = 0
|
||||
self.free_disk_mb = 0
|
||||
self.vcpus_used = self.vcpus_total
|
||||
|
||||
|
||||
class BaseBaremetalHostManager(host_manager.HostManager):
|
||||
"""Base class for Baremetal and Ironic HostManager classes."""
|
||||
|
||||
def host_state_cls(self, host, node, **kwargs):
|
||||
"""Factory function to create a new HostState. May be overridden
|
||||
in subclasses to extend functionality.
|
||||
"""
|
||||
return BaseBaremetalNodeState(host, node, **kwargs)
|
|
@ -0,0 +1,51 @@
|
|||
# Copyright (c) 2014 OpenStack Foundation
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
from nova.i18n import _
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.scheduler import filters
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExactCoreFilter(filters.BaseHostFilter):
|
||||
"""Exact Core Filter."""
|
||||
|
||||
def host_passes(self, host_state, filter_properties):
|
||||
"""Return True if host has the exact number of CPU cores."""
|
||||
instance_type = filter_properties.get('instance_type')
|
||||
if not instance_type:
|
||||
return True
|
||||
|
||||
if not host_state.vcpus_total:
|
||||
# Fail safe
|
||||
LOG.warning(_("VCPUs not set; assuming CPU collection broken"))
|
||||
return False
|
||||
|
||||
required_vcpus = instance_type['vcpus']
|
||||
usable_vcpus = host_state.vcpus_total - host_state.vcpus_used
|
||||
|
||||
if required_vcpus != usable_vcpus:
|
||||
LOG.debug("%(host_state)s does not have exactly "
|
||||
"%(requested_vcpus)s cores of usable vcpu, it has "
|
||||
"%(usable_vcpus)s.",
|
||||
{'host_state': host_state,
|
||||
'requested_vcpus': required_vcpus,
|
||||
'usable_vcpus': usable_vcpus})
|
||||
return False
|
||||
|
||||
return True
|
|
@ -0,0 +1,41 @@
|
|||
# Copyright (c) 2014 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.scheduler import filters
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExactDiskFilter(filters.BaseHostFilter):
|
||||
"""Exact Disk Filter."""
|
||||
|
||||
def host_passes(self, host_state, filter_properties):
|
||||
"""Return True if host has the exact amount of disk available."""
|
||||
instance_type = filter_properties.get('instance_type')
|
||||
requested_disk = (1024 * (instance_type['root_gb'] +
|
||||
instance_type['ephemeral_gb']) +
|
||||
instance_type['swap'])
|
||||
|
||||
if requested_disk != host_state.free_disk_mb:
|
||||
LOG.debug("%(host_state)s does not have exactly "
|
||||
"%(requested_disk)s MB usable disk, it "
|
||||
"has %(usable_disk_mb)s.",
|
||||
{'host_state': host_state,
|
||||
'requested_disk': requested_disk,
|
||||
'usable_disk_mb': host_state.free_disk_mb})
|
||||
return False
|
||||
|
||||
return True
|
|
@ -0,0 +1,38 @@
|
|||
# Copyright (c) 2014 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.scheduler import filters
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ExactRamFilter(filters.BaseHostFilter):
|
||||
"""Exact RAM Filter."""
|
||||
|
||||
def host_passes(self, host_state, filter_properties):
|
||||
"""Return True if host has the exact amount of RAM available."""
|
||||
instance_type = filter_properties.get('instance_type')
|
||||
requested_ram = instance_type['memory_mb']
|
||||
if requested_ram != host_state.free_ram_mb:
|
||||
LOG.debug("%(host_state)s does not have exactly "
|
||||
"%(requested_ram)s MB usable RAM, it has "
|
||||
"%(usable_ram)s.",
|
||||
{'host_state': host_state,
|
||||
'requested_ram': requested_ram,
|
||||
'usable_ram': host_state.free_ram_mb})
|
||||
return False
|
||||
|
||||
return True
|
|
@ -0,0 +1,93 @@
|
|||
# Copyright (c) 2012 NTT DOCOMO, INC.
|
||||
# Copyright (c) 2011-2014 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Ironic host manager.
|
||||
|
||||
This host manager will consume all cpu's, disk space, and
|
||||
ram from a host / node as it is supporting Baremetal hosts, which can not be
|
||||
subdivided into multiple instances.
|
||||
"""
|
||||
from oslo.config import cfg
|
||||
|
||||
from nova.openstack.common import log as logging
|
||||
from nova.openstack.common import timeutils
|
||||
import nova.scheduler.base_baremetal_host_manager as bbhm
|
||||
from nova.scheduler import host_manager
|
||||
|
||||
host_manager_opts = [
|
||||
cfg.ListOpt('baremetal_scheduler_default_filters',
|
||||
default=[
|
||||
'RetryFilter',
|
||||
'AvailabilityZoneFilter',
|
||||
'ComputeFilter',
|
||||
'ComputeCapabilitiesFilter',
|
||||
'ImagePropertiesFilter',
|
||||
'ExactRamFilter',
|
||||
'ExactDiskFilter',
|
||||
'ExactCoreFilter',
|
||||
],
|
||||
help='Which filter class names to use for filtering '
|
||||
'baremetal hosts when not specified in the request.'),
|
||||
cfg.BoolOpt('scheduler_use_baremetal_filters',
|
||||
default=False,
|
||||
help='Flag to decide whether to use '
|
||||
'baremetal_scheduler_default_filters or not.'),
|
||||
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(host_manager_opts)
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IronicNodeState(bbhm.BaseBaremetalNodeState):
|
||||
"""Mutable and immutable information tracked for a host.
|
||||
This is an attempt to remove the ad-hoc data structures
|
||||
previously used and lock down access.
|
||||
"""
|
||||
|
||||
def update_from_compute_node(self, compute):
|
||||
"""Update information about a host from its compute_node info."""
|
||||
super(IronicNodeState, self).update_from_compute_node(compute)
|
||||
|
||||
self.total_usable_disk_gb = compute['local_gb']
|
||||
self.updated = compute['updated_at']
|
||||
|
||||
def consume_from_instance(self, instance):
|
||||
"""Consume nodes entire resources regardless of instance request."""
|
||||
super(IronicNodeState, self).consume_from_instance(instance)
|
||||
|
||||
self.updated = timeutils.utcnow()
|
||||
|
||||
|
||||
class IronicHostManager(bbhm.BaseBaremetalHostManager):
|
||||
"""Ironic HostManager class."""
|
||||
|
||||
def __init__(self):
|
||||
super(IronicHostManager, self).__init__()
|
||||
if CONF.scheduler_use_baremetal_filters:
|
||||
baremetal_default = CONF.baremetal_scheduler_default_filters
|
||||
CONF.scheduler_default_filters = baremetal_default
|
||||
|
||||
def host_state_cls(self, host, node, **kwargs):
|
||||
"""Factory function/property to create a new HostState."""
|
||||
compute = kwargs.get('compute')
|
||||
if compute and compute.get('cpu_info') == 'baremetal cpu':
|
||||
return IronicNodeState(host, node, **kwargs)
|
||||
else:
|
||||
return host_manager.HostState(host, node, **kwargs)
|
|
@ -0,0 +1,75 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
Fake nodes for Ironic host manager tests.
|
||||
"""
|
||||
|
||||
from nova.openstack.common import jsonutils
|
||||
|
||||
|
||||
COMPUTE_NODES = [
|
||||
dict(id=1, local_gb=10, memory_mb=1024, vcpus=1,
|
||||
vcpus_used=0, local_gb_used=0, memory_mb_used=0,
|
||||
updated_at=None, cpu_info='baremetal cpu',
|
||||
service=dict(host='host1', disabled=False),
|
||||
hypervisor_hostname='node1uuid', host_ip='127.0.0.1',
|
||||
hypervisor_version=1, hypervisor_type='ironic',
|
||||
stats=jsonutils.dumps(dict(ironic_driver=
|
||||
"nova.virt.ironic.driver.IronicDriver",
|
||||
cpu_arch='i386')),
|
||||
supported_instances='[["i386", "baremetal", "baremetal"]]',
|
||||
free_disk_gb=10, free_ram_mb=1024),
|
||||
dict(id=2, local_gb=20, memory_mb=2048, vcpus=1,
|
||||
vcpus_used=0, local_gb_used=0, memory_mb_used=0,
|
||||
updated_at=None, cpu_info='baremetal cpu',
|
||||
service=dict(host='host2', disabled=True),
|
||||
hypervisor_hostname='node2uuid', host_ip='127.0.0.1',
|
||||
hypervisor_version=1, hypervisor_type='ironic',
|
||||
stats=jsonutils.dumps(dict(ironic_driver=
|
||||
"nova.virt.ironic.driver.IronicDriver",
|
||||
cpu_arch='i386')),
|
||||
supported_instances='[["i386", "baremetal", "baremetal"]]',
|
||||
free_disk_gb=20, free_ram_mb=2048),
|
||||
dict(id=3, local_gb=30, memory_mb=3072, vcpus=1,
|
||||
vcpus_used=0, local_gb_used=0, memory_mb_used=0,
|
||||
updated_at=None, cpu_info='baremetal cpu',
|
||||
service=dict(host='host3', disabled=False),
|
||||
hypervisor_hostname='node3uuid', host_ip='127.0.0.1',
|
||||
hypervisor_version=1, hypervisor_type='ironic',
|
||||
stats=jsonutils.dumps(dict(ironic_driver=
|
||||
"nova.virt.ironic.driver.IronicDriver",
|
||||
cpu_arch='i386')),
|
||||
supported_instances='[["i386", "baremetal", "baremetal"]]',
|
||||
free_disk_gb=30, free_ram_mb=3072),
|
||||
dict(id=4, local_gb=40, memory_mb=4096, vcpus=1,
|
||||
vcpus_used=0, local_gb_used=0, memory_mb_used=0,
|
||||
updated_at=None, cpu_info='baremetal cpu',
|
||||
service=dict(host='host4', disabled=False),
|
||||
hypervisor_hostname='node4uuid', host_ip='127.0.0.1',
|
||||
hypervisor_version=1, hypervisor_type='ironic',
|
||||
stats=jsonutils.dumps(dict(ironic_driver=
|
||||
"nova.virt.ironic.driver.IronicDriver",
|
||||
cpu_arch='i386')),
|
||||
supported_instances='[["i386", "baremetal", "baremetal"]]',
|
||||
free_disk_gb=40, free_ram_mb=4096),
|
||||
# Broken entry
|
||||
dict(id=5, local_gb=50, memory_mb=5120, vcpus=1, service=None,
|
||||
cpu_info='baremetal cpu',
|
||||
stats=jsonutils.dumps(dict(ironic_driver=
|
||||
"nova.virt.ironic.driver.IronicDriver",
|
||||
cpu_arch='i386')),
|
||||
supported_instances='[["i386", "baremetal", "baremetal"]]',
|
||||
free_disk_gb=50, free_ram_mb=5120),
|
||||
]
|
|
@ -0,0 +1,412 @@
|
|||
# Copyright (c) 2014 OpenStack Foundation
|
||||
# Copyright (c) 2011 OpenStack Foundation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
"""
|
||||
Tests For IronicHostManager
|
||||
"""
|
||||
|
||||
import mock
|
||||
|
||||
from nova import db
|
||||
from nova import exception
|
||||
from nova.openstack.common import jsonutils
|
||||
from nova.scheduler import filters
|
||||
from nova.scheduler import host_manager
|
||||
from nova.scheduler import ironic_host_manager
|
||||
from nova import test
|
||||
from nova.tests.scheduler import ironic_fakes
|
||||
|
||||
|
||||
class FakeFilterClass1(filters.BaseHostFilter):
|
||||
def host_passes(self, host_state, filter_properties):
|
||||
pass
|
||||
|
||||
|
||||
class FakeFilterClass2(filters.BaseHostFilter):
|
||||
def host_passes(self, host_state, filter_properties):
|
||||
pass
|
||||
|
||||
|
||||
class IronicHostManagerTestCase(test.NoDBTestCase):
|
||||
"""Test case for IronicHostManager class."""
|
||||
|
||||
def setUp(self):
|
||||
super(IronicHostManagerTestCase, self).setUp()
|
||||
self.host_manager = ironic_host_manager.IronicHostManager()
|
||||
|
||||
def test_get_all_host_states(self):
|
||||
# Ensure .service is set and we have the values we expect to.
|
||||
context = 'fake_context'
|
||||
|
||||
self.mox.StubOutWithMock(db, 'compute_node_get_all')
|
||||
db.compute_node_get_all(context).AndReturn(ironic_fakes.COMPUTE_NODES)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.host_manager.get_all_host_states(context)
|
||||
host_states_map = self.host_manager.host_state_map
|
||||
|
||||
self.assertEqual(len(host_states_map), 4)
|
||||
for i in range(4):
|
||||
compute_node = ironic_fakes.COMPUTE_NODES[i]
|
||||
host = compute_node['service']['host']
|
||||
node = compute_node['hypervisor_hostname']
|
||||
state_key = (host, node)
|
||||
self.assertEqual(compute_node['service'],
|
||||
host_states_map[state_key].service)
|
||||
self.assertEqual(jsonutils.loads(compute_node['stats']),
|
||||
host_states_map[state_key].stats)
|
||||
self.assertEqual(compute_node['free_ram_mb'],
|
||||
host_states_map[state_key].free_ram_mb)
|
||||
self.assertEqual(compute_node['free_disk_gb'] * 1024,
|
||||
host_states_map[state_key].free_disk_mb)
|
||||
|
||||
|
||||
class IronicHostManagerChangedNodesTestCase(test.NoDBTestCase):
|
||||
"""Test case for IronicHostManager class."""
|
||||
|
||||
def setUp(self):
|
||||
super(IronicHostManagerChangedNodesTestCase, self).setUp()
|
||||
self.host_manager = ironic_host_manager.IronicHostManager()
|
||||
ironic_driver = "nova.virt.ironic.driver.IronicDriver"
|
||||
supported_instances = '[["i386", "baremetal", "baremetal"]]'
|
||||
self.compute_node = dict(id=1, local_gb=10, memory_mb=1024, vcpus=1,
|
||||
vcpus_used=0, local_gb_used=0, memory_mb_used=0,
|
||||
updated_at=None, cpu_info='baremetal cpu',
|
||||
stats=jsonutils.dumps(dict(
|
||||
ironic_driver=ironic_driver,
|
||||
cpu_arch='i386')),
|
||||
supported_instances=supported_instances,
|
||||
free_disk_gb=10, free_ram_mb=1024)
|
||||
|
||||
@mock.patch.object(ironic_host_manager.IronicNodeState, '__init__')
|
||||
def test_create_ironic_node_state(self, init_mock):
|
||||
init_mock.return_value = None
|
||||
compute = {'cpu_info': 'baremetal cpu'}
|
||||
host_state = self.host_manager.host_state_cls('fake-host', 'fake-node',
|
||||
compute=compute)
|
||||
self.assertIs(ironic_host_manager.IronicNodeState, type(host_state))
|
||||
|
||||
@mock.patch.object(host_manager.HostState, '__init__')
|
||||
def test_create_non_ironic_host_state(self, init_mock):
|
||||
init_mock.return_value = None
|
||||
compute = {'cpu_info': 'other cpu'}
|
||||
host_state = self.host_manager.host_state_cls('fake-host', 'fake-node',
|
||||
compute=compute)
|
||||
self.assertIs(host_manager.HostState, type(host_state))
|
||||
|
||||
def test_get_all_host_states_after_delete_one(self):
|
||||
context = 'fake_context'
|
||||
|
||||
self.mox.StubOutWithMock(db, 'compute_node_get_all')
|
||||
# all nodes active for first call
|
||||
db.compute_node_get_all(context).AndReturn(ironic_fakes.COMPUTE_NODES)
|
||||
# remove node4 for second call
|
||||
running_nodes = [n for n in ironic_fakes.COMPUTE_NODES
|
||||
if n.get('hypervisor_hostname') != 'node4uuid']
|
||||
db.compute_node_get_all(context).AndReturn(running_nodes)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.host_manager.get_all_host_states(context)
|
||||
self.host_manager.get_all_host_states(context)
|
||||
host_states_map = self.host_manager.host_state_map
|
||||
self.assertEqual(3, len(host_states_map))
|
||||
|
||||
def test_get_all_host_states_after_delete_all(self):
|
||||
context = 'fake_context'
|
||||
|
||||
self.mox.StubOutWithMock(db, 'compute_node_get_all')
|
||||
# all nodes active for first call
|
||||
db.compute_node_get_all(context).AndReturn(ironic_fakes.COMPUTE_NODES)
|
||||
# remove all nodes for second call
|
||||
db.compute_node_get_all(context).AndReturn([])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
self.host_manager.get_all_host_states(context)
|
||||
self.host_manager.get_all_host_states(context)
|
||||
host_states_map = self.host_manager.host_state_map
|
||||
self.assertEqual(0, len(host_states_map))
|
||||
|
||||
def test_update_from_compute_node(self):
|
||||
host = ironic_host_manager.IronicNodeState("fakehost", "fakenode")
|
||||
host.update_from_compute_node(self.compute_node)
|
||||
|
||||
self.assertEqual(1024, host.free_ram_mb)
|
||||
self.assertEqual(1024, host.total_usable_ram_mb)
|
||||
self.assertEqual(10240, host.free_disk_mb)
|
||||
self.assertEqual(1, host.vcpus_total)
|
||||
self.assertEqual(0, host.vcpus_used)
|
||||
self.assertEqual(jsonutils.loads(self.compute_node['stats']),
|
||||
host.stats)
|
||||
|
||||
def test_consume_identical_instance_from_compute(self):
|
||||
host = ironic_host_manager.IronicNodeState("fakehost", "fakenode")
|
||||
host.update_from_compute_node(self.compute_node)
|
||||
|
||||
instance = dict(root_gb=10, ephemeral_gb=0, memory_mb=1024, vcpus=1)
|
||||
host.consume_from_instance(instance)
|
||||
|
||||
self.assertEqual(1, host.vcpus_used)
|
||||
self.assertEqual(0, host.free_ram_mb)
|
||||
self.assertEqual(0, host.free_disk_mb)
|
||||
|
||||
def test_consume_larger_instance_from_compute(self):
|
||||
host = ironic_host_manager.IronicNodeState("fakehost", "fakenode")
|
||||
host.update_from_compute_node(self.compute_node)
|
||||
|
||||
instance = dict(root_gb=20, ephemeral_gb=0, memory_mb=2048, vcpus=2)
|
||||
host.consume_from_instance(instance)
|
||||
|
||||
self.assertEqual(1, host.vcpus_used)
|
||||
self.assertEqual(0, host.free_ram_mb)
|
||||
self.assertEqual(0, host.free_disk_mb)
|
||||
|
||||
def test_consume_smaller_instance_from_compute(self):
|
||||
host = ironic_host_manager.IronicNodeState("fakehost", "fakenode")
|
||||
host.update_from_compute_node(self.compute_node)
|
||||
|
||||
instance = dict(root_gb=5, ephemeral_gb=0, memory_mb=512, vcpus=1)
|
||||
host.consume_from_instance(instance)
|
||||
|
||||
self.assertEqual(1, host.vcpus_used)
|
||||
self.assertEqual(0, host.free_ram_mb)
|
||||
self.assertEqual(0, host.free_disk_mb)
|
||||
|
||||
|
||||
class IronicHostManagerTestFilters(test.NoDBTestCase):
|
||||
"""Test filters work for IronicHostManager."""
|
||||
|
||||
def setUp(self):
|
||||
super(IronicHostManagerTestFilters, self).setUp()
|
||||
self.host_manager = ironic_host_manager.IronicHostManager()
|
||||
self.fake_hosts = [ironic_host_manager.IronicNodeState(
|
||||
'fake_host%s' % x, 'fake-node') for x in range(1, 5)]
|
||||
self.fake_hosts += [ironic_host_manager.IronicNodeState(
|
||||
'fake_multihost', 'fake-node%s' % x) for x in range(1, 5)]
|
||||
|
||||
def test_choose_host_filters_not_found(self):
|
||||
self.flags(scheduler_default_filters='FakeFilterClass3')
|
||||
self.host_manager.filter_classes = [FakeFilterClass1,
|
||||
FakeFilterClass2]
|
||||
self.assertRaises(exception.SchedulerHostFilterNotFound,
|
||||
self.host_manager._choose_host_filters, None)
|
||||
|
||||
def test_choose_host_filters(self):
|
||||
self.flags(scheduler_default_filters=['FakeFilterClass2'])
|
||||
self.host_manager.filter_classes = [FakeFilterClass1,
|
||||
FakeFilterClass2]
|
||||
|
||||
# Test we returns 1 correct function
|
||||
filter_classes = self.host_manager._choose_host_filters(None)
|
||||
self.assertEqual(1, len(filter_classes))
|
||||
self.assertEqual('FakeFilterClass2', filter_classes[0].__name__)
|
||||
|
||||
def _mock_get_filtered_hosts(self, info, specified_filters=None):
|
||||
self.mox.StubOutWithMock(self.host_manager, '_choose_host_filters')
|
||||
|
||||
info['got_objs'] = []
|
||||
info['got_fprops'] = []
|
||||
|
||||
def fake_filter_one(_self, obj, filter_props):
|
||||
info['got_objs'].append(obj)
|
||||
info['got_fprops'].append(filter_props)
|
||||
return True
|
||||
|
||||
self.stubs.Set(FakeFilterClass1, '_filter_one', fake_filter_one)
|
||||
self.host_manager._choose_host_filters(specified_filters).AndReturn(
|
||||
[FakeFilterClass1])
|
||||
|
||||
def _verify_result(self, info, result, filters=True):
|
||||
for x in info['got_fprops']:
|
||||
self.assertEqual(x, info['expected_fprops'])
|
||||
if filters:
|
||||
self.assertEqual(set(info['expected_objs']), set(info['got_objs']))
|
||||
self.assertEqual(set(info['expected_objs']), set(result))
|
||||
|
||||
def test_get_filtered_hosts(self):
|
||||
fake_properties = {'moo': 1, 'cow': 2}
|
||||
|
||||
info = {'expected_objs': self.fake_hosts,
|
||||
'expected_fprops': fake_properties}
|
||||
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result)
|
||||
|
||||
def test_get_filtered_hosts_with_specified_filters(self):
|
||||
fake_properties = {'moo': 1, 'cow': 2}
|
||||
|
||||
specified_filters = ['FakeFilterClass1', 'FakeFilterClass2']
|
||||
info = {'expected_objs': self.fake_hosts,
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info, specified_filters)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties, filter_class_names=specified_filters)
|
||||
self._verify_result(info, result)
|
||||
|
||||
def test_get_filtered_hosts_with_ignore(self):
|
||||
fake_properties = {'ignore_hosts': ['fake_host1', 'fake_host3',
|
||||
'fake_host5', 'fake_multihost']}
|
||||
|
||||
# [1] and [3] are host2 and host4
|
||||
info = {'expected_objs': [self.fake_hosts[1], self.fake_hosts[3]],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result)
|
||||
|
||||
def test_get_filtered_hosts_with_force_hosts(self):
|
||||
fake_properties = {'force_hosts': ['fake_host1', 'fake_host3',
|
||||
'fake_host5']}
|
||||
|
||||
# [0] and [2] are host1 and host3
|
||||
info = {'expected_objs': [self.fake_hosts[0], self.fake_hosts[2]],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
||||
|
||||
def test_get_filtered_hosts_with_no_matching_force_hosts(self):
|
||||
fake_properties = {'force_hosts': ['fake_host5', 'fake_host6']}
|
||||
|
||||
info = {'expected_objs': [],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
||||
|
||||
def test_get_filtered_hosts_with_ignore_and_force_hosts(self):
|
||||
# Ensure ignore_hosts processed before force_hosts in host filters.
|
||||
fake_properties = {'force_hosts': ['fake_host3', 'fake_host1'],
|
||||
'ignore_hosts': ['fake_host1']}
|
||||
|
||||
# only fake_host3 should be left.
|
||||
info = {'expected_objs': [self.fake_hosts[2]],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
||||
|
||||
def test_get_filtered_hosts_with_force_host_and_many_nodes(self):
|
||||
# Ensure all nodes returned for a host with many nodes
|
||||
fake_properties = {'force_hosts': ['fake_multihost']}
|
||||
|
||||
info = {'expected_objs': [self.fake_hosts[4], self.fake_hosts[5],
|
||||
self.fake_hosts[6], self.fake_hosts[7]],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
||||
|
||||
def test_get_filtered_hosts_with_force_nodes(self):
|
||||
fake_properties = {'force_nodes': ['fake-node2', 'fake-node4',
|
||||
'fake-node9']}
|
||||
|
||||
# [5] is fake-node2, [7] is fake-node4
|
||||
info = {'expected_objs': [self.fake_hosts[5], self.fake_hosts[7]],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
||||
|
||||
def test_get_filtered_hosts_with_force_hosts_and_nodes(self):
|
||||
# Ensure only overlapping results if both force host and node
|
||||
fake_properties = {'force_hosts': ['fake_host1', 'fake_multihost'],
|
||||
'force_nodes': ['fake-node2', 'fake-node9']}
|
||||
|
||||
# [5] is fake-node2
|
||||
info = {'expected_objs': [self.fake_hosts[5]],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
||||
|
||||
def test_get_filtered_hosts_with_force_hosts_and_wrong_nodes(self):
|
||||
# Ensure non-overlapping force_node and force_host yield no result
|
||||
fake_properties = {'force_hosts': ['fake_multihost'],
|
||||
'force_nodes': ['fake-node']}
|
||||
|
||||
info = {'expected_objs': [],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
||||
|
||||
def test_get_filtered_hosts_with_ignore_hosts_and_force_nodes(self):
|
||||
# Ensure ignore_hosts can coexist with force_nodes
|
||||
fake_properties = {'force_nodes': ['fake-node4', 'fake-node2'],
|
||||
'ignore_hosts': ['fake_host1', 'fake_host2']}
|
||||
|
||||
info = {'expected_objs': [self.fake_hosts[5], self.fake_hosts[7]],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
||||
|
||||
def test_get_filtered_hosts_with_ignore_hosts_and_force_same_nodes(self):
|
||||
# Ensure ignore_hosts is processed before force_nodes
|
||||
fake_properties = {'force_nodes': ['fake_node4', 'fake_node2'],
|
||||
'ignore_hosts': ['fake_multihost']}
|
||||
|
||||
info = {'expected_objs': [],
|
||||
'expected_fprops': fake_properties}
|
||||
self._mock_get_filtered_hosts(info)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
result = self.host_manager.get_filtered_hosts(self.fake_hosts,
|
||||
fake_properties)
|
||||
self._verify_result(info, result, False)
|
Loading…
Reference in New Issue