Make backup result report function in backup service
diff --git a/staffeln/common/constants.py b/staffeln/common/constants.py
index 6de73f6..d0ec6fe 100644
--- a/staffeln/common/constants.py
+++ b/staffeln/common/constants.py
@@ -1,7 +1,5 @@
-BACKUP_COMPLETED=2

-BACKUP_WIP=1

-BACKUP_PLANNED=0

-

-BACKUP_ENABLED_KEY = 'true'

-

-DEFAULT_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
\ No newline at end of file
+BACKUP_COMPLETED=2
+BACKUP_WIP=1
+BACKUP_PLANNED=0
+
+BACKUP_ENABLED_KEY = 'true'
diff --git a/staffeln/common/notify.py b/staffeln/common/notify.py
index 19f8440..e3aabf2 100644
--- a/staffeln/common/notify.py
+++ b/staffeln/common/notify.py
@@ -4,6 +4,7 @@
 from email.mime.text import MIMEText

 from email.mime.multipart import MIMEMultipart

 import staffeln.conf

+from staffeln.common import time as xtime

 

 CONF = staffeln.conf.CONF

 

@@ -29,18 +30,22 @@
         print(str(e))

         return False

 

-def SendNotification(content, receiver=None):

+def SendBackupResultNotification(success_volume_list, failed_volume_list):

     subject = "Backup result"

 

-    html = "<h3>${CONTENT}</h3>"

-    html = html.replace("${CONTENT}", content)

+    html = "<h3>${TIME}</h3>" \

+           "<h3>Success List</h3>" \

+           "<h4>${SUCCESS_VOLUME_LIST}</h4>" \

+           "<h3>Failed List</h3>" \

+           "<h4>${FAILED_VOLUME_LIST}</h4>"

 

-    if receiver == None:

-        return

-    if len(receiver) == 0:

-        return

+    success_volumes = '<br>'.join([str(elem) for elem in success_volume_list])

+    failed_volumes = '<br>'.join([str(elem) for elem in failed_volume_list])

+    html = html.replace("${TIME}", xtime.get_current_strtime())

+    html = html.replace("${SUCCESS_VOLUME_LIST}", success_volumes)

+    html = html.replace("${FAILED_VOLUME_LIST}", failed_volumes)

 

-    res = sendEmail(src_email=CONF.notification.sender_email,

+    return sendEmail(src_email=CONF.notification.sender_email,

                         src_pwd=CONF.notification.sender_pwd,

                         dest_email=CONF.notification.receiver,

                         subject=subject,

diff --git a/staffeln/common/time.py b/staffeln/common/time.py
index 6ebdee7..0af1cb4 100644
--- a/staffeln/common/time.py
+++ b/staffeln/common/time.py
@@ -1,48 +1,55 @@
-import re

-from datetime import datetime

-from dateutil.relativedelta import relativedelta

-

-regex = re.compile(

-    r'((?P<years>\d+?)y)?((?P<months>\d+?)m)?((?P<weeks>\d+?)w)?((?P<days>\d+?)d)?'

-)

-

-

-# parse_time parses timedelta string to time dict

-# input: <string> 1y2m3w5d - all values should be integer

-# output: <dict> {year: 1, month: 2, week: 3, day: 5}

-def parse_timedelta_string(time_str):

-    empty_flag = True

-    try:

-        parts = regex.match(time_str)

-        if not parts:

-            return None

-        parts = parts.groupdict()

-        time_params = {}

-        for key in parts:

-            if parts[key]:

-                time_params[key] = int(parts[key])

-                empty_flag = False

-            else:

-                time_params[key] = 0

-        if empty_flag: return None

-        return time_params

-    except:

-        return None

-

-

-def timeago(years, months, weeks, days, from_date=None):

-    if from_date is None:

-        from_date = datetime.now()

-    return from_date - relativedelta(years=years, months=months, weeks=weeks, days=days)

-

-## yearsago using Standard library

-# def yearsago(years, from_date=None):

-#     if from_date is None:

-#         from_date = datetime.now()

-#     try:

-#         return from_date.replace(year=from_date.year - years)

-#     except ValueError:

-#         # Must be 2/29!

-#         assert from_date.month == 2 and from_date.day == 29 # can be removed

-#         return from_date.replace(month=2, day=28,

-#                                  year=from_date.year-years)

+import re
+from datetime import datetime
+from dateutil.relativedelta import relativedelta
+
+DEFAULT_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
+
+regex = re.compile(
+    r'((?P<years>\d+?)y)?((?P<months>\d+?)m)?((?P<weeks>\d+?)w)?((?P<days>\d+?)d)?'
+)
+
+
+# parse_time parses timedelta string to time dict
+# input: <string> 1y2m3w5d - all values should be integer
+# output: <dict> {year: 1, month: 2, week: 3, day: 5}
+def parse_timedelta_string(time_str):
+    empty_flag = True
+    try:
+        parts = regex.match(time_str)
+        if not parts:
+            return None
+        parts = parts.groupdict()
+        time_params = {}
+        for key in parts:
+            if parts[key]:
+                time_params[key] = int(parts[key])
+                empty_flag = False
+            else:
+                time_params[key] = 0
+        if empty_flag: return None
+        return time_params
+    except:
+        return None
+
+
+def get_current_strtime():
+    now = datetime.datetime.now()
+    return now.strftime(DEFAULT_TIME_FORMAT)
+
+
+def timeago(years, months, weeks, days, from_date=None):
+    if from_date is None:
+        from_date = datetime.now()
+    return from_date - relativedelta(years=years, months=months, weeks=weeks, days=days)
+
+## yearsago using Standard library
+# def yearsago(years, from_date=None):
+#     if from_date is None:
+#         from_date = datetime.now()
+#     try:
+#         return from_date.replace(year=from_date.year - years)
+#     except ValueError:
+#         # Must be 2/29!
+#         assert from_date.month == 2 and from_date.day == 29 # can be removed
+#         return from_date.replace(month=2, day=28,
+#                                  year=from_date.year-years)
diff --git a/staffeln/conductor/manager.py b/staffeln/conductor/manager.py
index 3daf47e..5997828 100755
--- a/staffeln/conductor/manager.py
+++ b/staffeln/conductor/manager.py
@@ -9,6 +9,7 @@
 from staffeln.common import constants

 from staffeln.conductor import backup

 from staffeln.common import context

+from staffeln.common import notify

 from staffeln.common import time as xtime

 from staffeln.i18n import _

 

@@ -58,6 +59,9 @@
         return False

 

     # Manage active backup generators

+    # TODO(Alex): need to discuss

+    #  Need to wait until all backups are finished?

+    #  That is required to make the backup report

     def _process_wip_tasks(self):

         LOG.info(_("Processing WIP backup generators..."))

         queues_started = backup.Backup().get_queues(

@@ -82,17 +86,25 @@
         current_tasks = backup.Backup().get_queues()

         backup.Backup().create_queue(current_tasks)

 

+    def _report_backup_result(self):

+        self.success_backup_list = []

+        self.failed_backup_list = []

+        notify.SendNotification(self.success_backup_list, self.failed_backup_list)

+

     @periodics.periodic(spacing=CONF.conductor.backup_service_period, run_immediately=True)

     def backup_engine(self):

         LOG.info("backing... %s" % str(time.time()))

         LOG.info("%s periodics" % self.name)

 

         if self._check_quota(): return

+        # NOTE(Alex): If _process_wip_tasks() waits tiil no WIP tasks

+        # exist, no need to repeat this function before and after queue update.

         self._process_wip_tasks()

-        self._process_todo_tasks()

         self._update_task_queue()

-        self._process_wip_tasks()

         self._process_todo_tasks()

+        self._process_wip_tasks()

+        self._report_backup_result()

+

 

 

 class RotationManager(cotyledon.Service):

@@ -157,4 +169,4 @@
         if res == None: LOG.info(_("Retention time format is invalid. "

                                    "Follow <YEARS>y<MONTHS>m<WEEKS>w<DAYS>d."))

 

-        return res.strftime(constants.DEFAULT_TIME_FORMAT)

+        return res.strftime(xtime.DEFAULT_TIME_FORMAT)

diff --git a/staffeln/conf/notify.py b/staffeln/conf/notify.py
index 42daefe..c292e51 100644
--- a/staffeln/conf/notify.py
+++ b/staffeln/conf/notify.py
@@ -9,11 +9,6 @@
 )
 
 email_opts = [
-    cfg.StrOpt(
-        "template",
-        default="<h3>\${CONTENT}</h3>",
-        help=_("This html template is used to email the backup result."),
-    ),
     cfg.ListOpt(
         "receiver",
         default=[],