Periodically save prestera stats to filesystem.

Save a snapshot of the current prestera statistics to the
/tmp filesystem for catawampus to upload to ACS

Change-Id: I4a3b0a95d17f914c6a8a12f7133f117b1de34797
diff --git a/presterastats/Makefile b/presterastats/Makefile
index 3a5f19b..af578cd 100644
--- a/presterastats/Makefile
+++ b/presterastats/Makefile
@@ -9,6 +9,7 @@
 install:
 	mkdir -p $(BINDIR)
 	cp presterastats.py $(BINDIR)/presterastats
+	cp prestera_periodic.py $(BINDIR)/prestera_periodic
 
 install-libs:
 	@echo "No libs to install."
diff --git a/presterastats/prestera_periodic.py b/presterastats/prestera_periodic.py
new file mode 100755
index 0000000..91f146e
--- /dev/null
+++ b/presterastats/prestera_periodic.py
@@ -0,0 +1,121 @@
+#!/usr/bin/python
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Periodically call presterastats and save results to filesystem."""
+
+__author__ = 'poist@google.com (Gregory Poist)'
+
+import errno
+import os
+import subprocess
+import sys
+import tempfile
+import time
+
+import options
+
+
+optspec = """
+presterastats [options]
+--
+startup_delay=    wait this many seconds before first query [60]
+interval=         interval to read statistics [15]
+"""
+
+
+class PresteraPeriodic(object):
+  """Class wrapping a cpss command to request stats."""
+
+  OUTPUT_DIR = '/tmp/prestera'
+
+  def __init__(self, interval):
+    self.interval = interval
+    self.ports_output_file = os.path.join(self.OUTPUT_DIR, 'ports.json')
+
+  def WriteToStderr(self, msg):
+    """Write a message to stderr."""
+
+    sys.stderr.write(msg)
+    sys.stderr.flush()
+
+  def RunPresteraStats(self):
+    """Run presterastats, return command output."""
+    return subprocess.check_output(['presterastats'])
+
+  def AcquireStats(self):
+    """Call the child process and get stats."""
+
+    # Output goes to a temporary file, which is renamed to the destination
+    tmpfile = ''
+    ports_stats = ''
+    try:
+      ports_stats = self.RunPresteraStats()
+    except OSError as ex:
+      self.WriteToStderr('Failed to run presterastats: %s\n' % ex)
+    except subprocess.CalledProcessError as ex:
+      self.WriteToStderr('presterastats exited non-zero: %s\n' % ex)
+
+    if not ports_stats:
+      self.WriteToStderr('Failed to get data from presterastats\n')
+      return
+
+    try:
+      with tempfile.NamedTemporaryFile(delete=False) as fd:
+        if not self.CreateDirs(os.path.dirname(self.ports_output_file)):
+          self.WriteToStderr('Failed to create output directory: %s\n' %
+                             os.path.dirname(self.ports_output_file))
+          return
+        tmpfile = fd.name
+        fd.write(ports_stats)
+        fd.flush()
+        os.fsync(fd.fileno())
+        try:
+          os.rename(tmpfile, self.ports_output_file)
+        except OSError as ex:
+          self.WriteToStderr('Failed to move %s to %s: %s\n' % (
+              tmpfile, self.ports_output_file, ex))
+          return
+    finally:
+      if tmpfile and os.path.exists(tmpfile):
+        os.unlink(tmpfile)
+
+  def CreateDirs(self, dir_to_create):
+    """Recursively creates directories."""
+    try:
+      os.makedirs(dir_to_create)
+    except os.error as ex:
+      if ex.errno == errno.EEXIST:
+        return True
+      self.WriteToStderr('Failed to create directory: %s' % ex)
+      return False
+    return True
+
+  def RunForever(self):
+    while True:
+      self.AcquireStats()
+      time.sleep(self.interval)
+
+
+def main():
+  o = options.Options(optspec)
+  (opt, unused_flags, unused_extra) = o.parse(sys.argv[1:])
+  if opt.startup_delay:
+    time.sleep(opt.startup_delay)
+  prestera = PresteraPeriodic(opt.interval)
+  prestera.RunForever()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/presterastats/prestera_periodic_test.py b/presterastats/prestera_periodic_test.py
new file mode 100644
index 0000000..10be8e4
--- /dev/null
+++ b/presterastats/prestera_periodic_test.py
@@ -0,0 +1,129 @@
+#!/usr/bin/python
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Tests for prestera_periodic."""
+
+__author__ = 'poist@google.com (Gregory Poist)'
+
+import errno
+import os
+import subprocess
+import tempfile
+import unittest
+import prestera_periodic
+
+STATS_JSON = """
+{
+  "port-interface-statistics": {
+    "0/0": {
+      "broadcast_packets_received": 8739,
+      "broadcast_packets_sent": 3,
+      "bytes_received": 32061162,
+      "bytes_sent": 10145704,
+      "multicast_packets_received": 35484,
+      "multicast_packets_sent": 20471,
+      "unicast_packets_received": 22875,
+      "unicast_packets_sent": 20737
+    }
+  }
+}
+"""
+
+
+class FakePresteraPeriodic(prestera_periodic.PresteraPeriodic):
+  """Mock PresteraPeriodic."""
+
+  def WriteToStderr(self, msg):
+    self.error_count += 1
+
+  def RunPresteraStats(self):
+    self.get_stats_called = True
+    if self.raise_os_error:
+      raise OSError(errno.ENOENT, 'raise an exception')
+    if self.raise_subprocess:
+      raise subprocess.CalledProcessError(cmd='durp', returncode=1)
+    return self.stats_response
+
+
+class PresteraPeriodicTest(unittest.TestCase):
+
+  def CreateTempFile(self):
+    # Create a temp file and have that be the target output file.
+    fd, self.output_file = tempfile.mkstemp()
+    os.close(fd)
+
+  def DeleteTempFile(self):
+    if os.path.exists(self.output_file):
+      os.unlink(self.output_file)
+
+  def setUp(self):
+    self.CreateTempFile()
+    self.periodic = FakePresteraPeriodic(1000)
+    self.periodic.raise_os_error = False
+    self.periodic.raise_subprocess = False
+    self.periodic.stats_response = STATS_JSON
+    self.periodic.ports_output_file = self.output_file
+    self.periodic.error_count = 0
+
+  def tearDown(self):
+    self.DeleteTempFile()
+
+  def testAcquireStats(self):
+    self.periodic.AcquireStats()
+
+    self.assertEquals(True, self.periodic.get_stats_called)
+    with open(self.output_file, 'r') as f:
+      output = ''.join(line for line in f)
+      self.assertEqual(self.periodic.stats_response, output)
+
+  def testAcquireStatsFailureToCreateOutputDir(self):
+    self.periodic.ports_output_file = '/root/nope/cant/write/this'
+
+    self.periodic.AcquireStats()
+    self.assertTrue(self.periodic.error_count > 0)
+
+  def testSubsequentEmptyDataNoOverwrite(self):
+    self.periodic.AcquireStats()
+
+    self.periodic.stats_response = ''
+    self.periodic.AcquireStats()
+
+    with open(self.output_file, 'r') as f:
+      output = ''.join(line for line in f)
+      self.assertEqual(STATS_JSON, output)
+
+  def testSubsequentExecError(self):
+    self.periodic.AcquireStats()
+
+    self.periodic.raise_os_error = True
+    self.periodic.AcquireStats()
+
+    self.assertTrue(self.periodic.error_count > 0)
+    with open(self.output_file, 'r') as f:
+      output = ''.join(line for line in f)
+      self.assertEqual(STATS_JSON, output)
+
+  def testExecError(self):
+    self.periodic.raise_subprocess = True
+    self.periodic.AcquireStats()
+
+    self.assertTrue(self.periodic.error_count > 0)
+    with open(self.output_file, 'r') as f:
+      output = ''.join(line for line in f)
+      self.assertEqual('', output)
+
+
+if __name__ == '__main__':
+  unittest.main()