Implement the network stats for PowerVM

This change set implements the network stats for the PowerVM hypervisor.
It provides both rate based and absolute stats.

Partially Implements: bp/powervm-compute-inspector

Change-Id: I7c07691e61c8f5a6d5cb28185c90cdecb5b47bf6
This commit is contained in:
Drew Thorstensen 2015-07-02 12:45:35 -04:00
parent a0d328df08
commit c1b75d2575
2 changed files with 307 additions and 5 deletions

View File

@ -14,13 +14,17 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
from oslo_log import log as logging
from pypowervm import adapter as pvm_adpt
from pypowervm.helpers import log_helper as log_hlp
from pypowervm.helpers import vios_busy as vio_hlp
from pypowervm.tasks.monitor import util as pvm_mon_util
from pypowervm.utils import uuid as pvm_uuid
from pypowervm.wrappers import logical_partition as pvm_lpar
from pypowervm.wrappers import managed_system as pvm_ms
from pypowervm.wrappers import network as pvm_net
from ceilometer.compute.virt import inspector as virt_inspector
from ceilometer.i18n import _
@ -39,18 +43,18 @@ class PowerVMInspector(virt_inspector.Inspector):
super(PowerVMInspector, self).__init__()
# Build the adapter to the PowerVM API.
adpt = pvm_adpt.Adapter(
self.adpt = pvm_adpt.Adapter(
pvm_adpt.Session(), helpers=[log_hlp.log_helper,
vio_hlp.vios_busy_retry_helper])
# Get the host system UUID
host_uuid = self._get_host_uuid(adpt)
host_uuid = self._get_host_uuid(self.adpt)
# Ensure that metrics gathering is running for the host.
pvm_mon_util.ensure_ltm_monitors(adpt, host_uuid)
pvm_mon_util.ensure_ltm_monitors(self.adpt, host_uuid)
# Get the VM Metric Utility
self.vm_metrics = pvm_mon_util.LparMetricCache(adpt, host_uuid)
self.vm_metrics = pvm_mon_util.LparMetricCache(self.adpt, host_uuid)
@staticmethod
def _puuid(instance):
@ -189,3 +193,164 @@ class PowerVMInspector(virt_inspector.Inspector):
# Utilization is reported as percents. Therefore, multiply by 100.0
# to get a readable percentage based format.
return virt_inspector.CPUUtilStats(util=util * 100.0)
@staticmethod
def mac_for_metric_cna(metric_cna, client_cnas):
"""Finds the mac address for a given metric.
:param metric_cna: The metric for a given client network adapter (CNA)
:param client_cnas: The list of wrappers from pypowervm for the CNAs
attached to a given instance.
:return: Mac address of the adapter. If unable to be found, then None
is returned.
"""
# TODO(thorst) Investigate optimization in pypowervm for this.
for client_cna in client_cnas:
if client_cna.loc_code == metric_cna.physical_location:
# Found the appropriate mac. The PowerVM format is upper
# cased without colons. Convert it.
mac = client_cna.mac.lower()
return ':'.join(mac[i:i + 2]
for i in range(0, len(mac), 2))
return None
def _get_cnas(self, lpar_uuid):
"""Returns the client VM's Network Adapters.
:param lpar_uuid: The UUID of the VM.
:return: A list of pypowervm CNA wrappers.
"""
client_cna_resp = self.adpt.read(
pvm_lpar.LPAR.schema_type, root_id=lpar_uuid,
child_type=pvm_net.CNA.schema_type)
return pvm_net.CNA.wrap(client_cna_resp)
def inspect_vnics(self, instance):
"""Inspect the vNIC statistics for an instance.
:param instance: the target instance
:return: for each vNIC, the number of bytes & packets
received and transmitted
"""
# Get the current and previous sample. Delta is performed between
# these two.
uuid = self._puuid(instance)
cur_date, cur_metric = self.vm_metrics.get_latest_metric(uuid)
# If the cur_metric is none, then the instance can not be found in the
# sample and an error should be raised.
if cur_metric is None:
raise virt_inspector.InstanceNotFoundException(
_('VM %s not found in PowerVM Metrics Sample') % instance.name)
# If there isn't network information, this is because the Virtual
# I/O Metrics were turned off. Have to pass through this method.
if cur_metric.network is None:
return
# Get the network interfaces. A 'cna' is a Client VM's Network Adapter
client_cnas = self._get_cnas(uuid)
for metric_cna in cur_metric.network.cnas:
# Get the mac, but if it isn't found, then move to the next. Might
# have been removed since the last sample.
mac = self.mac_for_metric_cna(metric_cna, client_cnas)
if mac is None:
continue
# The name will be the location code. MAC is identified from
# above. Others appear libvirt specific.
interface = virt_inspector.Interface(
name=metric_cna.physical_location,
mac=mac, fref=None, parameters=None)
stats = virt_inspector.InterfaceStats(
rx_bytes=metric_cna.received_bytes,
rx_packets=metric_cna.received_packets,
tx_bytes=metric_cna.sent_bytes,
tx_packets=metric_cna.sent_packets)
# Yield the stats up to the invoker
yield (interface, stats)
def inspect_vnic_rates(self, instance, duration=None):
"""Inspect the vNIC rate statistics for an instance.
:param instance: the target instance
:param duration: the last 'n' seconds, over which the value should be
inspected
The PowerVM implementation does not make use of the duration
field.
:return: for each vNIC, the rate of bytes & packets
received and transmitted
"""
# Get the current and previous sample. Delta is performed between
# these two.
uuid = self._puuid(instance)
cur_date, cur_metric = self.vm_metrics.get_latest_metric(uuid)
prev_date, prev_metric = self.vm_metrics.get_previous_metric(uuid)
# If the current is none, then the instance can not be found in the
# sample and an error should be raised.
if cur_metric is None:
raise virt_inspector.InstanceNotFoundException(
_('VM %s not found in PowerVM Metrics Sample') % instance.name)
# If there isn't network information, this is because the Virtual
# I/O Metrics were turned off. Have to pass through this method.
if cur_metric.network is None:
return
# Get the network interfaces. A 'cna' is a Client VM's Network Adapter
client_cnas = self._get_cnas(uuid)
def find_prev_net(metric_cna):
"""Finds the metric vNIC from the previous sample's vNICs."""
# If no previous, return None
if prev_metric is None or prev_metric.network is None:
return None
for prev_cna in prev_metric.network.cnas:
if prev_cna.physical_location == metric_cna.physical_location:
return prev_cna
# Couldn't find a previous. Maybe the interface was recently
# added to the instance? Return None
return None
# Need to determine the time delta between the samples. This is
# usually 30 seconds from the API, but the metrics will be specific.
# However, if there is no previous sample, then we have to estimate.
# Therefore, we estimate 15 seconds - half of the standard 30 seconds.
date_delta = ((cur_date - prev_date) if prev_date is not None else
datetime.timedelta(seconds=15))
date_delta_num = float(date_delta.seconds)
for metric_cna in cur_metric.network.cnas:
# Get the mac, but if it isn't found, then move to the next. Might
# have been removed since the last sample.
mac = self.mac_for_metric_cna(metric_cna, client_cnas)
if mac is None:
continue
# The name will be the location code. MAC is identified from
# above. Others appear libvirt specific.
interface = virt_inspector.Interface(
name=metric_cna.physical_location,
mac=mac, fref=None, parameters=None)
prev = find_prev_net(metric_cna)
rx_bytes_diff = (metric_cna.received_bytes -
(0 if prev is None else prev.received_bytes))
tx_bytes_diff = (metric_cna.sent_bytes -
(0 if prev is None else prev.sent_bytes))
# Stats are the difference in the bytes, divided by the difference
# in time between the two samples.
rx_rate = float(rx_bytes_diff) / float(date_delta_num)
tx_rate = float(tx_bytes_diff) / float(date_delta_num)
stats = virt_inspector.InterfaceRateStats(rx_rate, tx_rate)
# Yield the results back to the invoker.
yield (interface, stats)

View File

@ -14,6 +14,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import datetime
import mock
from ceilometer.compute.virt import inspector as virt_inspector
@ -31,7 +32,8 @@ class TestPowerVMInspector(base.BaseTestCase):
# These fixtures allow for stand up of the unit tests that use
# pypowervm.
self.useFixture(api_fx.AdapterFx())
pvm_adpt_fx = self.useFixture(api_fx.AdapterFx())
self.adpt = pvm_adpt_fx.adpt
pvm_mon_fx = self.useFixture(pvm_fixtures.PyPowerVMMetrics())
# Individual test cases will set return values on the metrics that
@ -140,3 +142,138 @@ class TestPowerVMInspector(base.BaseTestCase):
# utilization. Fast!
resp = self.inspector.inspect_cpu_util(mock.Mock())
self.assertEqual(300.0, resp.util)
@staticmethod
def _mock_vnic_metric(rec_bytes, tx_bytes, rec_pkts, tx_pkts, phys_loc):
"""Helper method to create a specific mock network metric."""
metric = mock.MagicMock()
metric.received_bytes = rec_bytes
metric.sent_bytes = tx_bytes
metric.received_packets = rec_pkts
metric.sent_packets = tx_pkts
metric.physical_location = phys_loc
return metric
def _build_cur_mock_vnic_metrics(self):
"""Helper method to create mock network metrics."""
cna1 = self._mock_vnic_metric(1000, 1000, 10, 10, 'a')
cna2 = self._mock_vnic_metric(2000, 2000, 20, 20, 'b')
cna3 = self._mock_vnic_metric(3000, 3000, 30, 30, 'c')
metric = mock.MagicMock()
metric.network.cnas = [cna1, cna2, cna3]
return metric
def _build_prev_mock_vnic_metrics(self):
"""Helper method to create mock network metrics."""
cna1 = self._mock_vnic_metric(1000, 1000, 10, 10, 'a')
cna2 = self._mock_vnic_metric(200, 200, 20, 20, 'b')
metric = mock.MagicMock()
metric.network.cnas = [cna1, cna2]
return metric
@staticmethod
def _build_mock_cnas():
"""Builds a set of mock client network adapters."""
cna1 = mock.MagicMock()
cna1.loc_code, cna1.mac = 'a', 'AABBCCDDEEFF'
cna2 = mock.MagicMock()
cna2.loc_code, cna2.mac = 'b', 'AABBCCDDEE11'
cna3 = mock.MagicMock()
cna3.loc_code, cna3.mac = 'c', 'AABBCCDDEE22'
return [cna1, cna2, cna3]
@mock.patch('pypowervm.wrappers.network.CNA.wrap')
def test_inspect_vnics(self, mock_wrap):
"""Tests the inspect_vnics inspector method for PowerVM."""
# Validate that an error is raised if the instance can't be found in
# the sample data.
self.mock_metrics.get_latest_metric.return_value = None, None
self.assertRaises(virt_inspector.InstanceNotFoundException,
list, self.inspector.inspect_vnics(mock.Mock()))
# Validate that no data is returned if there is a current metric,
# just no network within it.
mock_empty_net = mock.MagicMock()
mock_empty_net.network = None
self.mock_metrics.get_latest_metric.return_value = None, mock_empty_net
self.assertEqual([], list(self.inspector.inspect_vnics(mock.Mock())))
# Build a couple CNAs and verify we get the proper list back
mock_wrap.return_value = self._build_mock_cnas()
self.adpt.read.return_value = mock.Mock()
mock_metrics = self._build_cur_mock_vnic_metrics()
self.mock_metrics.get_latest_metric.return_value = None, mock_metrics
resp = list(self.inspector.inspect_vnics(mock.Mock()))
self.assertEqual(3, len(resp))
interface1, stats1 = resp[0]
self.assertEqual('aa:bb:cc:dd:ee:ff', interface1.mac)
self.assertEqual('a', interface1.name)
self.assertEqual(1000, stats1.rx_bytes)
self.assertEqual(1000, stats1.tx_bytes)
self.assertEqual(10, stats1.rx_packets)
self.assertEqual(10, stats1.tx_packets)
@mock.patch('pypowervm.wrappers.network.CNA.wrap')
def test_inspect_vnic_rates(self, mock_wrap):
"""Tests the inspect_vnic_rates inspector method for PowerVM."""
# Validate that an error is raised if the instance can't be found in
# the sample data.
self.mock_metrics.get_latest_metric.return_value = None, None
self.mock_metrics.get_previous_metric.return_value = None, None
self.assertRaises(virt_inspector.InstanceNotFoundException,
list, self.inspector.inspect_vnic_rates(mock.Mock()))
# Validate that no data is returned if there is a current metric,
# just no network within it.
mock_empty_net = mock.MagicMock()
mock_empty_net.network = None
self.mock_metrics.get_latest_metric.return_value = None, mock_empty_net
self.assertEqual([],
list(self.inspector.inspect_vnic_rates(mock.Mock())))
# Build the response LPAR data
mock_wrap.return_value = self._build_mock_cnas()
self.adpt.read.return_value = mock.Mock()
# Current metric data
mock_cur = self._build_cur_mock_vnic_metrics()
cur_date = datetime.datetime.now()
self.mock_metrics.get_latest_metric.return_value = cur_date, mock_cur
# Build the previous
mock_prev = self._build_prev_mock_vnic_metrics()
prev_date = cur_date - datetime.timedelta(seconds=30)
self.mock_metrics.get_previous_metric.return_value = (prev_date,
mock_prev)
# Execute
resp = list(self.inspector.inspect_vnic_rates(mock.Mock()))
self.assertEqual(3, len(resp))
# First metric. No delta
interface1, stats1 = resp[0]
self.assertEqual('aa:bb:cc:dd:ee:ff', interface1.mac)
self.assertEqual('a', interface1.name)
self.assertEqual(0, stats1.rx_bytes_rate)
self.assertEqual(0, stats1.tx_bytes_rate)
# Second metric
interface2, stats2 = resp[1]
self.assertEqual('aa:bb:cc:dd:ee:11', interface2.mac)
self.assertEqual('b', interface2.name)
self.assertEqual(60.0, stats2.rx_bytes_rate)
self.assertEqual(60.0, stats2.tx_bytes_rate)
# Third metric had no previous.
interface3, stats3 = resp[2]
self.assertEqual('aa:bb:cc:dd:ee:22', interface3.mac)
self.assertEqual('c', interface3.name)
self.assertEqual(100.0, stats3.rx_bytes_rate)
self.assertEqual(100.0, stats3.tx_bytes_rate)