From 00360bf03e331a71432aad110c8304e121a44f98 Mon Sep 17 00:00:00 2001
From: Nicolas Wavrant <nicolas.wavrant@tiolive.com>
Date: Wed, 18 Dec 2013 12:07:57 +0100
Subject: [PATCH] stack-monitor: creation of the stack. Right now, it can run
 promises and monitoring scripts(manually added), and create a false rss feed

---
 stack/monitor/buildout.cfg             | 88 ++++++++++++++++++++++++++
 stack/monitor/make-rss.sh.in           |  9 +++
 stack/monitor/monitor.cfg.in           | 77 ++++++++++++++++++++++
 stack/monitor/monitor.py.in            | 73 +++++++++++++++++++++
 stack/monitor/run-monitor-script.sh.in |  4 ++
 stack/monitor/status2rss.py            | 46 ++++++++++++++
 6 files changed, 297 insertions(+)
 create mode 100644 stack/monitor/buildout.cfg
 create mode 100644 stack/monitor/make-rss.sh.in
 create mode 100644 stack/monitor/monitor.cfg.in
 create mode 100644 stack/monitor/monitor.py.in
 create mode 100644 stack/monitor/run-monitor-script.sh.in
 create mode 100644 stack/monitor/status2rss.py

diff --git a/stack/monitor/buildout.cfg b/stack/monitor/buildout.cfg
new file mode 100644
index 000000000..6cd24b63b
--- /dev/null
+++ b/stack/monitor/buildout.cfg
@@ -0,0 +1,88 @@
+[buildout]
+
+extends =
+  ../../component/dcron/buildout.cfg
+
+parts =
+  backup-script-template
+  collective.recipe.template-egg
+  dcron
+  eggs
+  extra-eggs
+  make-rss
+  monitor-bin
+  monitor-template
+  rss-bin
+  slapos-cookbook
+
+[collective.recipe.template-egg]
+recipe = zc.recipe.egg
+eggs = collective.recipe.template
+  PyRSS2Gen
+  
+[extra-eggs]
+recipe = zc.recipe.egg
+interpreter = pythonforrssgen
+eggs =
+  PyRSS2Gen
+  
+[make-rss-script]
+recipe = slapos.recipe.template
+url = ${:_profile_base_location_}/make-rss.sh.in
+#md5sum = 
+output = ${buildout:directory}/make-rss.sh.in
+mode = 0644
+
+[run-monitor-script]
+recipe = hexagonit.recipe.download
+url = ${:_profile_base_location_}/${:filename}
+download-only = true
+#md5sum = 
+filename = run-monitor-script.sh.in
+mode = 0644
+
+[monitor-template]
+recipe = slapos.recipe.template
+url = ${:_profile_base_location_}/monitor.cfg.in
+output = ${buildout:directory}/monitor.cfg
+#md5sum = 6564a2e7c7d9e631b997ff2960ad3299
+mode = 0644
+
+[monitor-bin]
+recipe = hexagonit.recipe.download
+url = ${:_profile_base_location_}/${:filename}
+download-only = true
+#md5sum = 
+filename = monitor.py.in
+mode = 0644
+
+[rss-bin]
+recipe = hexagonit.recipe.download
+url = ${:_profile_base_location_}/${:filename}
+download-only = true
+#md5sum = 
+filename = status2rss.py
+mode = 0644
+
+[dcron-service]
+recipe = slapos.recipe.template
+url = ${template-dcron-service:output}
+output = $${directory:services}/crond
+mode = 0700
+logfile = $${directory:log}/crond.log
+
+#[status2rss]
+#recipe = slapos.recipe.download
+#url = ${:_profile_base_location_}/status2rss.py
+#md5sum = 916f37f083b1ef391adea2f7a717bf8a
+#location = ${buildout:parts-directory}/${:_buildout_section_name_}/status2rss.py
+#mode = 0644
+
+[eggs]
+recipe = z3c.recipe.scripts
+eggs =
+  slapos.cookbook
+  PyRSS2Gen
+
+[versions]
+PyRSS2Gen = 1.1
diff --git a/stack/monitor/make-rss.sh.in b/stack/monitor/make-rss.sh.in
new file mode 100644
index 000000000..a788bd87e
--- /dev/null
+++ b/stack/monitor/make-rss.sh.in
@@ -0,0 +1,9 @@
+#!${dash-output:dash}
+
+status=$${directory:monitor-result}/monitor.bool
+RSS_DIR=$${directory:monitor-result}
+PYTHON=${buildout:directory}/bin/${extra-eggs:interpreter}
+STATUS2RSS=${rss-bin:location}/${rss-bin:filename}
+
+NAME=`basename $status`
+cat $status | $PYTHON $STATUS2RSS "Backup status $NAME" "http://localhost/$NAME.rss" > $RSS_DIR/monitor.rss
\ No newline at end of file
diff --git a/stack/monitor/monitor.cfg.in b/stack/monitor/monitor.cfg.in
new file mode 100644
index 000000000..4cef1bbb9
--- /dev/null
+++ b/stack/monitor/monitor.cfg.in
@@ -0,0 +1,77 @@
+[directory]
+home = $${buildout:directory}
+etc = $${:home}/etc
+bin = $${:home}/bin
+var = $${:home}/var
+cron-entries = $${:etc}/cron.d
+crontabs = $${:etc}/crontabs
+cronstamps = $${:etc}/cronstamps
+log = $${:var}/log
+monitor = $${:etc}/monitor
+monitor-result = $${:var}/monitor
+promise = $${:etc}/promise
+
+[cron]
+recipe = slapos.cookbook:cron
+dcrond-binary = ${dcron:location}/sbin/crond
+cron-entries = $${directory:cron-entries}
+crontabs = $${directory:crontabs}
+cronstamps = $${directory:cronstamps}
+catcher = $${cron-simplelogger:wrapper}
+binary = $${directory:service}/crond
+
+# Add log to cron
+[cron-simplelogger]
+recipe = slapos.cookbook:simplelogger
+wrapper = $${directory:bin}/cron_simplelogger
+log = $${directory:log}/cron.log
+
+[cron-entry-monitor]
+<= cron
+recipe = slapos.cookbook:cron.d
+name = launch-monitor
+frequency = * * * * *
+command = $${deploy-run-monitor-script:rendered}
+
+[cron-entry-rss]
+<= cron
+recipe = slapos.cookbook:cron.d
+name = build-rss
+frequency = * * * * *
+command = $${make-rss:output}
+
+[deploy-run-monitor-script]
+recipe = slapos.recipe.template:jinja2
+template = ${run-monitor-script:location}/${run-monitor-script:filename}
+rendered = $${directory:bin}/run-monitor.sh
+mode = 0744
+context =
+  raw dash_bin ${dash:location}/bin/dash
+  key monitor_bin deploy-monitor-script:rendered
+  key output_directory directory:monitor-result
+  raw output_file_verbose monitor.json
+  raw output_file_quiet monitor.bool
+
+[deploy-monitor-script]
+recipe = slapos.recipe.template:jinja2
+template = ${monitor-bin:location}/${monitor-bin:filename}
+rendered = $${directory:bin}/monitor.py
+mode = 0744
+context =
+  section directory directory
+  
+[deploy-rss-script]
+recipe = hexagonit.recipe.download
+url = ${rss-bin:destination}/${rss-bin:filename}
+destination = $${directory:bin}
+filename = ${rss-bin:filename}
+#md5sum =
+mode = 0744
+download-only = true
+
+[make-rss]
+recipe = slapos.recipe.template
+url = ${make-rss-script:output}
+output = $${directory:bin}/make-rss.sh
+#md5sum = 
+mode = 0744
\ No newline at end of file
diff --git a/stack/monitor/monitor.py.in b/stack/monitor/monitor.py.in
new file mode 100644
index 000000000..7109cd08a
--- /dev/null
+++ b/stack/monitor/monitor.py.in
@@ -0,0 +1,73 @@
+#!/usr/bin/python
+
+import json
+import os
+import subprocess
+import sys
+import time
+from optparse import OptionParser
+
+promise_dir = "{{ directory['promise'] }}"
+service_dir = "{{ directory['service'] }}"
+monitor_dir = "{{ directory['monitor'] }}"
+instance_path = "{{ directory['home'] }}"
+
+def getListOfScripts():
+  scripts = []
+  for dir in (promise_dir, monitor_dir):
+    if os.path.exists(dir) and os.path.isdir(dir):
+      for file in os.listdir(dir):
+        scripts.append(os.path.join(dir, file))
+  if scripts:
+    return scripts
+  else:
+    exit("There is a problem in your directories" \
+          "of monitoring. Please check them")
+
+def run():
+  scripts = getListOfScripts()
+  script_timeout = 3
+  failed_scripts = []
+  failed_bool = False
+  for script_path in scripts:
+    command = [os.path.join(promise_dir, script_path)]
+    script = os.path.basename(command[0])
+    
+    process_handler = subprocess.Popen(command,
+                                       cwd=instance_path,
+                                       env=None if sys.platform == 'cygwin' else {},
+                                       stdout=subprocess.PIPE,
+                                       stderr=subprocess.PIPE,
+                                       stdin=subprocess.PIPE)
+    process_handler.stdin.flush()
+    process_handler.stdin.close()
+    process_handler.stdin = None
+
+    time.sleep(script_timeout)
+
+    if process_handler.poll() is None:
+      process_handler.terminate()
+      failed_bool = True
+      failed_scripts.append({script_path : "Time Out"})
+    elif process_handler.poll() != 0:
+      stderr = process_handler.communicate()[1]
+      if stderr is not None:
+        failed_bool = True
+        failed_scripts.append({script_path : stderr.strip()})
+  return failed_scripts
+
+
+if __name__ == "__main__":
+  parser = OptionParser()
+  parser.add_option("-v", "--verbose", action="store_true", dest="verbose",
+                    help="return a json containing info for each monitored script")
+  (options, args) = parser.parse_args()
+  fails = run()
+  if options.verbose:
+    print fails
+  elif len(fails) == 0:
+    print 0
+    exit(0)
+  else:
+    print 1
+    exit(1)
\ No newline at end of file
diff --git a/stack/monitor/run-monitor-script.sh.in b/stack/monitor/run-monitor-script.sh.in
new file mode 100644
index 000000000..75d15bca3
--- /dev/null
+++ b/stack/monitor/run-monitor-script.sh.in
@@ -0,0 +1,4 @@
+#!{{ dash_bin }}
+
+echo "`date`, `{{ monitor_bin }} -v`" > {{ output_directory }}/{{ output_file_verbose }}
+echo "`date`, `{{ monitor_bin }}`" > {{ output_directory }}/{{ output_file_quiet }}
\ No newline at end of file
diff --git a/stack/monitor/status2rss.py b/stack/monitor/status2rss.py
new file mode 100644
index 000000000..a1e7f7f5c
--- /dev/null
+++ b/stack/monitor/status2rss.py
@@ -0,0 +1,46 @@
+import datetime
+import uuid
+import PyRSS2Gen
+import sys
+from email.utils import parsedate_tz, mktime_tz
+import base64
+
+# Based on http://thehelpfulhacker.net/2011/03/27/a-rss-feed-for-your-crontabs/
+
+# ### Defaults
+TITLE = sys.argv[1]
+LINK = sys.argv[2]
+DESCRIPTION = TITLE
+
+items = []
+
+while 1:
+  try:
+    line = sys.stdin.readline()
+  except KeyboardInterrupt:
+    break
+
+  if not line:
+    break
+
+  time, desc = line.split(',', 1)
+
+  rss_item = PyRSS2Gen.RSSItem(
+    title = desc,
+    description = "%s, %s" % (time, desc),
+    link = LINK,
+    pubDate = datetime.datetime.fromtimestamp(mktime_tz(parsedate_tz(time))),
+    guid = PyRSS2Gen.Guid(base64.b64encode("%s, %s" % (time, desc)))
+    )
+  items.append(rss_item)
+
+### Build the rss feed
+rss_feed = PyRSS2Gen.RSS2 (
+  title = TITLE,
+  link = LINK,
+  description = DESCRIPTION,
+  lastBuildDate = datetime.datetime.utcnow(),
+  items = items
+  )
+
+print rss_feed.to_xml()
\ No newline at end of file
-- 
2.30.9