Add extensible resources to resource tracker (2)
A resource plugin extension point is added to the resource tracker to allow the types of resources allocated at the compute node to be extensible. Information maintained by these plug-ins is written to the compute_nodes table in the database. The scheduler uses the information in the compute_nodes table to determine scheduling decisions. A plugin that implements vcpu resource tracking is included and all other code for tracking vcpu has been removed. This example ensures the plugins are tested in gate jobs. This was previously merged and reverted due to a bug affecting ironic CI. The bug was pre-existing but was exposed by the that patch. This change is based on the bug fix here: Icb19148660bca542a8120ecab064551d67ac28af and the previous version of this change is here: I64108338e3c958ba1276aaf113a68861cbe286f5 Co-Authored-By: Andrea Rosa <andrea.rosa@hp.com> Co-Authored-By: Paul Murray <pmurray@hp.com> This is part of: blueprint extensible-resource-tracking Change-Id: If1381f99fd7db420380288faf7b2f57553f69136
This commit is contained in:
parent
c08d32885d
commit
50c2d3d5d2
@ -42,10 +42,6 @@ class NopClaim(object):
|
||||
def memory_mb(self):
|
||||
return 0
|
||||
|
||||
@property
|
||||
def vcpus(self):
|
||||
return 0
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
@ -57,8 +53,8 @@ class NopClaim(object):
|
||||
pass
|
||||
|
||||
def __str__(self):
|
||||
return "[Claim: %d MB memory, %d GB disk, %d VCPUS]" % (self.memory_mb,
|
||||
self.disk_gb, self.vcpus)
|
||||
return "[Claim: %d MB memory, %d GB disk]" % (self.memory_mb,
|
||||
self.disk_gb)
|
||||
|
||||
|
||||
class Claim(NopClaim):
|
||||
@ -102,10 +98,6 @@ class Claim(NopClaim):
|
||||
def memory_mb(self):
|
||||
return self.instance['memory_mb'] + self.overhead['memory_mb']
|
||||
|
||||
@property
|
||||
def vcpus(self):
|
||||
return self.instance['vcpus']
|
||||
|
||||
def abort(self):
|
||||
"""Compute operation requiring claimed resources has failed or
|
||||
been aborted.
|
||||
@ -130,18 +122,16 @@ class Claim(NopClaim):
|
||||
# unlimited:
|
||||
memory_mb_limit = limits.get('memory_mb')
|
||||
disk_gb_limit = limits.get('disk_gb')
|
||||
vcpu_limit = limits.get('vcpu')
|
||||
|
||||
msg = _("Attempting claim: memory %(memory_mb)d MB, disk %(disk_gb)d "
|
||||
"GB, VCPUs %(vcpus)d")
|
||||
params = {'memory_mb': self.memory_mb, 'disk_gb': self.disk_gb,
|
||||
'vcpus': self.vcpus}
|
||||
"GB")
|
||||
params = {'memory_mb': self.memory_mb, 'disk_gb': self.disk_gb}
|
||||
LOG.audit(msg % params, instance=self.instance)
|
||||
|
||||
reasons = [self._test_memory(resources, memory_mb_limit),
|
||||
self._test_disk(resources, disk_gb_limit),
|
||||
self._test_cpu(resources, vcpu_limit),
|
||||
self._test_pci()]
|
||||
reasons = reasons + self._test_ext_resources(limits)
|
||||
reasons = [r for r in reasons if r is not None]
|
||||
if len(reasons) > 0:
|
||||
raise exception.ComputeResourcesUnavailable(reason=
|
||||
@ -176,14 +166,9 @@ class Claim(NopClaim):
|
||||
if not can_claim:
|
||||
return _('Claim pci failed.')
|
||||
|
||||
def _test_cpu(self, resources, limit):
|
||||
type_ = _("CPUs")
|
||||
unit = "VCPUs"
|
||||
total = resources['vcpus']
|
||||
used = resources['vcpus_used']
|
||||
requested = self.vcpus
|
||||
|
||||
return self._test(type_, unit, total, used, requested, limit)
|
||||
def _test_ext_resources(self, limits):
|
||||
return self.tracker.ext_resources_handler.test_resources(
|
||||
self.instance, limits)
|
||||
|
||||
def _test(self, type_, unit, total, used, requested, limit):
|
||||
"""Test if the given type of resource needed for a claim can be safely
|
||||
@ -235,10 +220,6 @@ class ResizeClaim(Claim):
|
||||
def memory_mb(self):
|
||||
return self.instance_type['memory_mb'] + self.overhead['memory_mb']
|
||||
|
||||
@property
|
||||
def vcpus(self):
|
||||
return self.instance_type['vcpus']
|
||||
|
||||
def _test_pci(self):
|
||||
pci_requests = pci_request.get_instance_pci_requests(
|
||||
self.instance, 'new_')
|
||||
@ -248,6 +229,10 @@ class ResizeClaim(Claim):
|
||||
if not claim:
|
||||
return _('Claim pci failed.')
|
||||
|
||||
def _test_ext_resources(self, limits):
|
||||
return self.tracker.ext_resources_handler.test_resources(
|
||||
self.instance_type, limits)
|
||||
|
||||
def abort(self):
|
||||
"""Compute operation requiring claimed resources has failed or
|
||||
been aborted.
|
||||
|
@ -24,6 +24,7 @@ from oslo.config import cfg
|
||||
from nova.compute import claims
|
||||
from nova.compute import flavors
|
||||
from nova.compute import monitors
|
||||
from nova.compute import resources as ext_resources
|
||||
from nova.compute import task_states
|
||||
from nova.compute import vm_states
|
||||
from nova import conductor
|
||||
@ -46,7 +47,10 @@ resource_tracker_opts = [
|
||||
help='Amount of memory in MB to reserve for the host'),
|
||||
cfg.StrOpt('compute_stats_class',
|
||||
default='nova.compute.stats.Stats',
|
||||
help='Class that will manage stats for the local compute host')
|
||||
help='Class that will manage stats for the local compute host'),
|
||||
cfg.ListOpt('compute_resources',
|
||||
default=['vcpu'],
|
||||
help='The names of the extra resources to track.'),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
@ -75,6 +79,8 @@ class ResourceTracker(object):
|
||||
self.conductor_api = conductor.API()
|
||||
monitor_handler = monitors.ResourceMonitorHandler()
|
||||
self.monitors = monitor_handler.choose_monitors(self)
|
||||
self.ext_resources_handler = \
|
||||
ext_resources.ResourceHandler(CONF.compute_resources)
|
||||
self.notifier = rpc.get_notifier()
|
||||
self.old_resources = {}
|
||||
|
||||
@ -229,12 +235,10 @@ class ResourceTracker(object):
|
||||
instance_type = self._get_instance_type(ctxt, instance, prefix)
|
||||
|
||||
if instance_type['id'] == itype['id']:
|
||||
self.stats.update_stats_for_migration(itype, sign=-1)
|
||||
if self.pci_tracker:
|
||||
self.pci_tracker.update_pci_for_migration(instance,
|
||||
sign=-1)
|
||||
self._update_usage(self.compute_node, itype, sign=-1)
|
||||
self.compute_node['stats'] = jsonutils.dumps(self.stats)
|
||||
|
||||
ctxt = context.get_admin_context()
|
||||
self._update(ctxt, self.compute_node)
|
||||
@ -377,9 +381,20 @@ class ResourceTracker(object):
|
||||
LOG.info(_('Compute_service record updated for %(host)s:%(node)s')
|
||||
% {'host': self.host, 'node': self.nodename})
|
||||
|
||||
def _write_ext_resources(self, resources):
|
||||
resources['stats'] = {}
|
||||
resources['stats'].update(self.stats)
|
||||
self.ext_resources_handler.write_resources(resources)
|
||||
|
||||
def _create(self, context, values):
|
||||
"""Create the compute node in the DB."""
|
||||
# initialize load stats from existing instances:
|
||||
self._write_ext_resources(values)
|
||||
# NOTE(pmurray): the stats field is stored as a json string. The
|
||||
# json conversion will be done automatically by the ComputeNode object
|
||||
# so this can be removed when using ComputeNode.
|
||||
values['stats'] = jsonutils.dumps(values['stats'])
|
||||
|
||||
self.compute_node = self.conductor_api.compute_node_create(context,
|
||||
values)
|
||||
|
||||
@ -449,6 +464,12 @@ class ResourceTracker(object):
|
||||
|
||||
def _update(self, context, values):
|
||||
"""Persist the compute node updates to the DB."""
|
||||
self._write_ext_resources(values)
|
||||
# NOTE(pmurray): the stats field is stored as a json string. The
|
||||
# json conversion will be done automatically by the ComputeNode object
|
||||
# so this can be removed when using ComputeNode.
|
||||
values['stats'] = jsonutils.dumps(values['stats'])
|
||||
|
||||
if not self._resource_change(values):
|
||||
return
|
||||
if "service" in self.compute_node:
|
||||
@ -475,7 +496,7 @@ class ResourceTracker(object):
|
||||
resources['local_gb_used'])
|
||||
|
||||
resources['running_vms'] = self.stats.num_instances
|
||||
resources['vcpus_used'] = self.stats.num_vcpus_used
|
||||
self.ext_resources_handler.update_from_instance(usage, sign)
|
||||
|
||||
def _update_usage_from_migration(self, context, instance, resources,
|
||||
migration):
|
||||
@ -518,11 +539,9 @@ class ResourceTracker(object):
|
||||
migration['old_instance_type_id'])
|
||||
|
||||
if itype:
|
||||
self.stats.update_stats_for_migration(itype)
|
||||
if self.pci_tracker:
|
||||
self.pci_tracker.update_pci_for_migration(instance)
|
||||
self._update_usage(resources, itype)
|
||||
resources['stats'] = jsonutils.dumps(self.stats)
|
||||
if self.pci_tracker:
|
||||
resources['pci_stats'] = jsonutils.dumps(
|
||||
self.pci_tracker.stats)
|
||||
@ -595,7 +614,6 @@ class ResourceTracker(object):
|
||||
self._update_usage(resources, instance, sign=sign)
|
||||
|
||||
resources['current_workload'] = self.stats.calculate_workload()
|
||||
resources['stats'] = jsonutils.dumps(self.stats)
|
||||
if self.pci_tracker:
|
||||
resources['pci_stats'] = jsonutils.dumps(self.pci_tracker.stats)
|
||||
else:
|
||||
@ -612,12 +630,10 @@ class ResourceTracker(object):
|
||||
# purge old stats and init with anything passed in by the driver
|
||||
self.stats.clear()
|
||||
self.stats.digest_stats(resources.get('stats'))
|
||||
resources['stats'] = jsonutils.dumps(self.stats)
|
||||
|
||||
# set some initial values, reserve room for host/hypervisor:
|
||||
resources['local_gb_used'] = CONF.reserved_host_disk_mb / 1024
|
||||
resources['memory_mb_used'] = CONF.reserved_host_memory_mb
|
||||
resources['vcpus_used'] = 0
|
||||
resources['free_ram_mb'] = (resources['memory_mb'] -
|
||||
resources['memory_mb_used'])
|
||||
resources['free_disk_gb'] = (resources['local_gb'] -
|
||||
@ -625,6 +641,9 @@ class ResourceTracker(object):
|
||||
resources['current_workload'] = 0
|
||||
resources['running_vms'] = 0
|
||||
|
||||
# Reset values for extended resources
|
||||
self.ext_resources_handler.reset_resources(resources, self.driver)
|
||||
|
||||
for instance in instances:
|
||||
if instance['vm_state'] == vm_states.DELETED:
|
||||
continue
|
||||
|
133
nova/compute/resources/__init__.py
Normal file
133
nova/compute/resources/__init__.py
Normal file
@ -0,0 +1,133 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
# 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.
|
||||
|
||||
import stevedore
|
||||
|
||||
from nova.i18n import _LW
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
RESOURCE_NAMESPACE = 'nova.compute.resources'
|
||||
|
||||
|
||||
class ResourceHandler():
|
||||
|
||||
def _log_missing_plugins(self, names):
|
||||
for name in names:
|
||||
if name not in self._mgr.names():
|
||||
LOG.warn(_LW('Compute resource plugin %s was not loaded') %
|
||||
name)
|
||||
|
||||
def __init__(self, names, propagate_map_exceptions=False):
|
||||
"""Initialise the resource handler by loading the plugins.
|
||||
|
||||
The ResourceHandler uses stevedore to load the resource plugins.
|
||||
The handler can handle and report exceptions raised in the plugins
|
||||
depending on the value of the propagate_map_exceptions parameter.
|
||||
It is useful in testing to propagate exceptions so they are exposed
|
||||
as part of the test. If exceptions are not propagated they are
|
||||
logged at error level.
|
||||
|
||||
Any named plugins that are not located are logged.
|
||||
|
||||
:param names: the list of plugins to load by name
|
||||
:param propagate_map_exceptions: True indicates exceptions in the
|
||||
plugins should be raised, False indicates they should be handled and
|
||||
logged.
|
||||
"""
|
||||
self._mgr = stevedore.NamedExtensionManager(
|
||||
namespace=RESOURCE_NAMESPACE,
|
||||
names=names,
|
||||
propagate_map_exceptions=propagate_map_exceptions,
|
||||
invoke_on_load=True)
|
||||
self._log_missing_plugins(names)
|
||||
|
||||
def reset_resources(self, resources, driver):
|
||||
"""Reset the resources to their initial state.
|
||||
|
||||
Each plugin is called to reset its state. The resources data provided
|
||||
is initial state gathered from the hypervisor. The driver is also
|
||||
provided in case the plugin needs to obtain additional information
|
||||
from the driver, for example, the memory calculation obtains
|
||||
the memory overhead from the driver.
|
||||
|
||||
:param resources: the resources reported by the hypervisor
|
||||
:param driver: the driver for the hypervisor
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
if self._mgr.extensions:
|
||||
self._mgr.map_method('reset', resources, driver)
|
||||
|
||||
def test_resources(self, usage, limits):
|
||||
"""Test the ability to support the given instance.
|
||||
|
||||
Each resource plugin is called to determine if it's resource is able
|
||||
to support the additional requirements of a new instance. The
|
||||
plugins either return None to indicate they have sufficient resource
|
||||
available or a human readable string to indicate why they can not.
|
||||
|
||||
:param usage: the additional resource usage
|
||||
:param limits: limits used for the calculation
|
||||
|
||||
:returns: a list or return values from the plugins
|
||||
"""
|
||||
if not self._mgr.extensions:
|
||||
return []
|
||||
|
||||
reasons = self._mgr.map_method('test', usage, limits)
|
||||
return reasons
|
||||
|
||||
def update_from_instance(self, usage, sign=1):
|
||||
"""Update the resource information to reflect the allocation for
|
||||
an instance with the given resource usage.
|
||||
|
||||
:param usage: the resource usage of the instance
|
||||
:param sign: has value 1 or -1. 1 indicates the instance is being
|
||||
added to the current usage, -1 indicates the instance is being removed.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
if not self._mgr.extensions:
|
||||
return
|
||||
|
||||
if sign == 1:
|
||||
self._mgr.map_method('add_instance', usage)
|
||||
else:
|
||||
self._mgr.map_method('remove_instance', usage)
|
||||
|
||||
def write_resources(self, resources):
|
||||
"""Write the resource data to populate the resources.
|
||||
|
||||
Each resource plugin is called to write its resource data to
|
||||
resources.
|
||||
|
||||
:param resources: the compute node resources
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
if self._mgr.extensions:
|
||||
self._mgr.map_method('write', resources)
|
||||
|
||||
def report_free_resources(self):
|
||||
"""Each resource plugin is called to log free resource information.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
if not self._mgr.extensions:
|
||||
return
|
||||
|
||||
self._mgr.map_method('report_free')
|
93
nova/compute/resources/base.py
Normal file
93
nova/compute/resources/base.py
Normal file
@ -0,0 +1,93 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
# 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.
|
||||
|
||||
|
||||
import abc
|
||||
|
||||
import six
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class Resource(object):
|
||||
"""This base class defines the interface used for compute resource
|
||||
plugins. It is not necessary to use this base class, but all compute
|
||||
resource plugins must implement the abstract methods found here.
|
||||
An instance of the plugin object is instantiated when it is loaded
|
||||
by calling __init__() with no parameters.
|
||||
"""
|
||||
|
||||
@abc.abstractmethod
|
||||
def reset(self, resources, driver):
|
||||
"""Set the resource to an initial state based on the resource
|
||||
view discovered from the hypervisor.
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def test(self, usage, limits):
|
||||
"""Test to see if we have sufficient resources to allocate for
|
||||
an instance with the given resource usage.
|
||||
|
||||
:param usage: the resource usage of the instances
|
||||
:param limits: limits to apply
|
||||
|
||||
:returns: None if the test passes or a string describing the reason
|
||||
why the test failed
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def add_instance(self, usage):
|
||||
"""Update resource information adding allocation according to the
|
||||
given resource usage.
|
||||
|
||||
:param usage: the resource usage of the instance being added
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def remove_instance(self, usage):
|
||||
"""Update resource information removing allocation according to the
|
||||
given resource usage.
|
||||
|
||||
:param usage: the resource usage of the instance being removed
|
||||
|
||||
:returns: None
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def write(self, resources):
|
||||
"""Write resource data to populate resources.
|
||||
|
||||
:param resources: the resources data to be populated
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def report_free(self):
|
||||
"""Log free resources.
|
||||
|
||||
This method logs how much free resource is held by
|
||||
the resource plugin.
|
||||
|
||||
:returns: None
|
||||
"""
|
||||
pass
|
83
nova/compute/resources/vcpu.py
Normal file
83
nova/compute/resources/vcpu.py
Normal file
@ -0,0 +1,83 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
# 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.compute.resources import base
|
||||
from nova.openstack.common import log as logging
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VCPU(base.Resource):
|
||||
"""VCPU compute resource plugin.
|
||||
|
||||
This is effectively a simple counter based on the vcpu requirement of each
|
||||
instance.
|
||||
"""
|
||||
def __init__(self):
|
||||
# initialize to a 'zero' resource.
|
||||
# reset will be called to set real resource values
|
||||
self._total = 0
|
||||
self._used = 0
|
||||
|
||||
def reset(self, resources, driver):
|
||||
# total vcpu is reset to the value taken from resources.
|
||||
self._total = int(resources['vcpus'])
|
||||
self._used = 0
|
||||
|
||||
def _get_requested(self, usage):
|
||||
return int(usage.get('vcpus', 0))
|
||||
|
||||
def _get_limit(self, limits):
|
||||
if limits and 'vcpu' in limits:
|
||||
return int(limits.get('vcpu'))
|
||||
|
||||
def test(self, usage, limits):
|
||||
requested = self._get_requested(usage)
|
||||
limit = self._get_limit(limits)
|
||||
|
||||
LOG.debug('Total CPUs: %(total)d VCPUs, used: %(used).02f VCPUs' %
|
||||
{'total': self._total, 'used': self._used})
|
||||
|
||||
if limit is None:
|
||||
# treat resource as unlimited:
|
||||
LOG.debug('CPUs limit not specified, defaulting to unlimited')
|
||||
return
|
||||
|
||||
free = limit - self._used
|
||||
|
||||
# Oversubscribed resource policy info:
|
||||
LOG.debug('CPUs limit: %(limit).02f VCPUs, free: %(free).02f VCPUs' %
|
||||
{'limit': limit, 'free': free})
|
||||
|
||||
if requested > free:
|
||||
return ('Free CPUs %(free).02f VCPUs < '
|
||||
'requested %(requested)d VCPUs' %
|
||||
{'free': free, 'requested': requested})
|
||||
|
||||
def add_instance(self, usage):
|
||||
requested = int(usage.get('vcpus', 0))
|
||||
self._used += requested
|
||||
|
||||
def remove_instance(self, usage):
|
||||
requested = int(usage.get('vcpus', 0))
|
||||
self._used -= requested
|
||||
|
||||
def write(self, resources):
|
||||
resources['vcpus'] = self._total
|
||||
resources['vcpus_used'] = self._used
|
||||
|
||||
def report_free(self):
|
||||
free_vcpus = self._total - self._used
|
||||
LOG.debug('Free VCPUs: %s' % free_vcpus)
|
@ -90,10 +90,6 @@ class Stats(dict):
|
||||
key = "num_os_type_%s" % os_type
|
||||
return self.get(key, 0)
|
||||
|
||||
@property
|
||||
def num_vcpus_used(self):
|
||||
return self.get("num_vcpus_used", 0)
|
||||
|
||||
def update_stats_for_instance(self, instance):
|
||||
"""Update stats after an instance is changed."""
|
||||
|
||||
@ -108,14 +104,12 @@ class Stats(dict):
|
||||
self._decrement("num_task_%s" % old_state['task_state'])
|
||||
self._decrement("num_os_type_%s" % old_state['os_type'])
|
||||
self._decrement("num_proj_%s" % old_state['project_id'])
|
||||
x = self.get("num_vcpus_used", 0)
|
||||
self["num_vcpus_used"] = x - old_state['vcpus']
|
||||
else:
|
||||
# new instance
|
||||
self._increment("num_instances")
|
||||
|
||||
# Now update stats from the new instance state:
|
||||
(vm_state, task_state, os_type, project_id, vcpus) = \
|
||||
(vm_state, task_state, os_type, project_id) = \
|
||||
self._extract_state_from_instance(instance)
|
||||
|
||||
if vm_state == vm_states.DELETED:
|
||||
@ -127,16 +121,10 @@ class Stats(dict):
|
||||
self._increment("num_task_%s" % task_state)
|
||||
self._increment("num_os_type_%s" % os_type)
|
||||
self._increment("num_proj_%s" % project_id)
|
||||
x = self.get("num_vcpus_used", 0)
|
||||
self["num_vcpus_used"] = x + vcpus
|
||||
|
||||
# save updated I/O workload in stats:
|
||||
self["io_workload"] = self.io_workload
|
||||
|
||||
def update_stats_for_migration(self, instance_type, sign=1):
|
||||
x = self.get("num_vcpus_used", 0)
|
||||
self["num_vcpus_used"] = x + (sign * instance_type['vcpus'])
|
||||
|
||||
def _decrement(self, key):
|
||||
x = self.get(key, 0)
|
||||
self[key] = x - 1
|
||||
@ -153,10 +141,8 @@ class Stats(dict):
|
||||
task_state = instance['task_state']
|
||||
os_type = instance['os_type']
|
||||
project_id = instance['project_id']
|
||||
vcpus = instance['vcpus']
|
||||
|
||||
self.states[uuid] = dict(vm_state=vm_state, task_state=task_state,
|
||||
os_type=os_type, project_id=project_id,
|
||||
vcpus=vcpus)
|
||||
os_type=os_type, project_id=project_id)
|
||||
|
||||
return (vm_state, task_state, os_type, project_id, vcpus)
|
||||
return (vm_state, task_state, os_type, project_id)
|
||||
|
@ -20,10 +20,12 @@ class FakeResourceTracker(resource_tracker.ResourceTracker):
|
||||
"""Version without a DB requirement."""
|
||||
|
||||
def _create(self, context, values):
|
||||
self._write_ext_resources(values)
|
||||
self.compute_node = values
|
||||
self.compute_node['id'] = 1
|
||||
|
||||
def _update(self, context, values, prune_stats=False):
|
||||
self._write_ext_resources(values)
|
||||
self.compute_node.update(values)
|
||||
|
||||
def _get_service(self, context):
|
||||
|
@ -25,10 +25,21 @@ from nova.pci import pci_manager
|
||||
from nova import test
|
||||
|
||||
|
||||
class FakeResourceHandler(object):
|
||||
test_called = False
|
||||
usage_is_instance = False
|
||||
|
||||
def test_resources(self, usage, limits):
|
||||
self.test_called = True
|
||||
self.usage_is_itype = usage.get('name') is 'fakeitype'
|
||||
return []
|
||||
|
||||
|
||||
class DummyTracker(object):
|
||||
icalled = False
|
||||
rcalled = False
|
||||
pci_tracker = pci_manager.PciDevTracker()
|
||||
ext_resources_handler = FakeResourceHandler()
|
||||
|
||||
def abort_instance_claim(self, *args, **kwargs):
|
||||
self.icalled = True
|
||||
@ -101,9 +112,6 @@ class ClaimTestCase(test.NoDBTestCase):
|
||||
except e as ee:
|
||||
self.assertTrue(re.search(re_obj, str(ee)))
|
||||
|
||||
def test_cpu_unlimited(self):
|
||||
self._claim(vcpus=100000)
|
||||
|
||||
def test_memory_unlimited(self):
|
||||
self._claim(memory_mb=99999999)
|
||||
|
||||
@ -113,10 +121,6 @@ class ClaimTestCase(test.NoDBTestCase):
|
||||
def test_disk_unlimited_ephemeral(self):
|
||||
self._claim(ephemeral_gb=999999)
|
||||
|
||||
def test_cpu_oversubscription(self):
|
||||
limits = {'vcpu': 16}
|
||||
self._claim(limits, vcpus=8)
|
||||
|
||||
def test_memory_with_overhead(self):
|
||||
overhead = {'memory_mb': 8}
|
||||
limits = {'memory_mb': 2048}
|
||||
@ -131,11 +135,6 @@ class ClaimTestCase(test.NoDBTestCase):
|
||||
self._claim, limits=limits, overhead=overhead,
|
||||
memory_mb=2040)
|
||||
|
||||
def test_cpu_insufficient(self):
|
||||
limits = {'vcpu': 16}
|
||||
self.assertRaises(exception.ComputeResourcesUnavailable,
|
||||
self._claim, limits=limits, vcpus=17)
|
||||
|
||||
def test_memory_oversubscription(self):
|
||||
self._claim(memory_mb=4096)
|
||||
|
||||
@ -162,21 +161,6 @@ class ClaimTestCase(test.NoDBTestCase):
|
||||
self._claim, limits=limits, root_gb=10, ephemeral_gb=40,
|
||||
memory_mb=16384)
|
||||
|
||||
def test_disk_and_cpu_insufficient(self):
|
||||
limits = {'disk_gb': 45, 'vcpu': 16}
|
||||
self.assertRaisesRegexp(re.compile("disk.*vcpus", re.IGNORECASE),
|
||||
exception.ComputeResourcesUnavailable,
|
||||
self._claim, limits=limits, root_gb=10, ephemeral_gb=40,
|
||||
vcpus=17)
|
||||
|
||||
def test_disk_and_cpu_and_memory_insufficient(self):
|
||||
limits = {'disk_gb': 45, 'vcpu': 16, 'memory_mb': 8192}
|
||||
pat = "memory.*disk.*vcpus"
|
||||
self.assertRaisesRegexp(re.compile(pat, re.IGNORECASE),
|
||||
exception.ComputeResourcesUnavailable,
|
||||
self._claim, limits=limits, root_gb=10, ephemeral_gb=40,
|
||||
vcpus=17, memory_mb=16384)
|
||||
|
||||
def test_pci_pass(self):
|
||||
dev_dict = {
|
||||
'compute_node_id': 1,
|
||||
@ -224,6 +208,11 @@ class ClaimTestCase(test.NoDBTestCase):
|
||||
self._set_pci_request(claim)
|
||||
claim._test_pci()
|
||||
|
||||
def test_ext_resources(self):
|
||||
self._claim()
|
||||
self.assertTrue(self.tracker.ext_resources_handler.test_called)
|
||||
self.assertFalse(self.tracker.ext_resources_handler.usage_is_itype)
|
||||
|
||||
def test_abort(self):
|
||||
claim = self._abort()
|
||||
self.assertTrue(claim.tracker.icalled)
|
||||
@ -260,6 +249,11 @@ class ResizeClaimTestCase(ClaimTestCase):
|
||||
claim.instance.update(
|
||||
system_metadata={'new_pci_requests': jsonutils.dumps(request)})
|
||||
|
||||
def test_ext_resources(self):
|
||||
self._claim()
|
||||
self.assertTrue(self.tracker.ext_resources_handler.test_called)
|
||||
self.assertTrue(self.tracker.ext_resources_handler.usage_is_itype)
|
||||
|
||||
def test_abort(self):
|
||||
claim = self._abort()
|
||||
self.assertTrue(claim.tracker.rcalled)
|
||||
|
@ -22,6 +22,7 @@ from oslo.config import cfg
|
||||
|
||||
from nova.compute import flavors
|
||||
from nova.compute import resource_tracker
|
||||
from nova.compute import resources
|
||||
from nova.compute import task_states
|
||||
from nova.compute import vm_states
|
||||
from nova import context
|
||||
@ -47,6 +48,7 @@ FAKE_VIRT_LOCAL_GB = ROOT_GB + EPHEMERAL_GB
|
||||
FAKE_VIRT_VCPUS = 1
|
||||
FAKE_VIRT_STATS = {'virt_stat': 10}
|
||||
FAKE_VIRT_STATS_JSON = jsonutils.dumps(FAKE_VIRT_STATS)
|
||||
RESOURCE_NAMES = ['vcpu']
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
@ -165,8 +167,10 @@ class BaseTestCase(test.TestCase):
|
||||
"current_workload": 1,
|
||||
"running_vms": 0,
|
||||
"cpu_info": None,
|
||||
"stats": [{"key": "num_instances", "value": "1"}],
|
||||
"hypervisor_hostname": "fakenode",
|
||||
"stats": {
|
||||
"num_instances": "1",
|
||||
},
|
||||
"hypervisor_hostname": "fakenode",
|
||||
}
|
||||
if values:
|
||||
compute.update(values)
|
||||
@ -319,6 +323,8 @@ class BaseTestCase(test.TestCase):
|
||||
driver = self._driver()
|
||||
|
||||
tracker = resource_tracker.ResourceTracker(host, driver, node)
|
||||
tracker.ext_resources_handler = \
|
||||
resources.ResourceHandler(RESOURCE_NAMES, True)
|
||||
return tracker
|
||||
|
||||
|
||||
@ -574,6 +580,38 @@ class TrackerPciStatsTestCase(BaseTrackerTestCase):
|
||||
return FakeVirtDriver(pci_support=True)
|
||||
|
||||
|
||||
class TrackerExtraResourcesTestCase(BaseTrackerTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TrackerExtraResourcesTestCase, self).setUp()
|
||||
self.driver = self._driver()
|
||||
|
||||
def _driver(self):
|
||||
return FakeVirtDriver()
|
||||
|
||||
def test_set_empty_ext_resources(self):
|
||||
resources = self.driver.get_available_resource(self.tracker.nodename)
|
||||
self.assertNotIn('stats', resources)
|
||||
self.tracker._write_ext_resources(resources)
|
||||
self.assertIn('stats', resources)
|
||||
|
||||
def test_set_extra_resources(self):
|
||||
def fake_write_resources(resources):
|
||||
resources['stats']['resA'] = '123'
|
||||
resources['stats']['resB'] = 12
|
||||
|
||||
self.stubs.Set(self.tracker.ext_resources_handler,
|
||||
'write_resources',
|
||||
fake_write_resources)
|
||||
|
||||
resources = self.driver.get_available_resource(self.tracker.nodename)
|
||||
self.tracker._write_ext_resources(resources)
|
||||
|
||||
expected = {"resA": "123", "resB": 12}
|
||||
self.assertEqual(sorted(expected),
|
||||
sorted(resources['stats']))
|
||||
|
||||
|
||||
class InstanceClaimTestCase(BaseTrackerTestCase):
|
||||
|
||||
def test_update_usage_only_for_tracked(self):
|
||||
|
344
nova/tests/compute/test_resources.py
Normal file
344
nova/tests/compute/test_resources.py
Normal file
@ -0,0 +1,344 @@
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
# 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 the compute extra resources framework."""
|
||||
|
||||
|
||||
from oslo.config import cfg
|
||||
from stevedore import extension
|
||||
from stevedore import named
|
||||
|
||||
from nova.compute import resources
|
||||
from nova.compute.resources import base
|
||||
from nova.compute.resources import vcpu
|
||||
from nova import context
|
||||
from nova.i18n import _
|
||||
from nova.objects import flavor as flavor_obj
|
||||
from nova import test
|
||||
from nova.tests.fake_instance import fake_instance_obj
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class FakeResourceHandler(resources.ResourceHandler):
|
||||
def __init__(self, extensions):
|
||||
self._mgr = \
|
||||
named.NamedExtensionManager.make_test_instance(extensions)
|
||||
|
||||
|
||||
class FakeResource(base.Resource):
|
||||
|
||||
def __init__(self):
|
||||
self.total_res = 0
|
||||
self.used_res = 0
|
||||
|
||||
def _get_requested(self, usage):
|
||||
if 'extra_specs' not in usage:
|
||||
return
|
||||
if self.resource_name not in usage['extra_specs']:
|
||||
return
|
||||
req = usage['extra_specs'][self.resource_name]
|
||||
return int(req)
|
||||
|
||||
def _get_limit(self, limits):
|
||||
if self.resource_name not in limits:
|
||||
return
|
||||
limit = limits[self.resource_name]
|
||||
return int(limit)
|
||||
|
||||
def reset(self, resources, driver):
|
||||
self.total_res = 0
|
||||
self.used_res = 0
|
||||
|
||||
def test(self, usage, limits):
|
||||
requested = self._get_requested(usage)
|
||||
if not requested:
|
||||
return
|
||||
|
||||
limit = self._get_limit(limits)
|
||||
if not limit:
|
||||
return
|
||||
|
||||
free = limit - self.used_res
|
||||
if requested <= free:
|
||||
return
|
||||
else:
|
||||
return (_('Free %(free)d < requested %(requested)d ') %
|
||||
{'free': free, 'requested': requested})
|
||||
|
||||
def add_instance(self, usage):
|
||||
requested = self._get_requested(usage)
|
||||
if requested:
|
||||
self.used_res += requested
|
||||
|
||||
def remove_instance(self, usage):
|
||||
requested = self._get_requested(usage)
|
||||
if requested:
|
||||
self.used_res -= requested
|
||||
|
||||
def write(self, resources):
|
||||
pass
|
||||
|
||||
def report_free(self):
|
||||
return "Free %s" % (self.total_res - self.used_res)
|
||||
|
||||
|
||||
class ResourceA(FakeResource):
|
||||
|
||||
def reset(self, resources, driver):
|
||||
# ResourceA uses a configuration option
|
||||
self.total_res = int(CONF.resA)
|
||||
self.used_res = 0
|
||||
self.resource_name = 'resource:resA'
|
||||
|
||||
def write(self, resources):
|
||||
resources['resA'] = self.total_res
|
||||
resources['used_resA'] = self.used_res
|
||||
|
||||
|
||||
class ResourceB(FakeResource):
|
||||
|
||||
def reset(self, resources, driver):
|
||||
# ResourceB uses resource details passed in parameter resources
|
||||
self.total_res = resources['resB']
|
||||
self.used_res = 0
|
||||
self.resource_name = 'resource:resB'
|
||||
|
||||
def write(self, resources):
|
||||
resources['resB'] = self.total_res
|
||||
resources['used_resB'] = self.used_res
|
||||
|
||||
|
||||
def fake_flavor_obj(**updates):
|
||||
flavor = flavor_obj.Flavor()
|
||||
flavor.id = 1
|
||||
flavor.name = 'fakeflavor'
|
||||
flavor.memory_mb = 8000
|
||||
flavor.vcpus = 3
|
||||
flavor.root_gb = 11
|
||||
flavor.ephemeral_gb = 4
|
||||
flavor.swap = 0
|
||||
flavor.rxtx_factor = 1.0
|
||||
flavor.vcpu_weight = 1
|
||||
if updates:
|
||||
flavor.update(updates)
|
||||
return flavor
|
||||
|
||||
|
||||
class BaseTestCase(test.TestCase):
|
||||
|
||||
def _initialize_used_res_counter(self):
|
||||
# Initialize the value for the used resource
|
||||
for ext in self.r_handler._mgr.extensions:
|
||||
ext.obj.used_res = 0
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
|
||||
# initialize flavors and stub get_by_id to
|
||||
# get flavors from here
|
||||
self._flavors = {}
|
||||
self.ctxt = context.get_admin_context()
|
||||
|
||||
# Create a flavor without extra_specs defined
|
||||
_flavor_id = 1
|
||||
_flavor = fake_flavor_obj(id=_flavor_id)
|
||||
self._flavors[_flavor_id] = _flavor
|
||||
|
||||
# Create a flavor with extra_specs defined
|
||||
_flavor_id = 2
|
||||
requested_resA = 5
|
||||
requested_resB = 7
|
||||
requested_resC = 7
|
||||
_extra_specs = {'resource:resA': requested_resA,
|
||||
'resource:resB': requested_resB,
|
||||
'resource:resC': requested_resC}
|
||||
_flavor = fake_flavor_obj(id=_flavor_id,
|
||||
extra_specs=_extra_specs)
|
||||
self._flavors[_flavor_id] = _flavor
|
||||
|
||||
# create fake resource extensions and resource handler
|
||||
_extensions = [
|
||||
extension.Extension('resA', None, ResourceA, ResourceA()),
|
||||
extension.Extension('resB', None, ResourceB, ResourceB()),
|
||||
]
|
||||
self.r_handler = FakeResourceHandler(_extensions)
|
||||
|
||||
# Resources details can be passed to each plugin or can be specified as
|
||||
# configuration options
|
||||
driver_resources = {'resB': 5}
|
||||
CONF.resA = '10'
|
||||
|
||||
# initialise the resources
|
||||
self.r_handler.reset_resources(driver_resources, None)
|
||||
|
||||
def test_update_from_instance_with_extra_specs(self):
|
||||
# Flavor with extra_specs
|
||||
_flavor_id = 2
|
||||
sign = 1
|
||||
self.r_handler.update_from_instance(self._flavors[_flavor_id], sign)
|
||||
|
||||
expected_resA = self._flavors[_flavor_id].extra_specs['resource:resA']
|
||||
expected_resB = self._flavors[_flavor_id].extra_specs['resource:resB']
|
||||
self.assertEqual(int(expected_resA),
|
||||
self.r_handler._mgr['resA'].obj.used_res)
|
||||
self.assertEqual(int(expected_resB),
|
||||
self.r_handler._mgr['resB'].obj.used_res)
|
||||
|
||||
def test_update_from_instance_without_extra_specs(self):
|
||||
# Flavor id without extra spec
|
||||
_flavor_id = 1
|
||||
self._initialize_used_res_counter()
|
||||
self.r_handler.resource_list = []
|
||||
sign = 1
|
||||
self.r_handler.update_from_instance(self._flavors[_flavor_id], sign)
|
||||
self.assertEqual(0, self.r_handler._mgr['resA'].obj.used_res)
|
||||
self.assertEqual(0, self.r_handler._mgr['resB'].obj.used_res)
|
||||
|
||||
def test_write_resources(self):
|
||||
self._initialize_used_res_counter()
|
||||
extra_resources = {}
|
||||
expected = {'resA': 10, 'used_resA': 0, 'resB': 5, 'used_resB': 0}
|
||||
self.r_handler.write_resources(extra_resources)
|
||||
self.assertEqual(expected, extra_resources)
|
||||
|
||||
def test_test_resources_without_extra_specs(self):
|
||||
limits = {}
|
||||
# Flavor id without extra_specs
|
||||
flavor = self._flavors[1]
|
||||
result = self.r_handler.test_resources(flavor, limits)
|
||||
self.assertEqual([None, None], result)
|
||||
|
||||
def test_test_resources_with_limits_for_different_resource(self):
|
||||
limits = {'resource:resC': 20}
|
||||
# Flavor id with extra_specs
|
||||
flavor = self._flavors[2]
|
||||
result = self.r_handler.test_resources(flavor, limits)
|
||||
self.assertEqual([None, None], result)
|
||||
|
||||
def test_passing_test_resources(self):
|
||||
limits = {'resource:resA': 10, 'resource:resB': 20}
|
||||
# Flavor id with extra_specs
|
||||
flavor = self._flavors[2]
|
||||
self._initialize_used_res_counter()
|
||||
result = self.r_handler.test_resources(flavor, limits)
|
||||
self.assertEqual([None, None], result)
|
||||
|
||||
def test_failing_test_resources_for_single_resource(self):
|
||||
limits = {'resource:resA': 4, 'resource:resB': 20}
|
||||
# Flavor id with extra_specs
|
||||
flavor = self._flavors[2]
|
||||
self._initialize_used_res_counter()
|
||||
result = self.r_handler.test_resources(flavor, limits)
|
||||
expected = ['Free 4 < requested 5 ', None]
|
||||
self.assertEqual(sorted(expected),
|
||||
sorted(result))
|
||||
|
||||
def test_empty_resource_handler(self):
|
||||
"""An empty resource handler has no resource extensions,
|
||||
should have no effect, and should raise no exceptions.
|
||||
"""
|
||||
empty_r_handler = FakeResourceHandler([])
|
||||
|
||||
resources = {}
|
||||
empty_r_handler.reset_resources(resources, None)
|
||||
|
||||
flavor = self._flavors[1]
|
||||
sign = 1
|
||||
empty_r_handler.update_from_instance(flavor, sign)
|
||||
|
||||
limits = {}
|
||||
test_result = empty_r_handler.test_resources(flavor, limits)
|
||||
self.assertEqual([], test_result)
|
||||
|
||||
sign = -1
|
||||
empty_r_handler.update_from_instance(flavor, sign)
|
||||
|
||||
extra_resources = {}
|
||||
expected_extra_resources = extra_resources
|
||||
empty_r_handler.write_resources(extra_resources)
|
||||
self.assertEqual(expected_extra_resources, extra_resources)
|
||||
|
||||
empty_r_handler.report_free_resources()
|
||||
|
||||
def test_vcpu_resource_load(self):
|
||||
# load the vcpu example
|
||||
names = ['vcpu']
|
||||
real_r_handler = resources.ResourceHandler(names)
|
||||
ext_names = real_r_handler._mgr.names()
|
||||
self.assertEqual(names, ext_names)
|
||||
|
||||
# check the extension loaded is the one we expect
|
||||
# and an instance of the object has been created
|
||||
ext = real_r_handler._mgr['vcpu']
|
||||
self.assertIsInstance(ext.obj, vcpu.VCPU)
|
||||
|
||||
|
||||
class TestVCPU(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestVCPU, self).setUp()
|
||||
self._vcpu = vcpu.VCPU()
|
||||
self._vcpu._total = 10
|
||||
self._vcpu._used = 0
|
||||
self._flavor = fake_flavor_obj(vcpus=5)
|
||||
self._big_flavor = fake_flavor_obj(vcpus=20)
|
||||
self._instance = fake_instance_obj(None)
|
||||
|
||||
def test_reset(self):
|
||||
# set vcpu values to something different to test reset
|
||||
self._vcpu._total = 10
|
||||
self._vcpu._used = 5
|
||||
|
||||
driver_resources = {'vcpus': 20}
|
||||
self._vcpu.reset(driver_resources, None)
|
||||
self.assertEqual(20, self._vcpu._total)
|
||||
self.assertEqual(0, self._vcpu._used)
|
||||
|
||||
def test_add_and_remove_instance(self):
|
||||
self._vcpu.add_instance(self._flavor)
|
||||
self.assertEqual(10, self._vcpu._total)
|
||||
self.assertEqual(5, self._vcpu._used)
|
||||
|
||||
self._vcpu.remove_instance(self._flavor)
|
||||
self.assertEqual(10, self._vcpu._total)
|
||||
self.assertEqual(0, self._vcpu._used)
|
||||
|
||||
def test_test_pass_limited(self):
|
||||
result = self._vcpu.test(self._flavor, {'vcpu': 10})
|
||||
self.assertIsNone(result, 'vcpu test failed when it should pass')
|
||||
|
||||
def test_test_pass_unlimited(self):
|
||||
result = self._vcpu.test(self._big_flavor, {})
|
||||
self.assertIsNone(result, 'vcpu test failed when it should pass')
|
||||
|
||||
def test_test_fail(self):
|
||||
result = self._vcpu.test(self._flavor, {'vcpu': 2})
|
||||
expected = _('Free CPUs 2.00 VCPUs < requested 5 VCPUs')
|
||||
self.assertEqual(expected, result)
|
||||
|
||||
def test_write(self):
|
||||
resources = {'stats': {}}
|
||||
self._vcpu.write(resources)
|
||||
expected = {
|
||||
'vcpus': 10,
|
||||
'vcpus_used': 0,
|
||||
'stats': {
|
||||
'num_vcpus': 10,
|
||||
'num_vcpus_used': 0
|
||||
}
|
||||
}
|
||||
self.assertEqual(sorted(expected),
|
||||
sorted(resources))
|
@ -136,8 +136,6 @@ class StatsTestCase(test.NoDBTestCase):
|
||||
self.assertEqual(1, self.stats["num_vm_None"])
|
||||
self.assertEqual(2, self.stats["num_vm_" + vm_states.BUILDING])
|
||||
|
||||
self.assertEqual(10, self.stats.num_vcpus_used)
|
||||
|
||||
def test_calculate_workload(self):
|
||||
self.stats._increment("num_task_None")
|
||||
self.stats._increment("num_task_" + task_states.SCHEDULING)
|
||||
@ -191,7 +189,6 @@ class StatsTestCase(test.NoDBTestCase):
|
||||
self.assertEqual(0, self.stats.num_instances_for_project("1234"))
|
||||
self.assertEqual(0, self.stats.num_os_type("Linux"))
|
||||
self.assertEqual(0, self.stats["num_vm_" + vm_states.BUILDING])
|
||||
self.assertEqual(0, self.stats.num_vcpus_used)
|
||||
|
||||
def test_io_workload(self):
|
||||
vms = [vm_states.ACTIVE, vm_states.BUILDING, vm_states.PAUSED]
|
||||
|
Loading…
x
Reference in New Issue
Block a user