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()