Merge "Get more flavor attributes from libvirt metadata (if available)"
This commit is contained in:
@@ -62,7 +62,18 @@ OPTS = [
|
||||
"The minimum should be the value of the config option "
|
||||
"of resource_update_interval. This option is only used "
|
||||
"for agent polling to Nova API, so it will work only "
|
||||
"when 'instance_discovery_method' is set to 'naive'.")
|
||||
"when 'instance_discovery_method' is set to 'naive'."),
|
||||
cfg.BoolOpt('fetch_extra_metadata',
|
||||
default=True,
|
||||
help="Whether or not additional instance attributes that "
|
||||
"require Nova API queries should be fetched. Currently "
|
||||
"the only value that requires fetching from Nova API is "
|
||||
"'metadata', the attribute storing user-configured "
|
||||
"server metadata, which is used to fill out some "
|
||||
"optional fields such as the server group of an "
|
||||
"instance. fetch_extra_metadata is currently set to "
|
||||
"True by default, but to reduce the load on Nova API "
|
||||
"this will be changed to False in a future release."),
|
||||
]
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@@ -93,7 +104,8 @@ class InstanceDiscovery(plugin_base.DiscoveryBase):
|
||||
self.expiration_time = conf.compute.resource_update_interval
|
||||
self.cache_expiry = conf.compute.resource_cache_expiry
|
||||
if self.method == "libvirt_metadata":
|
||||
# 4096 instances on a compute should be enough :)
|
||||
# 4096 resources on a compute should be enough :)
|
||||
self._flavor_id_cache = cachetools.LRUCache(4096)
|
||||
self._server_cache = cachetools.LRUCache(4096)
|
||||
else:
|
||||
self.lock = threading.Lock()
|
||||
@@ -119,8 +131,49 @@ class InstanceDiscovery(plugin_base.DiscoveryBase):
|
||||
return int(elem.text)
|
||||
return 0
|
||||
|
||||
def _get_flavor_id(self, flavor_xml, instance_id):
|
||||
flavor_name = flavor_xml.attrib["name"]
|
||||
# Flavor ID is available in libvirt metadata from 2025.2 onwards.
|
||||
flavor_id = flavor_xml.attrib.get("id")
|
||||
if flavor_id:
|
||||
return flavor_id
|
||||
# If not found in libvirt metadata, fallback to API queries.
|
||||
# If we already have the server metadata get the flavor ID from there.
|
||||
if self.conf.compute.fetch_extra_metadata:
|
||||
server = self.get_server(instance_id)
|
||||
if server:
|
||||
return server.flavor["id"]
|
||||
# If server metadata is not otherwise fetched, or the query failed,
|
||||
# query just the flavor for better cache hit rates.
|
||||
return (self.get_flavor_id(flavor_name) or flavor_name)
|
||||
|
||||
def _get_flavor_extra_specs(self, flavor_xml):
|
||||
# Extra specs are available in libvirt metadata from 2025.2 onwards.
|
||||
# Note that this checks for existence of the element, not whether
|
||||
# or not it is empty, as it *can* exist but have nothing set in it.
|
||||
extra_specs = flavor_xml.find("./extraSpecs")
|
||||
if extra_specs is not None:
|
||||
return {
|
||||
extra_spec.attrib["name"]: extra_spec.text
|
||||
for extra_spec in extra_specs.findall("./extraSpec")}
|
||||
# If not found in libvirt metadata, return None to signify
|
||||
# "not fetched", as we don't support performing additional
|
||||
# API queries just for the extra specs.
|
||||
return None
|
||||
|
||||
@cachetools.cachedmethod(operator.attrgetter('_flavor_id_cache'))
|
||||
def get_flavor_id(self, name):
|
||||
LOG.debug("Querying metadata for flavor %s from Nova API", name)
|
||||
try:
|
||||
return self.nova_cli.nova_client.flavors.find(
|
||||
name=name,
|
||||
is_public=None).id
|
||||
except exceptions.NotFound:
|
||||
return None
|
||||
|
||||
@cachetools.cachedmethod(operator.attrgetter('_server_cache'))
|
||||
def get_server(self, uuid):
|
||||
LOG.debug("Querying metadata for instance %s from Nova API", uuid)
|
||||
try:
|
||||
return self.nova_cli.nova_client.servers.get(uuid)
|
||||
except exceptions.NotFound:
|
||||
@@ -130,6 +183,7 @@ class InstanceDiscovery(plugin_base.DiscoveryBase):
|
||||
def discover_libvirt_polling(self, manager, param=None):
|
||||
instances = []
|
||||
for domain in self.connection.listAllDomains():
|
||||
instance_id = domain.UUIDString()
|
||||
xml_string = libvirt_utils.instance_metadata(domain)
|
||||
if xml_string is None:
|
||||
continue
|
||||
@@ -138,15 +192,6 @@ class InstanceDiscovery(plugin_base.DiscoveryBase):
|
||||
os_type_xml = full_xml.find("./os/type")
|
||||
metadata_xml = etree.fromstring(xml_string)
|
||||
|
||||
# TODO(sileht, jwysogla): We don't have the flavor ID
|
||||
# and server metadata here. We currently poll nova to get
|
||||
# the flavor ID, but storing the
|
||||
# flavor_id doesn't have any sense because the flavor description
|
||||
# can change over the time, we should store the detail of the
|
||||
# flavor. this is why nova doesn't put the id in the libvirt
|
||||
# metadata. I think matadata field could be eventually added to
|
||||
# the libvirt matadata created by nova.
|
||||
|
||||
try:
|
||||
flavor_xml = metadata_xml.find(
|
||||
"./flavor")
|
||||
@@ -158,11 +203,10 @@ class InstanceDiscovery(plugin_base.DiscoveryBase):
|
||||
"./name").text
|
||||
instance_arch = os_type_xml.attrib["arch"]
|
||||
|
||||
server = self.get_server(domain.UUIDString())
|
||||
flavor_id = (server.flavor["id"] if server is not None
|
||||
else flavor_xml.attrib["name"])
|
||||
extra_specs = self._get_flavor_extra_specs(flavor_xml)
|
||||
|
||||
flavor = {
|
||||
"id": flavor_id,
|
||||
"id": self._get_flavor_id(flavor_xml, instance_id),
|
||||
"name": flavor_xml.attrib["name"],
|
||||
"vcpus": self._safe_find_int(flavor_xml, "vcpus"),
|
||||
"ram": self._safe_find_int(flavor_xml, "memory"),
|
||||
@@ -170,18 +214,28 @@ class InstanceDiscovery(plugin_base.DiscoveryBase):
|
||||
"ephemeral": self._safe_find_int(flavor_xml, "ephemeral"),
|
||||
"swap": self._safe_find_int(flavor_xml, "swap"),
|
||||
}
|
||||
if extra_specs is not None:
|
||||
flavor["extra_specs"] = extra_specs
|
||||
|
||||
# The image description is partial, but Gnocchi only care about
|
||||
# the id, so we are fine
|
||||
image_xml = metadata_xml.find("./root[@type='image']")
|
||||
image = ({'id': image_xml.attrib['uuid']}
|
||||
if image_xml is not None else None)
|
||||
metadata = server.metadata if server is not None else {}
|
||||
|
||||
# Getting the server metadata requires expensive Nova API
|
||||
# queries, and may potentially contain sensitive user info,
|
||||
# so it is only fetched when configured to do so.
|
||||
if self.conf.compute.fetch_extra_metadata:
|
||||
server = self.get_server(instance_id)
|
||||
metadata = server.metadata if server is not None else {}
|
||||
else:
|
||||
metadata = {}
|
||||
except AttributeError:
|
||||
LOG.error(
|
||||
"Fail to get domain uuid %s metadata: "
|
||||
"metadata was missing expected attributes",
|
||||
domain.UUIDString())
|
||||
instance_id)
|
||||
continue
|
||||
|
||||
dom_state = domain.state()[0]
|
||||
@@ -194,7 +248,7 @@ class InstanceDiscovery(plugin_base.DiscoveryBase):
|
||||
(project_id + self.conf.host).encode('utf-8')).hexdigest()
|
||||
|
||||
instance_data = {
|
||||
"id": domain.UUIDString(),
|
||||
"id": instance_id,
|
||||
"name": instance_name,
|
||||
"flavor": flavor,
|
||||
"image": image,
|
||||
|
@@ -37,8 +37,13 @@ class TestPollsterBase(base.BaseTestCase):
|
||||
'active')
|
||||
setattr(self.instance, 'OS-EXT-STS:task_state', None)
|
||||
self.instance.id = 1
|
||||
self.instance.flavor = {'name': 'm1.small', 'id': 2, 'vcpus': 1,
|
||||
'ram': 512, 'disk': 20, 'ephemeral': 0}
|
||||
self.instance.flavor = {'name': 'm1.small',
|
||||
'id': 'eba4213d-3c6c-4b5f-8158-dd0022d71d62',
|
||||
'vcpus': 1,
|
||||
'ram': 512,
|
||||
'disk': 20,
|
||||
'ephemeral': 0,
|
||||
'extra_specs': {'hw_rng:allowed': 'true'}}
|
||||
self.instance.status = 'active'
|
||||
self.instance.metadata = {
|
||||
'fqdn': 'vm_fqdn',
|
||||
|
@@ -65,6 +65,8 @@ class TestCPUPollster(base.TestPollsterBase):
|
||||
self.assertEqual('active', samples[0].resource_metadata['status'])
|
||||
self.assertEqual('active', samples[0].resource_metadata['state'])
|
||||
self.assertIsNone(samples[0].resource_metadata['task_state'])
|
||||
self.assertEqual(self.instance.flavor,
|
||||
samples[0].resource_metadata['flavor'])
|
||||
|
||||
def test_get_reserved_metadata_with_keys(self):
|
||||
self.CONF.set_override('reserved_metadata_keys', ['fqdn'])
|
||||
|
@@ -69,11 +69,15 @@ class TestLocationMetadata(base.BaseTestCase):
|
||||
'hostId': '1234-5678',
|
||||
'OS-EXT-SRV-ATTR:host': 'host-test',
|
||||
'flavor': {'name': 'm1.tiny',
|
||||
'id': 1,
|
||||
'id': ('eba4213d-3c6c-'
|
||||
'4b5f-8158-'
|
||||
'dd0022d71d62'),
|
||||
'disk': 20,
|
||||
'ram': 512,
|
||||
'vcpus': 2,
|
||||
'ephemeral': 0},
|
||||
'ephemeral': 0,
|
||||
'extra_specs': {
|
||||
'hw_rng:allowed': 'true'}},
|
||||
'metadata': {'metering.autoscale.group':
|
||||
'X' * 512,
|
||||
'metering.ephemeral_gb': 42}}
|
||||
|
@@ -10,6 +10,8 @@
|
||||
# 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 argparse
|
||||
import datetime
|
||||
from unittest import mock
|
||||
|
||||
@@ -24,6 +26,29 @@ from ceilometer.tests import base
|
||||
|
||||
|
||||
LIBVIRT_METADATA_XML = """
|
||||
<instance>
|
||||
<package version="14.0.0"/>
|
||||
<name>test.dom.com</name>
|
||||
<creationTime>2016-11-16 07:35:06</creationTime>
|
||||
<flavor name="m1.tiny" id="eba4213d-3c6c-4b5f-8158-dd0022d71d62">
|
||||
<memory>512</memory>
|
||||
<disk>1</disk>
|
||||
<swap>0</swap>
|
||||
<ephemeral>0</ephemeral>
|
||||
<vcpus>1</vcpus>
|
||||
<extraSpecs>
|
||||
<extraSpec name="hw_rng:allowed">true</extraSpec>
|
||||
</extraSpecs>
|
||||
</flavor>
|
||||
<owner>
|
||||
<user uuid="a1f4684e58bd4c88aefd2ecb0783b497">admin</user>
|
||||
<project uuid="d99c829753f64057bc0f2030da309943">admin</project>
|
||||
</owner>
|
||||
<root type="image" uuid="bdaf114a-35e9-4163-accd-226d5944bf11"/>
|
||||
</instance>
|
||||
"""
|
||||
|
||||
LIBVIRT_METADATA_XML_OLD = """
|
||||
<instance>
|
||||
<package version="14.0.0"/>
|
||||
<name>test.dom.com</name>
|
||||
@@ -43,6 +68,93 @@ LIBVIRT_METADATA_XML = """
|
||||
</instance>
|
||||
"""
|
||||
|
||||
LIBVIRT_METADATA_XML_EMPTY_FLAVOR_ID = """
|
||||
<instance>
|
||||
<package version="14.0.0"/>
|
||||
<name>test.dom.com</name>
|
||||
<creationTime>2016-11-16 07:35:06</creationTime>
|
||||
<flavor name="m1.tiny" id="">
|
||||
<memory>512</memory>
|
||||
<disk>1</disk>
|
||||
<swap>0</swap>
|
||||
<ephemeral>0</ephemeral>
|
||||
<vcpus>1</vcpus>
|
||||
<extraSpecs>
|
||||
<extraSpec name="hw_rng:allowed">true</extraSpec>
|
||||
</extraSpecs>
|
||||
</flavor>
|
||||
<owner>
|
||||
<user uuid="a1f4684e58bd4c88aefd2ecb0783b497">admin</user>
|
||||
<project uuid="d99c829753f64057bc0f2030da309943">admin</project>
|
||||
</owner>
|
||||
<root type="image" uuid="bdaf114a-35e9-4163-accd-226d5944bf11"/>
|
||||
</instance>
|
||||
"""
|
||||
|
||||
LIBVIRT_METADATA_XML_NO_FLAVOR_ID = """
|
||||
<instance>
|
||||
<package version="14.0.0"/>
|
||||
<name>test.dom.com</name>
|
||||
<creationTime>2016-11-16 07:35:06</creationTime>
|
||||
<flavor name="m1.tiny">
|
||||
<memory>512</memory>
|
||||
<disk>1</disk>
|
||||
<swap>0</swap>
|
||||
<ephemeral>0</ephemeral>
|
||||
<vcpus>1</vcpus>
|
||||
<extraSpecs>
|
||||
<extraSpec name="hw_rng:allowed">true</extraSpec>
|
||||
</extraSpecs>
|
||||
</flavor>
|
||||
<owner>
|
||||
<user uuid="a1f4684e58bd4c88aefd2ecb0783b497">admin</user>
|
||||
<project uuid="d99c829753f64057bc0f2030da309943">admin</project>
|
||||
</owner>
|
||||
<root type="image" uuid="bdaf114a-35e9-4163-accd-226d5944bf11"/>
|
||||
</instance>
|
||||
"""
|
||||
|
||||
LIBVIRT_METADATA_XML_EMPTY_FLAVOR_EXTRA_SPECS = """
|
||||
<instance>
|
||||
<package version="14.0.0"/>
|
||||
<name>test.dom.com</name>
|
||||
<creationTime>2016-11-16 07:35:06</creationTime>
|
||||
<flavor name="m1.tiny" id="eba4213d-3c6c-4b5f-8158-dd0022d71d62">
|
||||
<memory>512</memory>
|
||||
<disk>1</disk>
|
||||
<swap>0</swap>
|
||||
<ephemeral>0</ephemeral>
|
||||
<vcpus>1</vcpus>
|
||||
<extraSpecs></extraSpecs>
|
||||
</flavor>
|
||||
<owner>
|
||||
<user uuid="a1f4684e58bd4c88aefd2ecb0783b497">admin</user>
|
||||
<project uuid="d99c829753f64057bc0f2030da309943">admin</project>
|
||||
</owner>
|
||||
<root type="image" uuid="bdaf114a-35e9-4163-accd-226d5944bf11"/>
|
||||
</instance>
|
||||
"""
|
||||
|
||||
LIBVIRT_METADATA_XML_NO_FLAVOR_EXTRA_SPECS = """
|
||||
<instance>
|
||||
<package version="14.0.0"/>
|
||||
<name>test.dom.com</name>
|
||||
<creationTime>2016-11-16 07:35:06</creationTime>
|
||||
<flavor name="m1.tiny" id="eba4213d-3c6c-4b5f-8158-dd0022d71d62">
|
||||
<memory>512</memory>
|
||||
<disk>1</disk>
|
||||
<swap>0</swap>
|
||||
<ephemeral>0</ephemeral>
|
||||
<vcpus>1</vcpus>
|
||||
</flavor>
|
||||
<owner>
|
||||
<user uuid="a1f4684e58bd4c88aefd2ecb0783b497">admin</user>
|
||||
<project uuid="d99c829753f64057bc0f2030da309943">admin</project>
|
||||
</owner>
|
||||
<root type="image" uuid="bdaf114a-35e9-4163-accd-226d5944bf11"/>
|
||||
</instance>
|
||||
"""
|
||||
|
||||
LIBVIRT_DESC_XML = """
|
||||
<domain type='kvm' id='1'>
|
||||
<name>instance-00000001</name>
|
||||
@@ -75,6 +187,10 @@ LIBVIRT_MANUAL_INSTANCE_DESC_XML = """
|
||||
|
||||
|
||||
class FakeDomain:
|
||||
def __init__(self, desc=None, metadata=None):
|
||||
self._desc = desc or LIBVIRT_DESC_XML
|
||||
self._metadata = metadata or LIBVIRT_METADATA_XML
|
||||
|
||||
def state(self):
|
||||
return [1, 2]
|
||||
|
||||
@@ -85,15 +201,18 @@ class FakeDomain:
|
||||
return "a75c2fa5-6c03-45a8-bbf7-b993cfcdec27"
|
||||
|
||||
def XMLDesc(self):
|
||||
return LIBVIRT_DESC_XML
|
||||
return self._desc
|
||||
|
||||
def metadata(self, flags, url):
|
||||
return LIBVIRT_METADATA_XML
|
||||
return self._metadata
|
||||
|
||||
|
||||
class FakeConn:
|
||||
def __init__(self, domains=None):
|
||||
self._domains = domains or [FakeDomain()]
|
||||
|
||||
def listAllDomains(self):
|
||||
return [FakeDomain()]
|
||||
return list(self._domains)
|
||||
|
||||
def isAlive(self):
|
||||
return True
|
||||
@@ -149,8 +268,13 @@ class TestDiscovery(base.BaseTestCase):
|
||||
# FIXME(sileht): This is wrong, this should be a uuid
|
||||
# The internal id of nova can't be retrieved via API or notification
|
||||
self.instance.id = 1
|
||||
self.instance.flavor = {'name': 'm1.small', 'id': 2, 'vcpus': 1,
|
||||
'ram': 512, 'disk': 20, 'ephemeral': 0}
|
||||
self.instance.flavor = {'name': 'm1.small',
|
||||
'id': 'eba4213d-3c6c-4b5f-8158-dd0022d71d62',
|
||||
'vcpus': 1,
|
||||
'ram': 512,
|
||||
'disk': 20,
|
||||
'ephemeral': 0,
|
||||
'extra_specs': {'hw_rng:allowed': 'true'}}
|
||||
self.instance.status = 'active'
|
||||
self.instance.metadata = {
|
||||
'fqdn': 'vm_fqdn',
|
||||
@@ -220,16 +344,26 @@ class TestDiscovery(base.BaseTestCase):
|
||||
self.client.instance_get_all_by_host.assert_called_once_with(
|
||||
self.CONF.host, "2016-01-01T00:00:00+00:00")
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt(self, mock_libvirt_conn):
|
||||
def test_discovery_with_libvirt(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn()
|
||||
mock_get_server.return_value = argparse.Namespace(
|
||||
metadata={"metering.server_group": "group1"})
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_not_called()
|
||||
mock_get_server.assert_called_with(
|
||||
"a75c2fa5-6c03-45a8-bbf7-b993cfcdec27")
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
@@ -254,6 +388,15 @@ class TestDiscovery(base.BaseTestCase):
|
||||
self.assertEqual("a75c2fa5-6c03-45a8-bbf7-b993cfcdec27",
|
||||
metadata["instance_id"])
|
||||
self.assertEqual("m1.tiny", metadata["instance_type"])
|
||||
self.assertEqual({"name": "m1.tiny",
|
||||
"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62",
|
||||
"ram": 512,
|
||||
"disk": 1,
|
||||
"swap": 0,
|
||||
"ephemeral": 0,
|
||||
"vcpus": 1,
|
||||
"extra_specs": {"hw_rng:allowed": "true"}},
|
||||
metadata["flavor"])
|
||||
self.assertEqual(
|
||||
"4d0bc931ea7f0513da2efd9acb4cf3a273c64b7bcc544e15c070e662",
|
||||
metadata["host"])
|
||||
@@ -262,6 +405,330 @@ class TestDiscovery(base.BaseTestCase):
|
||||
self.assertEqual("running", metadata["state"])
|
||||
self.assertEqual("hvm", metadata["os_type"])
|
||||
self.assertEqual("x86_64", metadata["architecture"])
|
||||
self.assertEqual({"server_group": "group1"},
|
||||
metadata["user_metadata"])
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt_old(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn(
|
||||
domains=[FakeDomain(metadata=LIBVIRT_METADATA_XML_OLD)])
|
||||
mock_get_server.return_value = argparse.Namespace(
|
||||
flavor={"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62"},
|
||||
metadata={"metering.server_group": "group1"})
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_not_called()
|
||||
mock_get_server.assert_called_with(
|
||||
"a75c2fa5-6c03-45a8-bbf7-b993cfcdec27")
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
"carrot", 1)
|
||||
self.assertEqual("a75c2fa5-6c03-45a8-bbf7-b993cfcdec27",
|
||||
s.resource_id)
|
||||
self.assertEqual("d99c829753f64057bc0f2030da309943",
|
||||
s.project_id)
|
||||
self.assertEqual("a1f4684e58bd4c88aefd2ecb0783b497",
|
||||
s.user_id)
|
||||
|
||||
metadata = s.resource_metadata
|
||||
self.assertEqual(1, metadata["vcpus"])
|
||||
self.assertEqual(512, metadata["memory_mb"])
|
||||
self.assertEqual(1, metadata["disk_gb"])
|
||||
self.assertEqual(0, metadata["ephemeral_gb"])
|
||||
self.assertEqual(1, metadata["root_gb"])
|
||||
self.assertEqual("bdaf114a-35e9-4163-accd-226d5944bf11",
|
||||
metadata["image_ref"])
|
||||
self.assertEqual("test.dom.com", metadata["display_name"])
|
||||
self.assertEqual("instance-00000001", metadata["name"])
|
||||
self.assertEqual("a75c2fa5-6c03-45a8-bbf7-b993cfcdec27",
|
||||
metadata["instance_id"])
|
||||
self.assertEqual("m1.tiny", metadata["instance_type"])
|
||||
self.assertEqual({"name": "m1.tiny",
|
||||
"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62",
|
||||
"ram": 512,
|
||||
"disk": 1,
|
||||
"swap": 0,
|
||||
"ephemeral": 0,
|
||||
"vcpus": 1},
|
||||
metadata["flavor"])
|
||||
self.assertEqual(
|
||||
"4d0bc931ea7f0513da2efd9acb4cf3a273c64b7bcc544e15c070e662",
|
||||
metadata["host"])
|
||||
self.assertEqual(self.CONF.host, metadata["instance_host"])
|
||||
self.assertEqual("active", metadata["status"])
|
||||
self.assertEqual("running", metadata["state"])
|
||||
self.assertEqual("hvm", metadata["os_type"])
|
||||
self.assertEqual("x86_64", metadata["architecture"])
|
||||
self.assertEqual({"server_group": "group1"},
|
||||
metadata["user_metadata"])
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt_no_extra_metadata(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
self.CONF.set_override("fetch_extra_metadata", False, group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn()
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_not_called()
|
||||
mock_get_server.assert_not_called()
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
"carrot", 1)
|
||||
|
||||
metadata = s.resource_metadata
|
||||
self.assertNotIn("user_metadata", metadata)
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt_empty_flavor_id_get_by_flavor(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
self.CONF.set_override("fetch_extra_metadata", False, group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn(
|
||||
domains=[FakeDomain(
|
||||
metadata=LIBVIRT_METADATA_XML_EMPTY_FLAVOR_ID)])
|
||||
mock_get_flavor_id.return_value = (
|
||||
"eba4213d-3c6c-4b5f-8158-dd0022d71d62")
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_called_with("m1.tiny")
|
||||
mock_get_server.assert_not_called()
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
"carrot", 1)
|
||||
|
||||
metadata = s.resource_metadata
|
||||
self.assertEqual("m1.tiny", metadata["instance_type"])
|
||||
self.assertEqual({"name": "m1.tiny",
|
||||
"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62",
|
||||
"ram": 512,
|
||||
"disk": 1,
|
||||
"swap": 0,
|
||||
"ephemeral": 0,
|
||||
"vcpus": 1,
|
||||
"extra_specs": {"hw_rng:allowed": "true"}},
|
||||
metadata["flavor"])
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt_empty_flavor_id_get_by_server(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
self.CONF.set_override("fetch_extra_metadata", True, group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn(
|
||||
domains=[FakeDomain(
|
||||
metadata=LIBVIRT_METADATA_XML_EMPTY_FLAVOR_ID)])
|
||||
mock_get_server.return_value = argparse.Namespace(
|
||||
flavor={"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62"},
|
||||
metadata={})
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_not_called()
|
||||
mock_get_server.assert_called_with(
|
||||
"a75c2fa5-6c03-45a8-bbf7-b993cfcdec27")
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
"carrot", 1)
|
||||
|
||||
metadata = s.resource_metadata
|
||||
self.assertEqual("m1.tiny", metadata["instance_type"])
|
||||
self.assertEqual({"name": "m1.tiny",
|
||||
"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62",
|
||||
"ram": 512,
|
||||
"disk": 1,
|
||||
"swap": 0,
|
||||
"ephemeral": 0,
|
||||
"vcpus": 1,
|
||||
"extra_specs": {"hw_rng:allowed": "true"}},
|
||||
metadata["flavor"])
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt_no_flavor_id_get_by_flavor(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
self.CONF.set_override("fetch_extra_metadata", False, group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn(
|
||||
domains=[FakeDomain(metadata=LIBVIRT_METADATA_XML_NO_FLAVOR_ID)])
|
||||
mock_get_flavor_id.return_value = (
|
||||
"eba4213d-3c6c-4b5f-8158-dd0022d71d62")
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_called_with("m1.tiny")
|
||||
mock_get_server.assert_not_called()
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
"carrot", 1)
|
||||
|
||||
metadata = s.resource_metadata
|
||||
self.assertEqual("m1.tiny", metadata["instance_type"])
|
||||
self.assertEqual({"name": "m1.tiny",
|
||||
"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62",
|
||||
"ram": 512,
|
||||
"disk": 1,
|
||||
"swap": 0,
|
||||
"ephemeral": 0,
|
||||
"vcpus": 1,
|
||||
"extra_specs": {"hw_rng:allowed": "true"}},
|
||||
metadata["flavor"])
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt_no_flavor_id_get_by_server(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
self.CONF.set_override("fetch_extra_metadata", True, group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn(
|
||||
domains=[FakeDomain(metadata=LIBVIRT_METADATA_XML_NO_FLAVOR_ID)])
|
||||
mock_get_server.return_value = argparse.Namespace(
|
||||
flavor={"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62"},
|
||||
metadata={})
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_not_called()
|
||||
mock_get_server.assert_called_with(
|
||||
"a75c2fa5-6c03-45a8-bbf7-b993cfcdec27")
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
"carrot", 1)
|
||||
|
||||
metadata = s.resource_metadata
|
||||
self.assertEqual("m1.tiny", metadata["instance_type"])
|
||||
self.assertEqual({"name": "m1.tiny",
|
||||
"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62",
|
||||
"ram": 512,
|
||||
"disk": 1,
|
||||
"swap": 0,
|
||||
"ephemeral": 0,
|
||||
"vcpus": 1,
|
||||
"extra_specs": {"hw_rng:allowed": "true"}},
|
||||
metadata["flavor"])
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt_empty_flavor_extra_specs(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
self.CONF.set_override("fetch_extra_metadata", False, group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn(
|
||||
domains=[FakeDomain(
|
||||
metadata=LIBVIRT_METADATA_XML_EMPTY_FLAVOR_EXTRA_SPECS)])
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_not_called()
|
||||
mock_get_server.assert_not_called()
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
"carrot", 1)
|
||||
|
||||
metadata = s.resource_metadata
|
||||
self.assertEqual("m1.tiny", metadata["instance_type"])
|
||||
self.assertEqual({"name": "m1.tiny",
|
||||
"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62",
|
||||
"ram": 512,
|
||||
"disk": 1,
|
||||
"swap": 0,
|
||||
"ephemeral": 0,
|
||||
"vcpus": 1,
|
||||
"extra_specs": {}},
|
||||
metadata["flavor"])
|
||||
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_server")
|
||||
@mock.patch.object(discovery.InstanceDiscovery, "get_flavor_id")
|
||||
@mock.patch("ceilometer.compute.virt.libvirt.utils."
|
||||
"refresh_libvirt_connection")
|
||||
def test_discovery_with_libvirt_no_flavor_extra_specs(
|
||||
self, mock_libvirt_conn,
|
||||
mock_get_flavor_id, mock_get_server):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
self.CONF.set_override("fetch_extra_metadata", False, group="compute")
|
||||
mock_libvirt_conn.return_value = FakeConn(
|
||||
domains=[FakeDomain(
|
||||
metadata=LIBVIRT_METADATA_XML_NO_FLAVOR_EXTRA_SPECS)])
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
|
||||
mock_get_flavor_id.assert_not_called()
|
||||
mock_get_server.assert_not_called()
|
||||
|
||||
self.assertEqual(1, len(resources))
|
||||
r = list(resources)[0]
|
||||
s = util.make_sample_from_instance(self.CONF, r, "metric", "delta",
|
||||
"carrot", 1)
|
||||
|
||||
metadata = s.resource_metadata
|
||||
self.assertEqual("m1.tiny", metadata["instance_type"])
|
||||
self.assertEqual({"name": "m1.tiny",
|
||||
"id": "eba4213d-3c6c-4b5f-8158-dd0022d71d62",
|
||||
"ram": 512,
|
||||
"disk": 1,
|
||||
"swap": 0,
|
||||
"ephemeral": 0,
|
||||
"vcpus": 1},
|
||||
metadata["flavor"])
|
||||
|
||||
def test_discovery_with_legacy_resource_cache_cleanup(self):
|
||||
self.CONF.set_override("instance_discovery_method", "naive",
|
||||
@@ -301,6 +768,25 @@ class TestDiscovery(base.BaseTestCase):
|
||||
resources = dsc.discover(mock.MagicMock())
|
||||
self.assertEqual(0, len(resources))
|
||||
|
||||
def test_get_flavor_id(self):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
fake_flavor = argparse.Namespace(
|
||||
id="eba4213d-3c6c-4b5f-8158-dd0022d71d62")
|
||||
self.client.nova_client.flavors.find.return_value = fake_flavor
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
self.assertEqual(fake_flavor.id, dsc.get_flavor_id("m1.tiny"))
|
||||
|
||||
def test_get_flavor_id_notfound(self):
|
||||
self.CONF.set_override("instance_discovery_method",
|
||||
"libvirt_metadata",
|
||||
group="compute")
|
||||
self.client.nova_client.flavors.find.side_effect = (
|
||||
exceptions.NotFound(404))
|
||||
dsc = discovery.InstanceDiscovery(self.CONF)
|
||||
self.assertIsNone(dsc.get_flavor_id("m1.tiny"))
|
||||
|
||||
def test_get_server(self):
|
||||
self.client.nova_client = mock.MagicMock()
|
||||
self.client.nova_client.servers = mock.MagicMock()
|
||||
|
@@ -0,0 +1,35 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
When using Nova 2025.2 and later, the flavor ID for an instance is now
|
||||
available from the libvirt domain metadata. Ceilometer now takes advantage
|
||||
of this and populates the flavor ID from metadata instead of querying
|
||||
Nova, when the value is available. If not available from metadata,
|
||||
Ceilometer will fallback to querying Nova API for the flavor ID.
|
||||
- |
|
||||
When using Nova 2025.2 and later, the extra specs for the flavor and
|
||||
instance is running is now available from the libvirt domain metadata.
|
||||
Ceilometer now adds the flavor's extra specs to compute sample metadata
|
||||
when found.
|
||||
- |
|
||||
Added the ``[compute]/fetch_extra_metadata`` configuration option, which
|
||||
allows configuration of whether or not Ceilometer fetches additional
|
||||
compute instance metadata attributes that require Nova API queries.
|
||||
This mainly affects the ``user_metadata`` attributes populated with
|
||||
metering-related values such as the server group an instance is part of.
|
||||
When ``fetch_extra_metadata`` is set to ``False``, Ceilometer Compute
|
||||
Agent will not query Nova API for anything unless absolutely necessary.
|
||||
upgrade:
|
||||
- |
|
||||
After Nova has been upgraded to 2025.2 or later, new instances will
|
||||
start providing additional flavor metadata for Ceilometer to use.
|
||||
Instances already running at the time of the upgrade are not updated
|
||||
as part of the process; to update those instances they will need to
|
||||
be cold restarted, cold migrated or shelved-and-unshelved (until this
|
||||
happens, Nova API queries will continue to be performed for those
|
||||
instances).
|
||||
deprecations:
|
||||
- |
|
||||
The newly added ``[compute]/fetch_extra_metadata`` option is set to
|
||||
``True`` by default, but to reduce the amount of load Ceilometer places
|
||||
on Nova this will be changed to ``False`` by default in a future release.
|
Reference in New Issue
Block a user