Extend compute model attributes

This patch extends compute model attributes by
adding new fields to Instance element. Values are
populated by nova the collector, using the same
nova list call, but requires a more recent compute
API microversion.
A new config option was added to allow users to
enable or disable the extended attributes and it is
disable by default.
Configure prometheus-based jobs to run on newer version
of nova api (2.96) and enables the extended attributes
collection.

Implements: bp/extend-compute-model-attributes

Assisted-By: Cursor (claude-4-sonnet)

Change-Id: Ibf31105d780dce510a59fc74241fa04e28529ade
Signed-off-by: Douglas Viroel <viroel@gmail.com>
This commit is contained in:
Douglas Viroel
2025-07-17 17:03:15 -03:00
parent 1668b9b9f8
commit 03c09825f7
22 changed files with 454 additions and 29 deletions

View File

@@ -471,13 +471,22 @@ class NovaModelBuilder(base.BaseModelBuilder):
"state": getattr(instance, "OS-EXT-STS:vm_state"),
"metadata": instance.metadata,
"project_id": instance.tenant_id,
"locked": instance.locked}
"locked": instance.locked,
# NOTE(dviroel): new attributes are updated if
# extended feature is enabled
"flavor_extra_specs": {},
"pinned_az": "",
}
if self.model.extended_attributes_enabled:
instance_attributes.update({
"flavor_extra_specs": flavor["extra_specs"],
})
if self.nova_helper.is_pinned_az_available():
instance_attributes["pinned_az"] = (
instance.pinned_availability_zone
)
# node_attributes = dict()
# node_attributes["layer"] = "virtual"
# node_attributes["category"] = "compute"
# node_attributes["type"] = "compute"
# node_attributes["attributes"] = instance_attributes
return element.Instance(**instance_attributes)
def _merge_compute_scope(self, compute_scope):

View File

@@ -54,6 +54,9 @@ class Instance(compute_resource.ComputeResource):
"metadata": wfields.JsonField(),
"project_id": wfields.UUIDField(),
"locked": wfields.BooleanField(default=False),
# New fields for extended compute model
"pinned_az": wfields.StringField(default=""),
"flavor_extra_specs": wfields.JsonField(default={}),
}
def accept(self, visitor):

View File

@@ -17,9 +17,11 @@ Openstack implementation of the cluster graph.
"""
import ast
from lxml import etree # nosec: B410
import networkx as nx
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_log import log
from watcher._i18n import _
@@ -28,6 +30,7 @@ from watcher.decision_engine.model import base
from watcher.decision_engine.model import element
LOG = log.getLogger(__name__)
CONF = cfg.CONF
class ModelRoot(nx.DiGraph, base.Model):
@@ -36,12 +39,24 @@ class ModelRoot(nx.DiGraph, base.Model):
def __init__(self, stale=False):
super(ModelRoot, self).__init__()
self.stale = stale
self._extended_attributes_enabled = None
def __nonzero__(self):
return not self.stale
__bool__ = __nonzero__
@property
def extended_attributes_enabled(self):
if self._extended_attributes_enabled is None:
self._extended_attributes_enabled = (
CONF.compute_model.enable_extended_attributes)
return self._extended_attributes_enabled
@extended_attributes_enabled.setter
def extended_attributes_enabled(self, value):
self._extended_attributes_enabled = value
@staticmethod
def assert_node(obj):
if not isinstance(obj, element.ComputeNode):

View File

@@ -16,8 +16,10 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from novaclient import api_versions
import os_resource_classes as orc
from oslo_log import log
from watcher.common import exception
from watcher.common import nova_helper
from watcher.common import placement_helper
@@ -72,7 +74,7 @@ class NovaNotification(base.NotificationEndpoint):
return instance
def update_instance(self, instance, data):
n_version = float(data['nova_object.version'])
n_version = api_versions.APIVersion(data['nova_object.version'])
instance_data = data['nova_object.data']
instance_flavor_data = instance_data['flavor']['nova_object.data']
@@ -91,12 +93,20 @@ class NovaNotification(base.NotificationEndpoint):
'vcpus': num_cores,
'disk': disk_gb,
'metadata': instance_metadata,
'project_id': instance_data['tenant_id']
'project_id': instance_data['tenant_id'],
})
# locked was added in nova notification payload version 1.1
if n_version > 1.0:
if n_version > api_versions.APIVersion(version_str='1.0'):
instance.update({'locked': instance_data['locked']})
# NOTE(dviroel): extra_specs can change due to a resize operation.
# 'extra_specs' was added in nova notification payload version 1.2
if (n_version > api_versions.APIVersion(version_str='1.1') and
self.cluster_data_model.extended_attributes_enabled):
extra_specs = instance_flavor_data.get("extra_specs", {})
instance.update({'flavor_extra_specs': extra_specs})
try:
node = self.get_or_create_node(instance_data['host'])
except exception.ComputeNodeNotFound as exc: