AMBARI-20424. AMS should skip network stats from virtual network interfaces. (mpapirkovskyy)
diff --git a/ambari-metrics-host-monitoring/conf/unix/metric_monitor.ini b/ambari-metrics-host-monitoring/conf/unix/metric_monitor.ini
index e98c65c..7fe7397 100644
--- a/ambari-metrics-host-monitoring/conf/unix/metric_monitor.ini
+++ b/ambari-metrics-host-monitoring/conf/unix/metric_monitor.ini
@@ -22,6 +22,8 @@
 enable_time_threshold = false
 enable_value_threshold = false
 skip_disk_patterns =
+skip_virtual_interfaces = false
+skip_network_interfaces_patterns =
 
 [emitter]
 send_interval = 60
diff --git a/ambari-metrics-host-monitoring/src/main/python/core/config_reader.py b/ambari-metrics-host-monitoring/src/main/python/core/config_reader.py
index 7010187..5686c50 100644
--- a/ambari-metrics-host-monitoring/src/main/python/core/config_reader.py
+++ b/ambari-metrics-host-monitoring/src/main/python/core/config_reader.py
@@ -22,7 +22,6 @@
 import StringIO
 import json
 import os
-import ast
 from ambari_commons import OSConst
 from ambari_commons.os_family_impl import OsFamilyImpl
 
@@ -254,3 +253,9 @@
 
   def get_disk_metrics_skip_pattern(self):
     return self.get("default", "skip_disk_patterns")
+
+  def get_virtual_interfaces_skip(self):
+    return self.get("default", "skip_virtual_interfaces")
+
+  def get_network_interfaces_skip_pattern(self):
+    return self.get("default", "skip_network_interfaces_patterns")
diff --git a/ambari-metrics-host-monitoring/src/main/python/core/host_info.py b/ambari-metrics-host-monitoring/src/main/python/core/host_info.py
index 90f73fa..035c833 100644
--- a/ambari-metrics-host-monitoring/src/main/python/core/host_info.py
+++ b/ambari-metrics-host-monitoring/src/main/python/core/host_info.py
@@ -19,14 +19,14 @@
 '''
 
 import logging
-import psutil
+import operator
 import os
 import platform
-import time
-import threading
-import socket
-import operator
+import psutil
 import re
+import socket
+import threading
+import time
 from collections import namedtuple
 
 logger = logging.getLogger()
@@ -153,13 +153,22 @@
 
     net_stats = psutil.net_io_counters(True)
     new_net_stats = {}
+
+    skip_virtual_interfaces = self.get_virtual_network_interfaces() if self.__config.get_virtual_interfaces_skip() == 'True' else []
+    skip_network_patterns = self.__config.get_network_interfaces_skip_pattern()
+    skip_network_patterns_list = skip_network_patterns.split(',') if skip_network_patterns and skip_network_patterns != 'None' else []
     for interface, values in net_stats.iteritems():
-      if interface != 'lo':
-        new_net_stats = {'bytes_out': new_net_stats.get('bytes_out', 0) + values.bytes_sent,
-                         'bytes_in': new_net_stats.get('bytes_in', 0) + values.bytes_recv,
-                         'pkts_out': new_net_stats.get('pkts_out', 0) + values.packets_sent,
-                         'pkts_in': new_net_stats.get('pkts_in', 0) + values.packets_recv
-        }
+      if interface != 'lo' and not interface in skip_virtual_interfaces:
+        ignore_network = False
+        for p in skip_network_patterns_list:
+          if re.match(p, interface):
+            ignore_network = True
+        if not ignore_network:
+          new_net_stats = {'bytes_out': new_net_stats.get('bytes_out', 0) + values.bytes_sent,
+                           'bytes_in': new_net_stats.get('bytes_in', 0) + values.bytes_recv,
+                           'pkts_out': new_net_stats.get('pkts_out', 0) + values.packets_sent,
+                           'pkts_in': new_net_stats.get('pkts_in', 0) + values.packets_recv
+          }
 
     with self.__last_network_lock:
       result = dict((k, (v - self.__last_network_data.get(k, 0)) / delta) for k, v in new_net_stats.iteritems())
@@ -354,3 +363,13 @@
 
   def get_ip_address(self):
     return socket.gethostbyname(socket.getfqdn())
+
+  def get_virtual_network_interfaces(self):
+    sys_net_path = "/sys/class/net"
+    net_devices = []
+    if os.path.isdir(sys_net_path):
+      links = [f for f in os.listdir(sys_net_path) if os.path.islink(os.path.join(sys_net_path, f))]
+      for link in links:
+        if "devices/virtual" in os.readlink(os.path.join(sys_net_path, link)):
+          net_devices.append(link)
+    return net_devices
diff --git a/ambari-metrics-host-monitoring/src/test/python/core/TestHostInfo.py b/ambari-metrics-host-monitoring/src/test/python/core/TestHostInfo.py
index 63a1ae1..1b249c7 100644
--- a/ambari-metrics-host-monitoring/src/test/python/core/TestHostInfo.py
+++ b/ambari-metrics-host-monitoring/src/test/python/core/TestHostInfo.py
@@ -18,13 +18,12 @@
 limitations under the License.
 '''
 
-import logging
-from host_info import HostInfo
-import platform
-from unittest import TestCase
-from mock.mock import patch, MagicMock
-from core.config_reader import Configuration
 import collections
+import logging
+import platform
+from host_info import HostInfo
+from mock.mock import patch, MagicMock
+from unittest import TestCase
 
 logger = logging.getLogger()
 
@@ -205,5 +204,145 @@
     self.assertEqual(disk_counter_per_disk['sdisk_sdb1_read_merged_count'], 16)
     self.assertEqual(disk_counter_per_disk['sdisk_sdb1_write_merged_count'], 17)
 
+  @patch.object(HostInfo, "get_virtual_network_interfaces", new = MagicMock(return_value = ['etc11', 'etc2']))
+  @patch("psutil.net_io_counters")
+  def test_get_network_info_virtual_devices(self, net_io_counters):
+    Stats = collections.namedtuple('interface', ['bytes_sent', 'bytes_recv',
+                                                  'packets_sent', 'packets_recv'
+                                                  ])
+
+    net_stats = Stats(bytes_sent = 0, bytes_recv = 1,
+                          packets_sent = 2, packets_recv = 3
+    )
+
+    all_net_stats = { 'etc11' : net_stats, 'etc2' : net_stats }
+    net_io_counters.return_value = all_net_stats
+
+    c = MagicMock()
+
+    #skip virtual devices
+    c.get_virtual_interfaces_skip.return_value = 'True'
+    hostinfo = HostInfo(c)
+    network_info = hostinfo.get_network_info()
+
+    self.assertEqual(network_info, {})
+
+    #do not skip virtual devices
+    c.get_virtual_interfaces_skip.return_value = 'False'
+    hostinfo = HostInfo(c)
+    network_info = hostinfo.get_network_info()
+
+    #len({'bytes_in': ..., 'pkts_in': ..., 'pkts_out': ..., 'bytes_out': ...}) == 4
+    self.assertEqual(len(network_info), 4)
+
+  @patch("psutil.net_io_counters")
+  def test_get_network_info_skip_by_pattern(self, net_io_counters):
+    Stats = collections.namedtuple('interface', ['bytes_sent', 'bytes_recv',
+                                                  'packets_sent', 'packets_recv'
+                                                  ])
+
+    net_stats = Stats(bytes_sent = 0, bytes_recv = 1,
+                          packets_sent = 2, packets_recv = 3
+    )
+
+    all_net_stats = { 'etc11' : net_stats, 'etc2' : net_stats }
+    net_io_counters.return_value = all_net_stats
+
+    c = MagicMock()
+
+    #skip all by pattern
+    c.get_virtual_interfaces_skip.return_value = 'False'
+    c.get_network_interfaces_skip_pattern.return_value = "^etc\d*$"
+    hostinfo = HostInfo(c)
+    network_info = hostinfo.get_network_info()
+
+    self.assertEqual(network_info, {})
+
+    #skip one by pattern
+    c.get_network_interfaces_skip_pattern.return_value = "^etc\d{1}$"
+    hostinfo = HostInfo(c)
+    network_info = hostinfo.get_network_info()
+
+    self.assertEqual(len(network_info), 4)
+
+    all_net_stats = { 'etc2' : net_stats }
+    net_io_counters.return_value = all_net_stats
+    c.get_network_interfaces_skip_pattern.return_value = "^etc\d{1}$"
+    hostinfo = HostInfo(c)
+    network_info = hostinfo.get_network_info()
+
+    self.assertEqual(network_info, {})
+
+    #skip by 'None' pattern
+    c.get_network_interfaces_skip_pattern.return_value = "None"
+    hostinfo = HostInfo(c)
+    network_info = hostinfo.get_network_info()
+
+    self.assertEqual(len(network_info), 4)
+
+  @patch.object(HostInfo, "get_virtual_network_interfaces", new = MagicMock(return_value = ['etc11', 'etc2']))
+  @patch("psutil.net_io_counters")
+  def test_get_network_info_skip_by_pattern_and_virtual(self, net_io_counters):
+    Stats = collections.namedtuple('interface', ['bytes_sent', 'bytes_recv',
+                                                  'packets_sent', 'packets_recv'
+                                                  ])
+
+    net_stats = Stats(bytes_sent = 0, bytes_recv = 1,
+                          packets_sent = 2, packets_recv = 3
+    )
+
+    all_net_stats = { 'etc11' : net_stats, 'etc2' : net_stats, 'etc333' : net_stats }
+    net_io_counters.return_value = all_net_stats
+
+    c = MagicMock()
+
+    #skip only one by pattern and other as virtual
+    c.get_virtual_interfaces_skip.return_value = 'True'
+    c.get_network_interfaces_skip_pattern.return_value = "^etc\d{3}$"
+    hostinfo = HostInfo(c)
+    network_info = hostinfo.get_network_info()
+
+    self.assertEqual(network_info, {})
+
+  @patch("os.path.isdir")
+  @patch("os.listdir")
+  @patch("os.readlink")
+  @patch("os.path.islink")
+  def test_get_virtual_network_interfaces(self, islink, readlink, listdir, isdir):
+    hostinfo = HostInfo(MagicMock())
+
+    #virtual net device is present
+    isdir.return_value = True
+    listdir.return_value = ['virtual_net_dev']
+    readlink.return_value = "../..devices/virtual/int6"
+    islink.return_value = True
+
+    virtual_net_devices = hostinfo.get_virtual_network_interfaces()
+    self.assertEqual(virtual_net_devices, ['virtual_net_dev'])
+
+    #virtual net device is not present
+    isdir.return_value = True
+    listdir.return_value = ['virtual_net_dev']
+    readlink.return_value = "../..devices/pp01.000/virtio1/int6"
+    islink.return_value = True
+
+    virtual_net_devices = hostinfo.get_virtual_network_interfaces()
+    self.assertEqual(virtual_net_devices, [])
+
+    #symlinks not present
+    isdir.return_value = True
+    listdir.return_value = ['virtual_net_dev']
+    readlink.return_value = "../..devices/virtual/int6"
+    islink.return_value = False
+
+    virtual_net_devices = hostinfo.get_virtual_network_interfaces()
+    self.assertEqual(virtual_net_devices, [])
+
+    #sysfs not available
+    isdir.return_value = False
+    virtual_net_devices = hostinfo.get_virtual_network_interfaces()
+
+    self.assertEqual(virtual_net_devices, [])
+