Fixes the Hyper-V agent individual ports metrics
Replaces aggregated metric values with separated values for each port. Co-Authored-By: Adrian Vladu <avladu@cloudbasesolutions.com> Change-Id: Ie946dff984ef53f014c6c57f8d1d5bb9c6e7596d Closes-Bug: #1289007
This commit is contained in:
parent
52fb98b7b6
commit
5d31b134d1
@ -62,7 +62,14 @@ agent_opts = [
|
||||
'Hyper-V\'s metric APIs. Collected data can by '
|
||||
'retrieved by other apps and services, e.g.: '
|
||||
'Ceilometer. Requires Hyper-V / Windows Server 2012 '
|
||||
'and above'))
|
||||
'and above')),
|
||||
cfg.IntOpt('metrics_max_retries',
|
||||
default=100,
|
||||
help=_('Specifies the maximum number of retries to enable '
|
||||
'Hyper-V\'s port metrics collection. The agent will try '
|
||||
'to enable the feature once every polling_interval '
|
||||
'period for at most metrics_max_retries or until it '
|
||||
'succeedes.'))
|
||||
]
|
||||
|
||||
|
||||
@ -119,6 +126,7 @@ class HyperVNeutronAgent(object):
|
||||
self._polling_interval = CONF.AGENT.polling_interval
|
||||
self._load_physical_network_mappings()
|
||||
self._network_vswitch_map = {}
|
||||
self._port_metric_retries = {}
|
||||
self._set_agent_state()
|
||||
self._setup_rpc()
|
||||
|
||||
@ -294,6 +302,7 @@ class HyperVNeutronAgent(object):
|
||||
|
||||
if CONF.AGENT.enable_metrics_collection:
|
||||
self._utils.enable_port_metrics_collection(port_id)
|
||||
self._port_metric_retries[port_id] = CONF.AGENT.metrics_max_retries
|
||||
|
||||
def _port_unbound(self, port_id):
|
||||
(net_uuid, map) = self._get_network_vswitch_map_by_port_id(port_id)
|
||||
@ -308,6 +317,22 @@ class HyperVNeutronAgent(object):
|
||||
if not map['ports']:
|
||||
self._reclaim_local_network(net_uuid)
|
||||
|
||||
def _port_enable_control_metrics(self):
|
||||
if not CONF.AGENT.enable_metrics_collection:
|
||||
return
|
||||
|
||||
for port_id in self._port_metric_retries.keys():
|
||||
if self._utils.can_enable_control_metrics(port_id):
|
||||
self._utils.enable_control_metrics(port_id)
|
||||
LOG.info(_('Port metrics enabled for port: %s'), port_id)
|
||||
del self._port_metric_retries[port_id]
|
||||
elif self._port_metric_retries[port_id] < 1:
|
||||
self._utils.enable_control_metrics(port_id)
|
||||
LOG.error(_('Port metrics raw enabling for port: %s'), port_id)
|
||||
del self._port_metric_retries[port_id]
|
||||
else:
|
||||
self._port_metric_retries[port_id] -= 1
|
||||
|
||||
def _update_ports(self, registered_ports):
|
||||
ports = self._utils.get_vnic_ids()
|
||||
if ports == registered_ports:
|
||||
@ -413,6 +438,8 @@ class HyperVNeutronAgent(object):
|
||||
# If treat devices fails - must resync with plugin
|
||||
sync = self._process_network_ports(port_info)
|
||||
ports = port_info['current']
|
||||
|
||||
self._port_enable_control_metrics()
|
||||
except Exception as e:
|
||||
LOG.exception(_("Error in agent event loop: %s"), e)
|
||||
sync = True
|
||||
|
@ -247,3 +247,10 @@ class HyperVUtils(object):
|
||||
def enable_port_metrics_collection(self, switch_port_name):
|
||||
raise NotImplementedError(_("Metrics collection is not supported on "
|
||||
"this version of Hyper-V"))
|
||||
|
||||
def enable_control_metrics(self, switch_port_name):
|
||||
raise NotImplementedError(_("Metrics collection is not supported on "
|
||||
"this version of Hyper-V"))
|
||||
|
||||
def can_enable_control_metrics(self, switch_port_name):
|
||||
return False
|
||||
|
@ -33,6 +33,10 @@ class HyperVUtilsV2(utils.HyperVUtils):
|
||||
_STATE_DISABLED = 3
|
||||
_OPERATION_MODE_ACCESS = 1
|
||||
|
||||
_VIRTUAL_SYSTEM_SETTING_DATA = 'Msvm_VirtualSystemSettingData'
|
||||
_VM_SUMMARY_ENABLED_STATE = 100
|
||||
_HYPERV_VM_STATE_ENABLED = 2
|
||||
|
||||
_ACL_DIR_IN = 1
|
||||
_ACL_DIR_OUT = 2
|
||||
|
||||
@ -43,6 +47,10 @@ class HyperVUtilsV2(utils.HyperVUtils):
|
||||
_ACL_ACTION_DENY = 2
|
||||
_ACL_ACTION_METER = 3
|
||||
|
||||
_METRIC_ENABLED = 2
|
||||
_NET_IN_METRIC_NAME = 'Filtered Incoming Network Traffic'
|
||||
_NET_OUT_METRIC_NAME = 'Filtered Outgoing Network Traffic'
|
||||
|
||||
_ACL_APPLICABILITY_LOCAL = 1
|
||||
_ACL_APPLICABILITY_REMOTE = 2
|
||||
|
||||
@ -212,6 +220,51 @@ class HyperVUtilsV2(utils.HyperVUtils):
|
||||
acl_dir, acl_type, self._ACL_ACTION_METER)
|
||||
self._add_virt_feature(port, acl)
|
||||
|
||||
def enable_control_metrics(self, switch_port_name):
|
||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
||||
if not found:
|
||||
return
|
||||
|
||||
metric_svc = self._conn.Msvm_MetricService()[0]
|
||||
metric_names = [self._NET_IN_METRIC_NAME, self._NET_OUT_METRIC_NAME]
|
||||
|
||||
for metric_name in metric_names:
|
||||
metric_def = self._conn.CIM_BaseMetricDefinition(Name=metric_name)
|
||||
if metric_def:
|
||||
metric_svc.ControlMetrics(
|
||||
Subject=port.path_(),
|
||||
Definition=metric_def[0].path_(),
|
||||
MetricCollectionEnabled=self._METRIC_ENABLED)
|
||||
|
||||
def can_enable_control_metrics(self, switch_port_name):
|
||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
||||
if not found:
|
||||
return False
|
||||
|
||||
if not self._is_port_vm_started(port):
|
||||
return False
|
||||
|
||||
# all 4 meter ACLs must be existent first. (2 x direction)
|
||||
acls = port.associators(wmi_result_class=self._PORT_ALLOC_ACL_SET_DATA)
|
||||
acls = [a for a in acls if a.Action == self._ACL_ACTION_METER]
|
||||
if len(acls) < 2:
|
||||
return False
|
||||
return True
|
||||
|
||||
def _is_port_vm_started(self, port):
|
||||
vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
vmsettings = port.associators(
|
||||
wmi_result_class=self._VIRTUAL_SYSTEM_SETTING_DATA)
|
||||
#See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx
|
||||
(ret_val, summary_info) = vs_man_svc.GetSummaryInformation(
|
||||
[self._VM_SUMMARY_ENABLED_STATE],
|
||||
[v.path_() for v in vmsettings])
|
||||
if ret_val or not summary_info:
|
||||
raise utils.HyperVException(msg=_('Cannot get VM summary data '
|
||||
'for: %s') % port.ElementName)
|
||||
|
||||
return summary_info[0].EnabledState is self._HYPERV_VM_STATE_ENABLED
|
||||
|
||||
def create_security_rule(self, switch_port_name, direction, acl_type,
|
||||
local_port, protocol, remote_address):
|
||||
port, found = self._get_switch_port_allocation(switch_port_name, False)
|
||||
|
@ -34,6 +34,8 @@ cfg.CONF.import_opt('enable_metrics_collection',
|
||||
|
||||
class TestHyperVNeutronAgent(base.BaseTestCase):
|
||||
|
||||
_FAKE_PORT_ID = 'fake_port_id'
|
||||
|
||||
def setUp(self):
|
||||
super(TestHyperVNeutronAgent, self).setUp()
|
||||
self.addCleanup(cfg.CONF.reset)
|
||||
@ -109,6 +111,40 @@ class TestHyperVNeutronAgent(base.BaseTestCase):
|
||||
'disconnect_switch_port'):
|
||||
self.agent._port_unbound(net_uuid)
|
||||
|
||||
def test_port_enable_control_metrics_ok(self):
|
||||
cfg.CONF.set_override('enable_metrics_collection', True, 'AGENT')
|
||||
self.agent._port_metric_retries[self._FAKE_PORT_ID] = (
|
||||
cfg.CONF.AGENT.metrics_max_retries)
|
||||
|
||||
with mock.patch.multiple(self.agent._utils,
|
||||
can_enable_control_metrics=mock.MagicMock(),
|
||||
enable_control_metrics=mock.MagicMock()):
|
||||
|
||||
self.agent._utils.can_enable_control_metrics.return_value = True
|
||||
self.agent._port_enable_control_metrics()
|
||||
self.agent._utils.enable_control_metrics.assert_called_with(
|
||||
self._FAKE_PORT_ID)
|
||||
|
||||
self.assertNotIn(self._FAKE_PORT_ID, self.agent._port_metric_retries)
|
||||
|
||||
def test_port_enable_control_metrics_maxed(self):
|
||||
cfg.CONF.set_override('enable_metrics_collection', True, 'AGENT')
|
||||
cfg.CONF.set_override('metrics_max_retries', 3, 'AGENT')
|
||||
self.agent._port_metric_retries[self._FAKE_PORT_ID] = (
|
||||
cfg.CONF.AGENT.metrics_max_retries)
|
||||
|
||||
with mock.patch.multiple(self.agent._utils,
|
||||
can_enable_control_metrics=mock.MagicMock(),
|
||||
enable_control_metrics=mock.MagicMock()):
|
||||
|
||||
self.agent._utils.can_enable_control_metrics.return_value = False
|
||||
for i in range(cfg.CONF.AGENT.metrics_max_retries + 1):
|
||||
self.assertIn(self._FAKE_PORT_ID,
|
||||
self.agent._port_metric_retries)
|
||||
self.agent._port_enable_control_metrics()
|
||||
|
||||
self.assertNotIn(self._FAKE_PORT_ID, self.agent._port_metric_retries)
|
||||
|
||||
def test_treat_devices_added_returns_true_for_missing_device(self):
|
||||
attrs = {'get_device_details.side_effect': Exception()}
|
||||
self.agent.plugin_rpc.configure_mock(**attrs)
|
||||
|
@ -40,6 +40,7 @@ class TestHyperVUtilsV2(base.BaseTestCase):
|
||||
_FAKE_VLAN_ID = "fake_vlan_id"
|
||||
_FAKE_CLASS_NAME = "fake_class_name"
|
||||
_FAKE_ELEMENT_NAME = "fake_element_name"
|
||||
_FAKE_HYPERV_VM_STATE = 'fake_hyperv_state'
|
||||
|
||||
_FAKE_ACL_ACT = 'fake_acl_action'
|
||||
_FAKE_ACL_DIR = 'fake_acl_dir'
|
||||
@ -249,6 +250,102 @@ class TestHyperVUtilsV2(base.BaseTestCase):
|
||||
self._utils._add_virt_feature.assert_called_with(
|
||||
mock_port, mock_acl)
|
||||
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
'._get_switch_port_allocation')
|
||||
def test_enable_control_metrics_ok(self, mock_get_port_allocation):
|
||||
mock_metrics_svc = self._utils._conn.Msvm_MetricService()[0]
|
||||
mock_metrics_def_source = self._utils._conn.CIM_BaseMetricDefinition
|
||||
mock_metric_def = mock.MagicMock()
|
||||
mock_port = mock.MagicMock()
|
||||
mock_get_port_allocation.return_value = (mock_port, True)
|
||||
|
||||
mock_metrics_def_source.return_value = [mock_metric_def]
|
||||
m_call = mock.call(Subject=mock_port.path_.return_value,
|
||||
Definition=mock_metric_def.path_.return_value,
|
||||
MetricCollectionEnabled=self._utils._METRIC_ENABLED)
|
||||
|
||||
self._utils.enable_control_metrics(self._FAKE_PORT_NAME)
|
||||
|
||||
mock_metrics_svc.ControlMetrics.assert_has_calls([m_call, m_call])
|
||||
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
'._get_switch_port_allocation')
|
||||
def test_enable_control_metrics_no_port(self, mock_get_port_allocation):
|
||||
mock_metrics_svc = self._utils._conn.Msvm_MetricService()[0]
|
||||
mock_get_port_allocation.return_value = (None, False)
|
||||
|
||||
self._utils.enable_control_metrics(self._FAKE_PORT_NAME)
|
||||
self.assertEqual(0, mock_metrics_svc.ControlMetrics.call_count)
|
||||
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
'._get_switch_port_allocation')
|
||||
def test_enable_control_metrics_no_def(self, mock_get_port_allocation):
|
||||
mock_metrics_svc = self._utils._conn.Msvm_MetricService()[0]
|
||||
mock_metrics_def_source = self._utils._conn.CIM_BaseMetricDefinition
|
||||
mock_port = mock.MagicMock()
|
||||
|
||||
mock_get_port_allocation.return_value = (mock_port, True)
|
||||
mock_metrics_def_source.return_value = None
|
||||
|
||||
self._utils.enable_control_metrics(self._FAKE_PORT_NAME)
|
||||
self.assertEqual(0, mock_metrics_svc.ControlMetrics.call_count)
|
||||
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
'._is_port_vm_started')
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
'._get_switch_port_allocation')
|
||||
def test_can_enable_control_metrics_true(self, mock_get, mock_is_started):
|
||||
mock_acl = mock.MagicMock()
|
||||
mock_acl.Action = self._utils._ACL_ACTION_METER
|
||||
self._test_can_enable_control_metrics(mock_get, mock_is_started,
|
||||
[mock_acl, mock_acl], True)
|
||||
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
'._is_port_vm_started')
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
'._get_switch_port_allocation')
|
||||
def test_can_enable_control_metrics_false(self, mock_get, mock_is_started):
|
||||
self._test_can_enable_control_metrics(mock_get, mock_is_started, [],
|
||||
False)
|
||||
|
||||
def _test_can_enable_control_metrics(self, mock_get_port, mock_vm_started,
|
||||
acls, expected_result):
|
||||
mock_port = mock.MagicMock()
|
||||
mock_acl = mock.MagicMock()
|
||||
mock_acl.Action = self._utils._ACL_ACTION_METER
|
||||
|
||||
mock_port.associators.return_value = acls
|
||||
mock_get_port.return_value = (mock_port, True)
|
||||
mock_vm_started.return_value = True
|
||||
|
||||
result = self._utils.can_enable_control_metrics(self._FAKE_PORT_NAME)
|
||||
self.assertEqual(expected_result, result)
|
||||
|
||||
def test_is_port_vm_started_true(self):
|
||||
self._test_is_port_vm_started(self._utils._HYPERV_VM_STATE_ENABLED,
|
||||
True)
|
||||
|
||||
def test_is_port_vm_started_false(self):
|
||||
self._test_is_port_vm_started(self._FAKE_HYPERV_VM_STATE, False)
|
||||
|
||||
def _test_is_port_vm_started(self, vm_state, expected_result):
|
||||
mock_svc = self._utils._conn.Msvm_VirtualSystemManagementService()[0]
|
||||
mock_port = mock.MagicMock()
|
||||
mock_vmsettings = mock.MagicMock()
|
||||
mock_summary = mock.MagicMock()
|
||||
mock_summary.EnabledState = vm_state
|
||||
mock_vmsettings.path_.return_value = self._FAKE_RES_PATH
|
||||
|
||||
mock_port.associators.return_value = [mock_vmsettings]
|
||||
mock_svc.GetSummaryInformation.return_value = (self._FAKE_RET_VAL,
|
||||
[mock_summary])
|
||||
|
||||
result = self._utils._is_port_vm_started(mock_port)
|
||||
self.assertEqual(expected_result, result)
|
||||
mock_svc.GetSummaryInformation.assert_called_once_with(
|
||||
[self._utils._VM_SUMMARY_ENABLED_STATE],
|
||||
[self._FAKE_RES_PATH])
|
||||
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
'._remove_virt_feature')
|
||||
@mock.patch('neutron.plugins.hyperv.agent.utilsv2.HyperVUtilsV2'
|
||||
|
Loading…
Reference in New Issue
Block a user