Fixes hypervisor based image filtering on Hyper-V

Fixes Bug #1073547

Nova scheduler's ImagePropertiesFilter based filtering requires the
"supported_instances" stat returned by the driver's get_host_stats method.
This fix implements the get_host_stats method, providing also some
refactoring in the process by adding a HostOps class.

Change-Id: Ia6e73eb3e8ff0be028854fe4fd6d8e305fa1d504
This commit is contained in:
Alessandro Pilotti
2012-10-31 19:38:55 +02:00
parent 3157a42744
commit 80fb666fa2
6 changed files with 184 additions and 112 deletions

View File

@@ -103,6 +103,7 @@ class HyperVAPITestCase(basetestcase.BaseTestCase):
# Modules in which the mocks are going to be injected # Modules in which the mocks are going to be injected
from nova.virt.hyperv import baseops from nova.virt.hyperv import baseops
from nova.virt.hyperv import hostops
from nova.virt.hyperv import livemigrationops from nova.virt.hyperv import livemigrationops
from nova.virt.hyperv import snapshotops from nova.virt.hyperv import snapshotops
from nova.virt.hyperv import vmops from nova.virt.hyperv import vmops
@@ -112,6 +113,7 @@ class HyperVAPITestCase(basetestcase.BaseTestCase):
modules_to_test = [ modules_to_test = [
driver_hyperv, driver_hyperv,
baseops, baseops,
hostops,
vmops, vmops,
vmutils, vmutils,
volumeops, volumeops,
@@ -157,6 +159,14 @@ class HyperVAPITestCase(basetestcase.BaseTestCase):
self.assertEquals(dic['hypervisor_hostname'], platform.node()) self.assertEquals(dic['hypervisor_hostname'], platform.node())
def test_get_host_stats(self):
dic = self._conn.get_host_stats(True)
self.assertEquals(dic['disk_total'],
dic['disk_used'] + dic['disk_available'])
self.assertEquals(dic['host_memory_total'],
dic['host_memory_overhead'] + dic['host_memory_free'])
def test_list_instances(self): def test_list_instances(self):
num_vms = self._hypervutils.get_vm_count() num_vms = self._hypervutils.get_vm_count()
instances = self._conn.list_instances() instances = self._conn.list_instances()

View File

@@ -63,6 +63,7 @@ Using the Python WMI library:
from nova.openstack.common import log as logging from nova.openstack.common import log as logging
from nova.virt import driver from nova.virt import driver
from nova.virt.hyperv import hostops
from nova.virt.hyperv import livemigrationops from nova.virt.hyperv import livemigrationops
from nova.virt.hyperv import snapshotops from nova.virt.hyperv import snapshotops
from nova.virt.hyperv import vmops from nova.virt.hyperv import vmops
@@ -75,6 +76,7 @@ class HyperVDriver(driver.ComputeDriver):
def __init__(self): def __init__(self):
super(HyperVDriver, self).__init__() super(HyperVDriver, self).__init__()
self._hostops = hostops.HostOps()
self._volumeops = volumeops.VolumeOps() self._volumeops = volumeops.VolumeOps()
self._vmops = vmops.VMOps(self._volumeops) self._vmops = vmops.VMOps(self._volumeops)
self._snapshotops = snapshotops.SnapshotOps() self._snapshotops = snapshotops.SnapshotOps()
@@ -121,15 +123,13 @@ class HyperVDriver(driver.ComputeDriver):
pass pass
def get_available_resource(self): def get_available_resource(self):
return self._vmops.get_available_resource() return self._hostops.get_available_resource()
def get_host_stats(self, refresh=False): def get_host_stats(self, refresh=False):
"""See xenapi_conn.py implementation.""" return self._hostops.get_host_stats(refresh)
return {}
def host_power_action(self, host, action): def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host.""" return self._hostops.host_power_action(host, action)
pass
def set_host_enabled(self, host, enabled): def set_host_enabled(self, host, enabled):
"""Sets the specified host's ability to accept new instances.""" """Sets the specified host's ability to accept new instances."""

169
nova/virt/hyperv/hostops.py Normal file
View File

@@ -0,0 +1,169 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2012 Cloudbase Solutions Srl
# 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.
"""
Management class for host operations.
"""
import multiprocessing
import platform
from nova.openstack.common import log as logging
from nova.virt.hyperv import baseops
LOG = logging.getLogger(__name__)
class HostOps(baseops.BaseOps):
def __init__(self):
super(HostOps, self).__init__()
self._stats = None
def _get_vcpu_total(self):
"""Get vcpu number of physical computer.
:returns: the number of cpu core.
"""
# On certain platforms, this will raise a NotImplementedError.
try:
return multiprocessing.cpu_count()
except NotImplementedError:
LOG.warn(_("Cannot get the number of cpu, because this "
"function is not implemented for this platform. "
"This error can be safely ignored for now."))
return 0
def _get_memory_mb_total(self):
"""Get the total memory size(MB) of physical computer.
:returns: the total amount of memory(MB).
"""
total_kb = self._conn_cimv2.query(
"SELECT TotalVisibleMemorySize FROM win32_operatingsystem")[0]\
.TotalVisibleMemorySize
total_mb = long(total_kb) / 1024
return total_mb
def _get_local_gb_total(self):
"""Get the total hdd size(GB) of physical computer.
:returns:
The total amount of HDD(GB).
Note that this value shows a partition where
NOVA-INST-DIR/instances mounts.
"""
#TODO(jordanrinke): This binds to C only right now,
#need to bind to instance dir
total_kb = self._conn_cimv2.query(
"SELECT Size FROM win32_logicaldisk WHERE DriveType=3")[0].Size
total_gb = long(total_kb) / (1024 ** 3)
return total_gb
def _get_vcpu_used(self):
""" Get vcpu usage number of physical computer.
:returns: The total number of vcpu that currently used.
"""
#TODO(jordanrinke) figure out a way to count assigned VCPUs
total_vcpu = 0
return total_vcpu
def _get_memory_mb_used(self):
"""Get the free memory size(MB) of physical computer.
:returns: the total usage of memory(MB).
"""
total_kb = self._conn_cimv2.query(
"SELECT FreePhysicalMemory FROM win32_operatingsystem")[0]\
.FreePhysicalMemory
total_mb = long(total_kb) / 1024
return total_mb
def _get_local_gb_used(self):
"""Get the free hdd size(GB) of physical computer.
:returns:
The total usage of HDD(GB).
Note that this value shows a partition where
NOVA-INST-DIR/instances mounts.
"""
#TODO(jordanrinke): This binds to C only right now,
#need to bind to instance dir
total_kb = self._conn_cimv2.query(
"SELECT FreeSpace FROM win32_logicaldisk WHERE DriveType=3")[0]\
.FreeSpace
total_gb = long(total_kb) / (1024 ** 3)
return total_gb
def _get_hypervisor_version(self):
"""Get hypervisor version.
:returns: hypervisor version (ex. 12003)
"""
version = self._conn_cimv2.Win32_OperatingSystem()[0]\
.Version.replace('.', '')
LOG.info(_('Windows version: %s ') % version)
return version
def get_available_resource(self):
"""Retrieve resource info.
This method is called when nova-compute launches, and
as part of a periodic task.
:returns: dictionary describing resources
"""
LOG.info(_('get_available_resource called'))
# TODO(alexpilotti) implemented cpu_info
dic = {'vcpus': self._get_vcpu_total(),
'memory_mb': self._get_memory_mb_total(),
'local_gb': self._get_local_gb_total(),
'vcpus_used': self._get_vcpu_used(),
'memory_mb_used': self._get_memory_mb_used(),
'local_gb_used': self._get_local_gb_used(),
'hypervisor_type': "hyperv",
'hypervisor_version': self._get_hypervisor_version(),
'hypervisor_hostname': platform.node(),
'cpu_info': 'unknown'}
return dic
def _update_stats(self):
LOG.debug(_("Updating host stats"))
data = {}
data["disk_total"] = self._get_local_gb_total()
data["disk_used"] = self._get_local_gb_used()
data["disk_available"] = data["disk_total"] - data["disk_used"]
data["host_memory_total"] = self._get_memory_mb_total()
data["host_memory_overhead"] = self._get_memory_mb_used()
data["host_memory_free"] = \
data["host_memory_total"] - data["host_memory_overhead"]
data["host_memory_free_computed"] = data["host_memory_free"]
data["supported_instances"] = \
[('i686', 'hyperv', 'hvm'),
('x86_64', 'hyperv', 'hvm')]
self._stats = data
def get_host_stats(self, refresh=False):
"""Return the current state of the host. If 'refresh' is
True, run the update first."""
LOG.info(_("get_host_stats called"))
if refresh or not self._stats:
self._update_stats()
return self._stats
def host_power_action(self, host, action):
"""Reboots, shuts down or powers up the host."""
pass

View File

@@ -18,9 +18,7 @@
""" """
Management class for basic VM operations. Management class for basic VM operations.
""" """
import multiprocessing
import os import os
import platform
import uuid import uuid
from nova import db from nova import db
@@ -475,111 +473,6 @@ class VMOps(baseops.BaseOps):
LOG.error(msg) LOG.error(msg)
raise vmutils.HyperVException(msg) raise vmutils.HyperVException(msg)
def _get_vcpu_total(self):
"""Get vcpu number of physical computer.
:returns: the number of cpu core.
"""
# On certain platforms, this will raise a NotImplementedError.
try:
return multiprocessing.cpu_count()
except NotImplementedError:
LOG.warn(_("Cannot get the number of cpu, because this "
"function is not implemented for this platform. "
"This error can be safely ignored for now."))
return 0
def _get_memory_mb_total(self):
"""Get the total memory size(MB) of physical computer.
:returns: the total amount of memory(MB).
"""
total_kb = self._conn_cimv2.query(
"SELECT TotalVisibleMemorySize FROM win32_operatingsystem")[0]\
.TotalVisibleMemorySize
total_mb = long(total_kb) / 1024
return total_mb
def _get_local_gb_total(self):
"""Get the total hdd size(GB) of physical computer.
:returns:
The total amount of HDD(GB).
Note that this value shows a partition where
NOVA-INST-DIR/instances mounts.
"""
#TODO(jordanrinke): This binds to C only right now,
#need to bind to instance dir
total_kb = self._conn_cimv2.query(
"SELECT Size FROM win32_logicaldisk WHERE DriveType=3")[0].Size
total_gb = long(total_kb) / (1024 ** 3)
return total_gb
def _get_vcpu_used(self):
""" Get vcpu usage number of physical computer.
:returns: The total number of vcpu that currently used.
"""
#TODO(jordanrinke) figure out a way to count assigned VCPUs
total_vcpu = 0
return total_vcpu
def _get_memory_mb_used(self):
"""Get the free memory size(MB) of physical computer.
:returns: the total usage of memory(MB).
"""
total_kb = self._conn_cimv2.query(
"SELECT FreePhysicalMemory FROM win32_operatingsystem")[0]\
.FreePhysicalMemory
total_mb = long(total_kb) / 1024
return total_mb
def _get_local_gb_used(self):
"""Get the free hdd size(GB) of physical computer.
:returns:
The total usage of HDD(GB).
Note that this value shows a partition where
NOVA-INST-DIR/instances mounts.
"""
#TODO(jordanrinke): This binds to C only right now,
#need to bind to instance dir
total_kb = self._conn_cimv2.query(
"SELECT FreeSpace FROM win32_logicaldisk WHERE DriveType=3")[0]\
.FreeSpace
total_gb = long(total_kb) / (1024 ** 3)
return total_gb
def _get_hypervisor_version(self):
"""Get hypervisor version.
:returns: hypervisor version (ex. 12003)
"""
version = self._conn_cimv2.Win32_OperatingSystem()[0]\
.Version.replace('.', '')
LOG.info(_('Windows version: %s ') % version)
return version
def get_available_resource(self):
"""Retrieve resource info.
This method is called when nova-compute launches, and
as part of a periodic task.
:returns: dictionary describing resources
"""
LOG.info(_('get_available_resource called'))
# TODO(alexpilotti) implemented cpu_info
dic = {'vcpus': self._get_vcpu_total(),
'memory_mb': self._get_memory_mb_total(),
'local_gb': self._get_local_gb_total(),
'vcpus_used': self._get_vcpu_used(),
'memory_mb_used': self._get_memory_mb_used(),
'local_gb_used': self._get_local_gb_used(),
'hypervisor_type': "hyperv",
'hypervisor_version': self._get_hypervisor_version(),
'hypervisor_hostname': platform.node(),
'cpu_info': 'unknown'}
return dic
def _cache_image(self, fn, target, fname, cow=False, Size=None, def _cache_image(self, fn, target, fname, cow=False, Size=None,
*args, **kwargs): *args, **kwargs):
"""Wrapper for a method that creates an image that caches the image. """Wrapper for a method that creates an image that caches the image.