From 16b33bfe76a33dc8eba5631c71d164d5344684de Mon Sep 17 00:00:00 2001
From: Alain Takoudjou <alain.takoudjou@nexedi.com>
Date: Wed, 27 Apr 2016 13:45:06 +0200
Subject: [PATCH] monitor: add support for report script folder

---
 stack/monitor/buildout.cfg                   |  6 +-
 stack/monitor/instance-monitor.cfg.jinja2.in |  3 +-
 stack/monitor/scripts/monitor.py             | 80 +++++++++++++++++---
 stack/monitor/scripts/run-promise.py         | 26 ++++---
 stack/resilient/buildout.cfg                 |  2 +-
 stack/resilient/instance-pull-backup.cfg.in  |  3 +-
 6 files changed, 94 insertions(+), 26 deletions(-)

diff --git a/stack/monitor/buildout.cfg b/stack/monitor/buildout.cfg
index dd9c8bd29..e02165900 100644
--- a/stack/monitor/buildout.cfg
+++ b/stack/monitor/buildout.cfg
@@ -99,7 +99,7 @@ recipe = slapos.recipe.template:jinja2
 filename = template-monitor.cfg
 template = ${:_profile_base_location_}/instance-monitor.cfg.jinja2.in
 rendered = ${buildout:directory}/template-monitor.cfg
-md5sum = cd7f386fe1b5066d8508758249b408d3  
+md5sum = b6da7709da533c5c8280e5e103809c58  
 context =
     key apache_location apache:location
     key gzip_location gzip:location
@@ -130,13 +130,13 @@ depends =
 [monitor2-bin]
 <= monitor-template-script
 filename = monitor.py
-md5sum = 31beec15d3c3cd7979d04ecb834c439a
+md5sum = 6db3d9fcfff32c556f2bedee804ffb47
 
 [run-promise-py]
 recipe = slapos.recipe.template:jinja2
 template = ${:_profile_base_location_}/scripts/run-promise.py
 rendered = ${buildout:parts-directory}/monitor-scripts/run-promise.py
-md5sum = 8ba8b661c55f2c5a379e9e42573be486
+md5sum = 5694920c5789f4d953cb5d41adfa7f8b
 mode = 0755
 context =
   raw python ${buildout:directory}/bin/${extra-eggs:interpreter}
diff --git a/stack/monitor/instance-monitor.cfg.jinja2.in b/stack/monitor/instance-monitor.cfg.jinja2.in
index 81b4b9417..5de39730d 100644
--- a/stack/monitor/instance-monitor.cfg.jinja2.in
+++ b/stack/monitor/instance-monitor.cfg.jinja2.in
@@ -70,7 +70,7 @@ etc = ${directory:etc}
 run = ${directory:monitor}/run
 #run = ${directory:scripts}
 promises = ${directory:monitor-promise}
-report = ${directory:monitor-report}
+reports = ${directory:monitor-report}
 pids = ${directory:run}/monitor
 cgi-bin = ${directory:monitor}/cgi-bin
 webdav = ${directory:monitor}/webdav
@@ -134,6 +134,7 @@ root-title = ${monitor-instance-parameter:root-instance-title}
 public-folder = ${monitor-directory:public}
 private-folder = ${monitor-directory:private}
 webdav-folder = ${monitor-directory:webdav}
+report-folder = ${monitor-directory:reports}
 web-folder = ${monitor-directory:web-dir}
 base-url = ${monitor-instance-parameter:monitor-base-url}
 monitor-hal-json = ${monitor-directory:public}/monitor.hal.json
diff --git a/stack/monitor/scripts/monitor.py b/stack/monitor/scripts/monitor.py
index 19cfcfaf7..38e4ad472 100644
--- a/stack/monitor/scripts/monitor.py
+++ b/stack/monitor/scripts/monitor.py
@@ -94,6 +94,7 @@ class Monitoring(object):
     self.collector_db  = config.get("monitor", "collector-db")
     self.collect_script = config.get("monitor", "collect-script")
     self.webdav_folder = config.get("monitor", "webdav-folder")
+    self.report_script_folder = config.get("monitor", "report-folder")
     self.webdav_url = '%s/share' % config.get("monitor", "base-url")
     self.public_url = '%s/public' % config.get("monitor", "base-url")
     self.status_history_folder = os.path.join(self.public_folder, 'history')
@@ -106,6 +107,7 @@ class Monitoring(object):
     self.parameter_cfg_file = config.get("monitor", "parameter-file-path").strip()
 
     self.config_folder = os.path.join(self.private_folder, 'config')
+    self.report_folder = os.path.join(self.private_folder, 'report')
 
     self.promise_dict = {}
     for promise_folder in self.promise_folder_list:
@@ -267,9 +269,11 @@ class Monitoring(object):
         raise
 
     self.data_folder = os.path.join(self.private_folder, 'data', '.jio_documents')
+    self.report_folder = os.path.join(self.report_folder, '.jio_documents')
     config_folder = os.path.join(self.config_folder, '.jio_documents')
     mkdirAll(self.data_folder)
     mkdirAll(config_folder)
+    mkdirAll(self.report_folder)
     try:
       os.symlink(os.path.join(self.private_folder, 'data'),
                   os.path.join(jio_private, 'data'))
@@ -281,6 +285,11 @@ class Monitoring(object):
     except OSError, e:
       if e.errno != os.errno.EEXIST:
         raise
+    try:
+      os.symlink(self.report_folder, os.path.join(jio_private, 'report'))
+    except OSError, e:
+      if e.errno != os.errno.EEXIST:
+        raise
 
   def makeConfigurationFiles(self):
     config_folder = os.path.join(self.config_folder, '.jio_documents')
@@ -385,6 +394,53 @@ class Monitoring(object):
     with open(self.monitor_hal_json, "w") as fp:
       json.dump(self.monitor_dict, fp)
 
+  def generateReportCronEntries(self):
+    cron_line_list = []
+    # We should add the possibility to modify this parameter later from monitor interface
+    report_frequency = "*/30 * * * *"
+
+    report_name_list = [name.replace('.report.json', '')
+      for name in os.listdir(self.report_folder) if name.endswith('.report.json')]
+
+    for filename in os.listdir(self.report_script_folder):
+      report_script = os.path.join(self.report_script_folder, filename)
+      if os.path.isfile(report_script) and os.access(report_script, os.X_OK):
+        report_name = os.path.splitext(filename)[0]
+        report_json_path = "%s.report.json" % report_name
+
+        report_cmd_line = [
+          report_frequency,
+          self.promise_runner,
+          '--pid_path "%s"' % os.path.join(self.service_pid_folder,
+            "%s.pid" % filename),
+          '--output "%s"' % os.path.join(self.report_folder,report_json_path),
+          '--promise_script "%s"' % report_script,
+          '--promise_name "%s"' % report_name,
+          '--monitor_url "%s/jio_private/"' % self.webdav_url, # XXX hardcoded,
+          '--history_folder "%s"' % self.report_folder,
+          '--instance_name "%s"' % self.title,
+          '--hosting_name "%s"' % self.root_title,
+          '--promise_type "report"']
+
+        cron_line_list.append(' '.join(report_cmd_line))
+
+      if report_name in report_name_list:
+        report_name_list.pop(report_name_list.index(report_name))
+
+    # cleanup removed report json result
+    if report_name_list != []:
+      for report_name in report_name_list:
+        result_path = os.path.join(self.public_folder, '%s.report.json' % report_name)
+        if os.path.exists(result_path):
+          try:
+            os.unlink(result_path)
+          except OSError, e:
+            print "Error: Failed to delete %s" % result_path, str(e)
+            pass
+
+    with open(self.crond_folder + "/monitor-reports", "w") as freport:
+      freport.write("\n".join(cron_line_list))
+
   def generateServiceCronEntries(self):
     # XXX only if at least one configuration file is modified, then write in the cron
     #cron_line_list = ['PATH=%s\n' % os.environ['PATH']]
@@ -401,13 +457,13 @@ class Monitoring(object):
       promise_cmd_line = [
         softConfigGet(service_config, "service", "frequency") or "* * * * *",
         self.promise_runner,
-        '--pid_path %s' % os.path.join(self.service_pid_folder,
+        '--pid_path "%s"' % os.path.join(self.service_pid_folder,
           "%s.pid" % service_name),
-        '--output %s' % service_status_path,
-        '--promise_script %s' % promise["path"],
+        '--output "%s"' % service_status_path,
+        '--promise_script "%s"' % promise["path"],
         '--promise_name "%s"' % service_name,
         '--monitor_url "%s/jio_private/"' % self.webdav_url, # XXX hardcoded,
-        '--history_folder %s' % self.status_history_folder,
+        '--history_folder "%s"' % self.status_history_folder,
         '--instance_name "%s"' % self.title,
         '--hosting_name "%s"' % self.root_title]
 
@@ -442,6 +498,13 @@ class Monitoring(object):
       cronf.write(entry_line)
 
   def bootstrapMonitor(self):
+
+    # create symlinks from monitor.conf
+    self.createSymlinksFromConfig(self.public_folder, self.public_path_list)
+    self.createSymlinksFromConfig(self.private_folder, self.private_path_list)
+
+    self.configureFolders()
+
     # create symlinks from service configurations
     self.promise_items = self.promise_dict.items()
     for service_name, promise in self.promise_items:
@@ -457,12 +520,6 @@ class Monitoring(object):
                                       private_path_list.split(),
                                       service_name)
 
-    # create symlinks from monitor.conf
-    self.createSymlinksFromConfig(self.public_folder, self.public_path_list)
-    self.createSymlinksFromConfig(self.private_folder, self.private_path_list)
-
-    self.configureFolders()
-
     # generate monitor.json
     self.monitor_dict = {}
     self.generateMonitorHalJson()
@@ -474,6 +531,9 @@ class Monitoring(object):
     # put promises to a cron file
     self.generateServiceCronEntries()
 
+    # put report script to cron
+    self.generateReportCronEntries()
+
     # Generate parameters files and scripts
     self.makeConfigurationFiles()
 
diff --git a/stack/monitor/scripts/run-promise.py b/stack/monitor/scripts/run-promise.py
index 50a1b806f..5bfad4102 100644
--- a/stack/monitor/scripts/run-promise.py
+++ b/stack/monitor/scripts/run-promise.py
@@ -24,15 +24,18 @@ def parseArguments():
                       help='Promise script to execute.')
   parser.add_argument('--promise_name',
                       help='Title to give to this promise.')
+  parser.add_argument('--promise_type',
+                      default='status',
+                      help='Type of promise to execute. [status, report].')
   parser.add_argument('--monitor_url',
                       help='Monitor Instance website URL.')
   parser.add_argument('--history_folder',
                       help='Path where old result file will be placed before generate a new json result file.')
   parser.add_argument('--instance_name',
-                      default='UNKNOW Software Instance',
+                      default='UNKNOWN Software Instance',
                       help='Software Instance name.')
   parser.add_argument('--hosting_name',
-                      default='UNKNOW Hosting Subscription',
+                      default='UNKNOWN Hosting Subscription',
                       help='Hosting Subscription name.')
 
   return parser.parse_args()
@@ -74,14 +77,16 @@ def main():
   updateStatusHistoryFolder(
     parser.promise_name,
     parser.output,
-    parser.history_folder
+    parser.history_folder,
+    parser.promise_type
   )
   with open(parser.output, "w") as outputfile:
     json.dump(status_json, outputfile)
   os.remove(parser.pid_path)
 
-def updateStatusHistoryFolder(name, status_file, history_folder):
+def updateStatusHistoryFolder(name, status_file, history_folder, promise_type):
   old_history_list = []
+  keep_item_amount = 25
   history_path = os.path.join(history_folder, name, '.jio_documents')
   if not os.path.exists(status_file):
     return
@@ -96,18 +101,19 @@ def updateStatusHistoryFolder(name, status_file, history_folder):
       else: raise
   with open(status_file, 'r') as sf:
     status_dict = json.loads(sf.read())
-    filename = '%s.status.json' % (
-      status_dict['start-date'].replace(' ', '_').replace(':', ''))
+    filename = '%s.%s.json' % (
+      status_dict['start-date'].replace(' ', '_').replace(':', ''),
+      promise_type)
 
   copyfile(status_file, os.path.join(history_path, filename))
-  # Don't let history foler grow too much, keep 40 files
+  # Don't let history foler grow too much, keep xx files
   file_list = filter(os.path.isfile,
-      glob.glob("%s/*.status.json" % history_path)
+      glob.glob("%s/*.%s.json" % (history_path, promise_type))
     )
   file_count = len(file_list)
-  if file_count > 40:
+  if file_count > keep_item_amount:
     file_list.sort(key=lambda x: os.path.getmtime(x))
-    while file_count > 40:
+    while file_count > keep_item_amount:
       to_delete = file_list.pop(0)
       try:
         os.unlink(to_delete)
diff --git a/stack/resilient/buildout.cfg b/stack/resilient/buildout.cfg
index b6b5c2fe7..b279e6711 100644
--- a/stack/resilient/buildout.cfg
+++ b/stack/resilient/buildout.cfg
@@ -63,7 +63,7 @@ mode = 0644
 recipe = slapos.recipe.template
 url = ${:_profile_base_location_}/instance-pull-backup.cfg.in
 output = ${buildout:directory}/instance-pull-backup.cfg
-md5sum = 028dfc01dfb0d738e1b4793f67b24e8c
+md5sum = cb7acac7ab41bf44c20d6d03bfad8217
 mode = 0644
 
 [template-replicated]
diff --git a/stack/resilient/instance-pull-backup.cfg.in b/stack/resilient/instance-pull-backup.cfg.in
index 47d95389f..26156b3aa 100644
--- a/stack/resilient/instance-pull-backup.cfg.in
+++ b/stack/resilient/instance-pull-backup.cfg.in
@@ -11,6 +11,7 @@ parts =
 
 
 ## Monitor for pbs
+  monitor-base
   monitor-check-resilient-feed-file
 
 extends = ${monitor2-template:rendered}
@@ -266,7 +267,7 @@ private-path-list +=
 [monitor-check-resilient-feed-file]
 recipe = slapos.recipe.template:jinja2
 template = ${template-monitor-check-resilient-feed:location}/${template-monitor-check-resilient-feed:filename}
-rendered = $${monitor-directory:promises}/check-create-resilient-feed-files
+rendered = $${monitor-directory:reports}/check-create-resilient-feed-files
 mode = 700
 context =
   key input_feed_directory directory:notifier-feeds
-- 
2.30.9