]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Fixes the Hyper-V agent individual ports metrics
authorClaudiu Belu <cbelu@cloudbasesolutions.com>
Thu, 6 Mar 2014 22:29:14 +0000 (14:29 -0800)
committerClaudiu Belu <cbelu@cloudbasesolutions.com>
Wed, 19 Mar 2014 09:56:13 +0000 (02:56 -0700)
Replaces aggregated metric values with separated values for each port.

Co-Authored-By: Adrian Vladu <avladu@cloudbasesolutions.com>
Change-Id: Ie946dff984ef53f014c6c57f8d1d5bb9c6e7596d
Closes-Bug: #1289007

neutron/plugins/hyperv/agent/hyperv_neutron_agent.py
neutron/plugins/hyperv/agent/utils.py
neutron/plugins/hyperv/agent/utilsv2.py
neutron/tests/unit/hyperv/test_hyperv_neutron_agent.py
neutron/tests/unit/hyperv/test_hyperv_utilsv2.py

index cb3054f12058162652e1907bf9bd3abee1f8ffd8..c86626508f1d29f19d69ad3590b314a5d0542a0d 100644 (file)
@@ -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
index b606707d46d0da440d20baee333cd02551a13d78..31439f0b0cba2ffefab7ac40dc3d27a22ed45099 100644 (file)
@@ -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
index cc38db139ab6da822db50743fe680d251c53d900..9a1f7902fc400a07d899b40468f09f4d070996d9 100644 (file)
@@ -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)
index 28455c6b203575fc293dd937e2ca66a6177a2e3e..2eed64b44893a46b603fa0d62f884091927e98ff 100644 (file)
@@ -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)
index 565368a24e5506b8cf5ebb11547f58cb66833e2d..ef8741b9aa47b42155c234662bc7399761a59adf 100644 (file)
@@ -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'