Refactored the compute model and its elements

In this changeset, I refactored the whole Watcher codebase to
adopt a naming convention about the various elements of the
Compute model so that it reflects the same naming convention
adopted by Nova.

Change-Id: I28adba5e1f27175f025330417b072686134d5f51
Partially-Implements: blueprint cluster-model-objects-wrapper
This commit is contained in:
Vincent Françoise
2016-07-06 17:44:29 +02:00
parent dbde1afea0
commit 31c37342cd
53 changed files with 1865 additions and 1803 deletions

View File

@@ -16,7 +16,6 @@
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
from oslo_config import cfg
@@ -40,7 +39,7 @@ class CollectorManager(object):
for collector_name in available_collectors:
collector = self.collector_loader.load(collector_name)
collectors[collector_name] = collector
self._collectors = collectors
self._collectors = collectors
return self._collectors

View File

@@ -20,10 +20,8 @@ from oslo_log import log
from watcher.common import nova_helper
from watcher.decision_engine.model.collector import base
from watcher.decision_engine.model import hypervisor as obj_hypervisor
from watcher.decision_engine.model import element
from watcher.decision_engine.model import model_root
from watcher.decision_engine.model import resource
from watcher.decision_engine.model import vm as obj_vm
LOG = log.getLogger(__name__)
@@ -50,45 +48,46 @@ class NovaClusterDataModelCollector(base.BaseClusterDataModelCollector):
LOG.debug("Building latest Nova cluster data model")
model = model_root.ModelRoot()
mem = resource.Resource(resource.ResourceType.memory)
num_cores = resource.Resource(resource.ResourceType.cpu_cores)
disk = resource.Resource(resource.ResourceType.disk)
disk_capacity = resource.Resource(resource.ResourceType.disk_capacity)
mem = element.Resource(element.ResourceType.memory)
num_cores = element.Resource(element.ResourceType.cpu_cores)
disk = element.Resource(element.ResourceType.disk)
disk_capacity = element.Resource(element.ResourceType.disk_capacity)
model.create_resource(mem)
model.create_resource(num_cores)
model.create_resource(disk)
model.create_resource(disk_capacity)
flavor_cache = {}
hypervisors = self.wrapper.get_hypervisors_list()
for h in hypervisors:
service = self.wrapper.nova.services.find(id=h.service['id'])
# create hypervisor in cluster_model_collector
hypervisor = obj_hypervisor.Hypervisor()
hypervisor.uuid = service.host
hypervisor.hostname = h.hypervisor_hostname
nodes = self.wrapper.get_compute_node_list()
for n in nodes:
service = self.wrapper.nova.services.find(id=n.service['id'])
# create node in cluster_model_collector
node = element.ComputeNode()
node.uuid = service.host
node.hostname = n.hypervisor_hostname
# set capacity
mem.set_capacity(hypervisor, h.memory_mb)
disk.set_capacity(hypervisor, h.free_disk_gb)
disk_capacity.set_capacity(hypervisor, h.local_gb)
num_cores.set_capacity(hypervisor, h.vcpus)
hypervisor.state = h.state
hypervisor.status = h.status
model.add_hypervisor(hypervisor)
vms = self.wrapper.get_vms_by_hypervisor(str(service.host))
for v in vms:
mem.set_capacity(node, n.memory_mb)
disk.set_capacity(node, n.free_disk_gb)
disk_capacity.set_capacity(node, n.local_gb)
num_cores.set_capacity(node, n.vcpus)
node.state = n.state
node.status = n.status
model.add_node(node)
instances = self.wrapper.get_instances_by_node(str(service.host))
for v in instances:
# create VM in cluster_model_collector
vm = obj_vm.VM()
vm.uuid = v.id
# nova/nova/compute/vm_states.py
vm.state = getattr(v, 'OS-EXT-STS:vm_state')
instance = element.Instance()
instance.uuid = v.id
# nova/nova/compute/instance_states.py
instance.state = getattr(v, 'OS-EXT-STS:instance_state')
# set capacity
self.wrapper.get_flavor_instance(v, flavor_cache)
mem.set_capacity(vm, v.flavor['ram'])
disk.set_capacity(vm, v.flavor['disk'])
num_cores.set_capacity(vm, v.flavor['vcpus'])
mem.set_capacity(instance, v.flavor['ram'])
disk.set_capacity(instance, v.flavor['disk'])
num_cores.set_capacity(instance, v.flavor['vcpus'])
model.get_mapping().map(node, instance)
model.add_instance(instance)
model.get_mapping().map(hypervisor, vm)
model.add_vm(vm)
return model

View File

@@ -0,0 +1,39 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2016 b<>com
#
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.com>
#
# 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 watcher.decision_engine.model.element import disk_info
from watcher.decision_engine.model.element import instance
from watcher.decision_engine.model.element import node
from watcher.decision_engine.model.element import resource
ServiceState = node.ServiceState
PowerState = node.PowerState
ComputeNode = node.ComputeNode
InstanceState = instance.InstanceState
Instance = instance.Instance
DiskInfo = disk_info.DiskInfo
ResourceType = resource.ResourceType
Resource = resource.Resource
__all__ = [
'ServiceState', 'PowerState', 'ComputeNode', 'InstanceState', 'Instance',
'DiskInfo', 'ResourceType', 'Resource']

View File

@@ -1,11 +1,13 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
# Copyright (c) 2016 b<>com
#
# Authors: Vincent FRANCOISE <vincent.francoise@b-com.com>
#
# 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
# 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,
@@ -14,11 +16,14 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import enum
import abc
import six
class HypervisorState(enum.Enum):
ONLINE = 'up'
OFFLINE = 'down'
ENABLED = 'enabled'
DISABLED = 'disabled'
@six.add_metaclass(abc.ABCMeta)
class Element(object):
@abc.abstractmethod
def accept(self, visitor):
raise NotImplementedError()

View File

@@ -14,8 +14,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import abc
class ComputeResource(object):
import six
from watcher.decision_engine.model.element import base
@six.add_metaclass(abc.ABCMeta)
class ComputeResource(base.Element):
def __init__(self):
self._uuid = ""

View File

@@ -14,8 +14,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from watcher.decision_engine.model.element import base
class DiskInfo(base.Element):
class DiskInfo(object):
def __init__(self):
self.name = ""
self.major = 0
@@ -23,6 +26,9 @@ class DiskInfo(object):
self.size = 0
self.scheduler = ""
def accept(self, visitor):
raise NotImplementedError()
def set_size(self, size):
"""DiskInfo

View File

@@ -0,0 +1,54 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# 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 enum
from watcher.decision_engine.model.element import compute_resource
class InstanceState(enum.Enum):
ACTIVE = 'active' # Instance is running
BUILDING = 'building' # Instance only exists in DB
PAUSED = 'paused'
SUSPENDED = 'suspended' # Instance is suspended to disk.
STOPPED = 'stopped' # Instance is shut off, the disk image is still there.
RESCUED = 'rescued' # A rescue image is running with the original image
# attached.
RESIZED = 'resized' # a Instance with the new size is active.
SOFT_DELETED = 'soft-delete'
# still available to restore.
DELETED = 'deleted' # Instance is permanently deleted.
ERROR = 'error'
class Instance(compute_resource.ComputeResource):
def __init__(self):
super(Instance, self).__init__()
self._state = InstanceState.ACTIVE.value
def accept(self, visitor):
raise NotImplementedError()
@property
def state(self):
return self._state
@state.setter
def state(self, state):
self._state = state

View File

@@ -14,17 +14,46 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from watcher.decision_engine.model import compute_resource
from watcher.decision_engine.model import hypervisor_state
from watcher.decision_engine.model import power_state
import enum
from watcher.decision_engine.model.element import compute_resource
class Hypervisor(compute_resource.ComputeResource):
class ServiceState(enum.Enum):
ONLINE = 'up'
OFFLINE = 'down'
ENABLED = 'enabled'
DISABLED = 'disabled'
class PowerState(enum.Enum):
# away mode
g0 = "g0"
# power on suspend (processor caches are flushed)
# The power to the CPU(s) and RAM is maintained
g1_S1 = "g1_S1"
# CPU powered off. Dirty cache is flushed to RAM
g1_S2 = "g1_S2"
# Suspend to RAM
g1_S3 = "g1_S3"
# Suspend to Disk
g1_S4 = "g1_S4"
# switch outlet X OFF on the PDU (Power Distribution Unit)
switch_off = "switch_off"
# switch outlet X ON on the PDU (Power Distribution Unit)
switch_on = "switch_on"
class ComputeNode(compute_resource.ComputeResource):
def __init__(self):
super(Hypervisor, self).__init__()
self._state = hypervisor_state.HypervisorState.ONLINE
self._status = hypervisor_state.HypervisorState.ENABLED
self._power_state = power_state.PowerState.g0
super(ComputeNode, self).__init__()
self._state = ServiceState.ONLINE
self._status = ServiceState.ENABLED
self._power_state = PowerState.g0
def accept(self, visitor):
raise NotImplementedError()
@property
def state(self):

View File

@@ -14,9 +14,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from oslo_log import log
import threading
from oslo_log import log
from watcher._i18n import _LW
LOG = log.getLogger(__name__)
@@ -25,110 +26,108 @@ LOG = log.getLogger(__name__)
class Mapping(object):
def __init__(self, model):
self.model = model
self._mapping_hypervisors = {}
self.mapping_vm = {}
self.compute_node_mapping = {}
self.instance_mapping = {}
self.lock = threading.Lock()
def map(self, hypervisor, vm):
"""Select the hypervisor where the instance is launched
def map(self, node, instance):
"""Select the node where the instance is launched
:param hypervisor: the hypervisor
:param vm: the virtual machine or instance
:param node: the node
:param instance: the virtual machine or instance
"""
try:
self.lock.acquire()
# init first
if hypervisor.uuid not in self._mapping_hypervisors.keys():
self._mapping_hypervisors[hypervisor.uuid] = []
if node.uuid not in self.compute_node_mapping.keys():
self.compute_node_mapping[node.uuid] = []
# map node => vms
self._mapping_hypervisors[hypervisor.uuid].append(
vm.uuid)
# map node => instances
self.compute_node_mapping[node.uuid].append(
instance.uuid)
# map vm => node
self.mapping_vm[vm.uuid] = hypervisor.uuid
# map instance => node
self.instance_mapping[instance.uuid] = node.uuid
finally:
self.lock.release()
def unmap(self, hypervisor, vm):
"""Remove the instance from the hypervisor
def unmap(self, node, instance):
"""Remove the instance from the node
:param hypervisor: the hypervisor
:param vm: the virtual machine or instance
:param node: the node
:param instance: the virtual machine or instance
"""
self.unmap_from_id(hypervisor.uuid, vm.uuid)
self.unmap_from_id(node.uuid, instance.uuid)
def unmap_from_id(self, node_uuid, vm_uuid):
"""Remove the instance (by id) from the hypervisor (by id)
def unmap_from_id(self, node_uuid, instance_uuid):
"""Remove the instance (by id) from the node (by id)
:rtype : object
"""
try:
self.lock.acquire()
if str(node_uuid) in self._mapping_hypervisors:
self._mapping_hypervisors[str(node_uuid)].remove(str(vm_uuid))
# remove vm
self.mapping_vm.pop(vm_uuid)
if str(node_uuid) in self.compute_node_mapping:
self.compute_node_mapping[str(node_uuid)].remove(
str(instance_uuid))
# remove instance
self.instance_mapping.pop(instance_uuid)
else:
LOG.warning(_LW(
"trying to delete the virtual machine %(vm)s but it was "
"not found on hypervisor %(hyp)s"),
{'vm': vm_uuid, 'hyp': node_uuid})
"Trying to delete the instance %(instance)s but it was "
"not found on node %(node)s"),
{'instance': instance_uuid, 'node': node_uuid})
finally:
self.lock.release()
def get_mapping(self):
return self._mapping_hypervisors
return self.compute_node_mapping
def get_mapping_vm(self):
return self.mapping_vm
def get_node_from_instance(self, instance):
return self.get_node_from_instance_id(instance.uuid)
def get_node_from_vm(self, vm):
return self.get_node_from_vm_id(vm.uuid)
def get_node_from_instance_id(self, instance_uuid):
"""Getting host information from the guest instance
def get_node_from_vm_id(self, vm_uuid):
"""Getting host information from the guest VM
:param vm: the uuid of the instance
:return: hypervisor
:param instance: the uuid of the instance
:return: node
"""
return self.model.get_hypervisor_from_id(
self.get_mapping_vm()[str(vm_uuid)])
return self.model.get_node_from_id(
self.instance_mapping[str(instance_uuid)])
def get_node_vms(self, hypervisor):
"""Get the list of instances running on the hypervisor
def get_node_instances(self, node):
"""Get the list of instances running on the node
:param hypervisor:
:param node:
:return:
"""
return self.get_node_vms_from_id(hypervisor.uuid)
return self.get_node_instances_from_id(node.uuid)
def get_node_vms_from_id(self, node_uuid):
if str(node_uuid) in self._mapping_hypervisors.keys():
return self._mapping_hypervisors[str(node_uuid)]
def get_node_instances_from_id(self, node_uuid):
if str(node_uuid) in self.compute_node_mapping.keys():
return self.compute_node_mapping[str(node_uuid)]
else:
# empty
return []
def migrate_vm(self, vm, src_hypervisor, dest_hypervisor):
"""Migrate single instance from src_hypervisor to dest_hypervisor
def migrate_instance(self, instance, source_node, destination_node):
"""Migrate single instance from source_node to destination_node
:param vm:
:param src_hypervisor:
:param dest_hypervisor:
:param instance:
:param source_node:
:param destination_node:
:return:
"""
if src_hypervisor == dest_hypervisor:
if source_node == destination_node:
return False
# unmap
self.unmap(src_hypervisor, vm)
self.unmap(source_node, instance)
# map
self.map(dest_hypervisor, vm)
self.map(destination_node, instance)
return True

View File

@@ -17,16 +17,14 @@
from watcher._i18n import _
from watcher.common import exception
from watcher.common import utils
from watcher.decision_engine.model import hypervisor
from watcher.decision_engine.model import element
from watcher.decision_engine.model import mapping
from watcher.decision_engine.model import vm
class ModelRoot(object):
def __init__(self, stale=False):
self._hypervisors = utils.Struct()
self._vms = utils.Struct()
self._nodes = utils.Struct()
self._instances = utils.Struct()
self.mapping = mapping.Mapping(self)
self.resource = utils.Struct()
self.stale = stale
@@ -36,46 +34,46 @@ class ModelRoot(object):
__bool__ = __nonzero__
def assert_hypervisor(self, obj):
if not isinstance(obj, hypervisor.Hypervisor):
def assert_node(self, obj):
if not isinstance(obj, element.ComputeNode):
raise exception.IllegalArgumentException(
message=_("'obj' argument type is not valid"))
def assert_vm(self, obj):
if not isinstance(obj, vm.VM):
def assert_instance(self, obj):
if not isinstance(obj, element.Instance):
raise exception.IllegalArgumentException(
message=_("'obj' argument type is not valid"))
def add_hypervisor(self, hypervisor):
self.assert_hypervisor(hypervisor)
self._hypervisors[hypervisor.uuid] = hypervisor
def add_node(self, node):
self.assert_node(node)
self._nodes[node.uuid] = node
def remove_hypervisor(self, hypervisor):
self.assert_hypervisor(hypervisor)
if str(hypervisor.uuid) not in self._hypervisors.keys():
raise exception.HypervisorNotFound(hypervisor.uuid)
def remove_node(self, node):
self.assert_node(node)
if str(node.uuid) not in self._nodes:
raise exception.ComputeNodeNotFound(node.uuid)
else:
del self._hypervisors[hypervisor.uuid]
del self._nodes[node.uuid]
def add_vm(self, vm):
self.assert_vm(vm)
self._vms[vm.uuid] = vm
def add_instance(self, instance):
self.assert_instance(instance)
self._instances[instance.uuid] = instance
def get_all_hypervisors(self):
return self._hypervisors
def get_all_compute_nodes(self):
return self._nodes
def get_hypervisor_from_id(self, hypervisor_uuid):
if str(hypervisor_uuid) not in self._hypervisors.keys():
raise exception.HypervisorNotFound(hypervisor_uuid)
return self._hypervisors[str(hypervisor_uuid)]
def get_node_from_id(self, node_uuid):
if str(node_uuid) not in self._nodes:
raise exception.ComputeNodeNotFound(node_uuid)
return self._nodes[str(node_uuid)]
def get_vm_from_id(self, uuid):
if str(uuid) not in self._vms.keys():
def get_instance_from_id(self, uuid):
if str(uuid) not in self._instances:
raise exception.InstanceNotFound(name=uuid)
return self._vms[str(uuid)]
return self._instances[str(uuid)]
def get_all_vms(self):
return self._vms
def get_all_instances(self):
return self._instances
def get_mapping(self):
return self.mapping
@@ -83,5 +81,5 @@ class ModelRoot(object):
def create_resource(self, r):
self.resource[str(r.name)] = r
def get_resource_from_id(self, id):
return self.resource[str(id)]
def get_resource_from_id(self, resource_id):
return self.resource[str(resource_id)]

View File

@@ -1,31 +0,0 @@
# 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 enum
class PowerState(enum.Enum):
# away mode
g0 = "g0"
# power on suspend (processor caches are flushed)
# The power to the CPU(s) and RAM is maintained
g1_S1 = "g1_S1"
# CPU powered off. Dirty cache is flushed to RAM
g1_S2 = "g1_S2"
# Suspend to RAM
g1_S3 = "g1_S3"
# Suspend to Disk
g1_S4 = "g1_S4"
# switch outlet X OFF on the PDU (Power Distribution Unit)
switch_off = "switch_off"
# switch outlet X ON on the PDU (Power Distribution Unit)
switch_on = "switch_on"

View File

@@ -1,31 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# 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 watcher.decision_engine.model import compute_resource
from watcher.decision_engine.model import vm_state
class VM(compute_resource.ComputeResource):
def __init__(self):
super(VM, self).__init__()
self._state = vm_state.VMState.ACTIVE.value
@property
def state(self):
return self._state
@state.setter
def state(self, state):
self._state = state

View File

@@ -1,34 +0,0 @@
# -*- encoding: utf-8 -*-
# Copyright (c) 2015 b<>com
#
# 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 enum
class VMState(enum.Enum):
ACTIVE = 'active' # VM is running
BUILDING = 'building' # VM only exists in DB
PAUSED = 'paused'
SUSPENDED = 'suspended' # VM is suspended to disk.
STOPPED = 'stopped' # VM is powered off, the disk image is still there.
RESCUED = 'rescued' # A rescue image is running with the original VM image
# attached.
RESIZED = 'resized' # a VM with the new size is active.
SOFT_DELETED = 'soft-delete'
# still available to restore.
DELETED = 'deleted' # VM is permanently deleted.
ERROR = 'error'