Support time synchronization on hosts

This change introduces functionality to synchronize
infra/kube nodes' clock with external NTP authority.

Configuring external time source is optional, however
default behaviour will be to setup NTP time source on
infra-node and sync kube-nodes clock with it.

It's also possible to setup custom time zone.

Change-Id: I725ce9a306da1977628b6c03d5ff10fca77fb3b0
Issue-ID: OOM-1710
Signed-off-by:  Bartek Grzybowski <b.grzybowski@partner.samsung.com>
diff --git a/ansible/group_vars/all.yml b/ansible/group_vars/all.yml
index 1dc938f..f9d6726 100755
--- a/ansible/group_vars/all.yml
+++ b/ansible/group_vars/all.yml
@@ -147,3 +147,12 @@
 #  openStackDomain: "Default"
 #  openStackUserName: "admin"
 #  openStackEncryptedPassword: "f7920677e15e2678b0f33736189e8965"
+
+# Optional time synchronisation settings
+# timesync:
+#   servers:
+#     - <ip address of NTP_1>
+#     - <...>
+#     - <ip address of NTP_N>
+#   slewclock: false
+#   timezone: <timezone name from tz database>
diff --git a/ansible/infrastructure.yml b/ansible/infrastructure.yml
index 18290ae..74a7b68 100644
--- a/ansible/infrastructure.yml
+++ b/ansible/infrastructure.yml
@@ -8,6 +8,7 @@
 - name: Setup infrastructure servers
   hosts: infrastructure
   roles:
+    - chrony
     - certificates
     - docker
     - dns
@@ -18,6 +19,7 @@
 - name: Setup base for Kubernetes nodes
   hosts: kubernetes
   roles:
+    - chrony
     - docker
   tasks:
     - include_role:
diff --git a/ansible/roles/chrony/defaults/main.yml b/ansible/roles/chrony/defaults/main.yml
new file mode 100644
index 0000000..af433da
--- /dev/null
+++ b/ansible/roles/chrony/defaults/main.yml
@@ -0,0 +1,16 @@
+---
+timesync: {}
+chrony:
+  servers: "{{ timesync.servers | default([hostvars[groups.infrastructure[0]].cluster_ip]) }}" # chronyd's NTP servers
+  slewclock: "{{ timesync.slewclock | default(false) }}" # chronyd's makestep property
+  timezone: "{{ timesync.timezone | default('Universal') }}" # Timezone name according to tz database
+  makestep: '1 -1'
+  maxjitter: 10 # Max allowed jitter if using infra as time source as it may by unstable due to pretending stratum 1 time source
+  initstepslew: 30
+  conf:
+    RedHat:
+      config_file: /etc/chrony.conf
+      driftfile: /var/lib/chrony/drift
+    Debian:
+      config_file: /etc/chrony/chrony.conf
+      driftfile: /var/lib/chrony/chrony.drift
diff --git a/ansible/roles/chrony/handlers/main.yml b/ansible/roles/chrony/handlers/main.yml
new file mode 100644
index 0000000..80ab9fa
--- /dev/null
+++ b/ansible/roles/chrony/handlers/main.yml
@@ -0,0 +1,5 @@
+---
+- name: Restart chronyd
+  systemd:
+    name: chronyd
+    state: restarted
diff --git a/ansible/roles/chrony/tasks/main.yml b/ansible/roles/chrony/tasks/main.yml
new file mode 100644
index 0000000..69a1158
--- /dev/null
+++ b/ansible/roles/chrony/tasks/main.yml
@@ -0,0 +1,26 @@
+---
+- name: Check if server mode
+  set_fact:
+    chrony_mode: 'server'
+  when: "'infrastructure' in group_names and timesync.servers is not defined"
+
+- name: Check if client mode
+  set_fact:
+    chrony_mode: 'client'
+  when: "timesync.servers is defined or 'infrastructure' not in group_names"
+
+- name: "Upload chronyd {{ chrony_mode }} configuration"
+  template:
+    src: "chrony.conf.j2"
+    dest: "{{ chrony['conf'][ansible_os_family]['config_file'] }}"
+  notify: Restart chronyd
+
+- name: Ensure chronyd is enabled/running
+  systemd:
+    name: chronyd
+    state: started
+    enabled: true
+
+- name: Setup timezone
+  timezone:
+    name: "{{ chrony.timezone }}"
diff --git a/ansible/roles/chrony/templates/chrony.conf.j2 b/ansible/roles/chrony/templates/chrony.conf.j2
new file mode 100644
index 0000000..3bfb4e4
--- /dev/null
+++ b/ansible/roles/chrony/templates/chrony.conf.j2
@@ -0,0 +1,22 @@
+{% if chrony_mode == 'server' %}
+local stratum 1
+allow
+{% elif chrony_mode == 'client' %}
+{% for tserver in chrony.servers %}
+server {{ tserver }} iburst
+{% endfor %}
+{% if chrony.slewclock == false %}
+{# Step the time by default  #}
+makestep {{ chrony.makestep }}
+{% else %}
+{# Slew the clock but step at boot time if time error larger than 30 seconds #}
+initstepslew {{ chrony.initstepslew }}{% for tserver in chrony.servers %} {{ tserver }}{% endfor %}
+
+{% endif %}
+{% if timesync.servers is not defined %}
+maxjitter {{ chrony.maxjitter }}
+{% endif %}
+{% endif %}
+driftfile {{ chrony['conf'][ansible_os_family]['driftfile'] }}
+rtcsync
+logdir /var/log/chrony