Add option to set exact backup times

Change-Id: I01202d79750b810d4e24571bbf0ecab11bab7398
diff --git a/README.rst b/README.rst
index f539168..ce7c1d9 100644
--- a/README.rst
+++ b/README.rst
@@ -49,6 +49,46 @@
   .. note:: full_backups_to_keep param states how many backup will be stored locally on cassandra client
 
 
+Backup client at exact times:
+
+..code-block:: yaml
+
+  cassandra:
+    backup:
+      client:
+        enabled: true
+        full_backups_to_keep: 3
+        incr_before_full: 3
+        backup_times:
+          dayOfWeek: 0
+          hour: 4
+          minute: 52
+        compression: true
+        compression_threads: 2
+        database:
+          user: user
+          password: password
+        target:
+          host: host01
+
+  .. note:: Parameters in ``backup_times`` section can be used to set up exact
+  time the cron job should be executed. In this example, the backup job
+  would be executed every Sunday at 4:52 AM. If any of the individual
+  ``backup_times`` parameters is not defined, the defalut ``*`` value will be
+  used. For example, if minute parameter is ``*``, it will run the backup every minute,
+  which is ususally not desired.
+  Available parameters are ``dayOfWeek``, ``dayOfMonth``, ``month``, ``hour`` and ``minute``.
+  Please see the crontab reference for further info on how to set these parameters.
+
+  .. note:: Please be aware that only ``backup_times`` section OR
+  ``hours_before_full(incr)`` can be defined. If both are defined,
+  the ``backup_times`` section will be peferred.
+
+  .. note:: New parameter ``incr_before_full`` needs to be defined. This
+  number sets number of incremental backups to be run, before a full backup
+  is performed.
+
+
 Backup server rsync
 
 .. code-block:: yaml
@@ -72,6 +112,43 @@
       backup:
         restrict_clients: false
 
+Backup server at exact times:
+
+..code-block:: yaml
+
+  cassandra:
+    backup:
+      server:
+        enabled: true
+        full_backups_to_keep: 3
+        incr_before_full: 3
+        backup_dir: /srv/backup
+        backup_times:
+          dayOfWeek: 0
+          hour: 4
+          minute: 52
+        key:
+          cassandra_pub_key:
+            enabled: true
+            key: key
+
+  .. note:: Parameters in ``backup_times`` section can be used to set up exact
+  time the cron job should be executed. In this example, the backup job
+  would be executed every Sunday at 4:52 AM. If any of the individual
+  ``backup_times`` parameters is not defined, the defalut ``*`` value will be
+  used. For example, if minute parameter is ``*``, it will run the backup every minute,
+  which is ususally not desired.
+  Available parameters are ``dayOfWeek``, ``dayOfMonth``, ``month``, ``hour`` and ``minute``.
+  Please see the crontab reference for further info on how to set these parameters.
+
+  .. note:: Please be aware that only ``backup_times`` section OR
+  ``hours_before_full(incr)`` can be defined. If both are defined, The
+  ``backup_times`` section will be peferred.
+
+  .. note:: New parameter ``incr_before_full`` needs to be defined. This
+  number sets number of incremental backups to be run, before a full backup
+  is performed.
+
 Client restore from local backup:
 
 .. code-block:: yaml
diff --git a/cassandra/backup.sls b/cassandra/backup.sls
index 5014073..2301888 100644
--- a/cassandra/backup.sls
+++ b/cassandra/backup.sls
@@ -49,8 +49,24 @@
 {%- if not backup.cron %}
   - commented: True
 {%- endif %}
+{%- if backup.client.backup_times is defined %}
+{%- if backup.client.backup_times.dayOfWeek is defined %}
+  - dayweek: {{ backup.client.backup_times.dayOfWeek }}
+{%- endif -%}
+{%- if backup.client.backup_times.month is defined %}
+  - month: {{ backup.client.backup_times.month }}
+{%- endif %}
+{%- if backup.client.backup_times.dayOfMonth is defined %}
+  - daymonth: {{ backup.client.backup_times.dayOfMonth }}
+{%- endif %}
+{%- if backup.client.backup_times.hour is defined %}
+  - hour: {{ backup.client.backup_times.hour }}
+{%- endif %}
+{%- if backup.client.backup_times.minute is defined %}
+  - minute: {{ backup.client.backup_times.minute }}
+{%- endif %}
+{%- elif backup.client.hours_before_incr is defined %}
   - minute: 0
-{%- if backup.client.hours_before_full is defined %}
 {%- if backup.client.hours_before_full <= 23 and backup.client.hours_before_full > 1 %}
   - hour: '*/{{ backup.client.hours_before_full }}'
 {%- elif not backup.client.hours_before_full <= 1 %}
diff --git a/cassandra/files/backup/cassandra-backup-client-runner-call.sh b/cassandra/files/backup/cassandra-backup-client-runner-call.sh
index ff71928..be3580f 100644
--- a/cassandra/files/backup/cassandra-backup-client-runner-call.sh
+++ b/cassandra/files/backup/cassandra-backup-client-runner-call.sh
@@ -3,6 +3,19 @@
 # Script to call cassandra-backup-runner.sh in for loop to backup all keyspaces.
 # This script is also able to rsync backed up data to remote host and perform clean up on historical backups
 
+SKIPCLEANUP=false
+while getopts "sf" opt; do
+  case $opt in
+    s)
+      echo "Cleanup will be skipped" >&2
+      SKIPCLEANUP=true
+      ;;
+    \?)
+      echo "Invalid option: -$OPTARG" >&2
+      ;;
+  esac
+done
+
 # Configuration
 # -------------
     PROGNAME="getSnapshot"
@@ -25,16 +38,16 @@
     APPENDTIMESTAMP="yes"
     SCRIPTDIR="/usr/local/bin"
     KEEP={{ backup.client.full_backups_to_keep }}
-    HOURSFULLBACKUPLIFE={{ backup.client.hours_before_full }} # Lifetime of the latest full backup in seconds
-    RSYNCLOGDIR="/var/log/backups"
-    RSYNCLOG="/var/log/backups/cassandra-rsync.log"
-
-
+    {%- if backup.client.backup_times is not defined %}
+    HOURSFULLBACKUPLIFE={{ backup.client.hours_before_full }} # Lifetime of the latest full backup in hours
     if [ $HOURSFULLBACKUPLIFE -gt 24 ]; then
         FULLBACKUPLIFE=$(( 24 * 60 * 60 ))
     else
         FULLBACKUPLIFE=$(( $HOURSFULLBACKUPLIFE * 60 * 60 ))
     fi
+    {%- endif %}
+    RSYNCLOGDIR="/var/log/backups"
+    RSYNCLOG="/var/log/backups/cassandra-rsync.log"
 
 # Functions
 # ---------
@@ -103,7 +116,25 @@
 
 # Cleanup
 # ---------
+if [ $SKIPCLEANUP = false ] ; then
+    {%- if backup.client.backup_times is not defined %}
+    echo "----------------------------"
     echo "Cleanup. Keeping only $KEEP full backups"
     AGE=$(($FULLBACKUPLIFE * $KEEP / 60))
     find $BACKUPDIR -maxdepth 1 -type d -mmin +$AGE -execdir echo "removing: "$BACKUPDIR/{} \; -execdir rm -rf $BACKUPDIR/{} \;
-
+    {%- else %}
+    echo "----------------------------"
+    echo "Cleanup. Keeping only $KEEP full backups"
+    NUMBER_OF_FULL=`find $BACKUPDIR -maxdepth 1 -mindepth 1 -type d -print| wc -l`
+    FULL_TO_DELETE=$(( $NUMBER_OF_FULL - $KEEP ))
+    if [ $FULL_TO_DELETE -gt 0 ] ; then
+        cd $BACKUPDIR
+        ls -t | tail -n -$FULL_TO_DELETE | xargs -d '\n' rm -rf
+    else
+        echo "There are less full backups than required, not deleting anything."
+    fi
+    {%- endif %}
+else
+    echo "----------------------------"
+    echo "-s parameter passed. Cleanup was not triggered"
+fi
diff --git a/cassandra/files/backup/cassandra-backup-server-runner.sh b/cassandra/files/backup/cassandra-backup-server-runner.sh
index fa17caf..722674f 100644
--- a/cassandra/files/backup/cassandra-backup-server-runner.sh
+++ b/cassandra/files/backup/cassandra-backup-server-runner.sh
@@ -6,16 +6,31 @@
 
     BACKUPDIR="{{ backup.backup_dir }}/full"
     KEEP={{ backup.server.full_backups_to_keep }}
-    HOURSFULLBACKUPLIFE={{ backup.server.hours_before_full }} # Lifetime of the latest full backup in seconds
-
+    {%- if backup.server.backup_times is not defined %}
+    HOURSFULLBACKUPLIFE={{ backup.server.hours_before_full }} # Lifetime of the latest full backup in hours
     if [ $HOURSFULLBACKUPLIFE -gt 24 ]; then
         FULLBACKUPLIFE=$(( 24 * 60 * 60 ))
     else
         FULLBACKUPLIFE=$(( $HOURSFULLBACKUPLIFE * 60 * 60 ))
     fi
+    {%- endif %}
 
 # Cleanup
 # ---------
-    echo "Cleanup. Keeping only $KEEP full backups"
-    AGE=$(($FULLBACKUPLIFE * $KEEP / 60))
-    find $BACKUPDIR -maxdepth 1 -type d -mmin +$AGE -execdir echo "removing: "$BACKUPDIR/{} \; -execdir rm -rf $BACKUPDIR/{} \;
+{%- if backup.server.backup_times is not defined %}
+echo "----------------------------"
+echo "Cleanup. Keeping only $KEEP full backups"
+AGE=$(($FULLBACKUPLIFE * $KEEP / 60))
+find $BACKUPDIR -maxdepth 1 -type d -mmin +$AGE -execdir echo "removing: "$BACKUPDIR/{} \; -execdir rm -rf $BACKUPDIR/{} \;
+{%- else %}
+echo "----------------------------"
+echo "Cleanup. Keeping only $KEEP full backups"
+NUMBER_OF_FULL=`find $BACKUPDIR -maxdepth 1 -mindepth 1 -type d -print| wc -l`
+FULL_TO_DELETE=$(( $NUMBER_OF_FULL - $KEEP ))
+if [ $FULL_TO_DELETE -gt 0 ] ; then
+    cd $BACKUPDIR
+    ls -t | tail -n -$FULL_TO_DELETE | xargs -d '\n' rm -rf
+else
+    echo "There are less full backups than required, not deleting anything."
+fi
+{%- endif %}
diff --git a/tests/pillar/backup_client_backup_times.sls b/tests/pillar/backup_client_backup_times.sls
new file mode 100644
index 0000000..6f0b43b
--- /dev/null
+++ b/tests/pillar/backup_client_backup_times.sls
@@ -0,0 +1,16 @@
+cassandra:
+  backup:
+    client:
+      enabled: true
+      full_backups_to_keep: 3
+      incr_before_full: 3
+      backup_times:
+        dayOfWeek: 0
+#       month: *
+#       dayOfMonth: *
+        hour: 4
+        minute: 52
+      target:
+        host: cfg01
+      restore_latest: 1
+      restore_from: local
\ No newline at end of file
diff --git a/tests/pillar/backup_server_backup_times.sls b/tests/pillar/backup_server_backup_times.sls
new file mode 100644
index 0000000..5c226b6
--- /dev/null
+++ b/tests/pillar/backup_server_backup_times.sls
@@ -0,0 +1,17 @@
+cassandra:
+  backup:
+    server:
+      enabled: true
+      full_backups_to_keep: 3
+      incr_before_full: 3
+      backup_dir: /srv/backup
+      backup_times:
+        dayOfWeek: 0
+#       month: *
+#       dayOfMonth: *
+        hour: 4
+        minute: 52
+      key:
+        cassandra_pub_key:
+          enabled: true
+          key: key
\ No newline at end of file