From: Claudiu Belu Date: Thu, 6 Mar 2014 22:29:14 +0000 (-0800) Subject: Fixes the Hyper-V agent individual ports metrics X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=4559a5f381082dba30bb535b69deeb09135da680;p=openstack-build%2Fneutron-build.git Fixes the Hyper-V agent individual ports metrics Replaces aggregated metric values with separated values for each port. Co-Authored-By: Adrian Vladu Change-Id: Ie946dff984ef53f014c6c57f8d1d5bb9c6e7596d Closes-Bug: #1289007 --- diff --git a/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py b/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py index cb3054f12..c86626508 100644 --- a/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py +++ b/neutron/plugins/hyperv/agent/hyperv_neutron_agent.py @@ -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 diff --git a/neutron/plugins/hyperv/agent/utils.py b/neutron/plugins/hyperv/agent/utils.py index b606707d4..31439f0b0 100644 --- a/neutron/plugins/hyperv/agent/utils.py +++ b/neutron/plugins/hyperv/agent/utils.py @@ -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 diff --git a/neutron/plugins/hyperv/agent/utilsv2.py b/neutron/plugins/hyperv/agent/utilsv2.py index cc38db139..9a1f7902f 100644 --- a/neutron/plugins/hyperv/agent/utilsv2.py +++ b/neutron/plugins/hyperv/agent/utilsv2.py @@ -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) diff --git a/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py b/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py index 28455c6b2..2eed64b44 100644 --- a/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py +++ b/neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py @@ -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) diff --git a/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py b/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py index 565368a24..ef8741b9a 100644 --- a/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py +++ b/neutron/tests/unit/hyperv/test_hyperv_utilsv2.py @@ -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'