Add support for rancher 1.6 API

- New ansible module to handle rancher API
- Setting up of the cattle db and log related options
  to achieve lower space usage.

As of this moment it does what was intended:
- it setups new values for db and log related options
- it can be used for any other setting options

Change-Id: I25048469df0cb035cc6eac39740210cdfa175ada
Issue-ID: OOM-1681
Signed-off-by: Petr Ospalý <p.ospaly@partner.samsung.com>
diff --git a/ansible/library/rancher1_api.py b/ansible/library/rancher1_api.py
new file mode 100644
index 0000000..ce3df1b
--- /dev/null
+++ b/ansible/library/rancher1_api.py
@@ -0,0 +1,242 @@
+#!/usr/bin/python
+
+from ansible.module_utils.basic import AnsibleModule
+
+import requests
+import json
+import functools
+
+DOCUMENTATION = """
+---
+module: rancher1_api
+short_description: Client library for rancher API
+description:
+  - This module modifies a rancher 1.6 using it's API (v1).
+  - WIP, as of now it can only change a current value to a new one.
+
+options:
+  rancher:
+    description:
+      - The domain name or the IP address and the port of the rancher
+        where API is exposed.
+      - For example: http://10.0.0.1:8080
+    required: true
+    aliases:
+      - server
+      - rancher_url
+      - rancher_api
+      - api
+      - url
+  category:
+    description:
+      - The path in JSON API without the last element.
+    required: false
+    default: settings
+    aliases:
+      - rancher_category
+      - api_category
+  option:
+    description:
+      - The name of the settings option.
+    required: true
+    aliases:
+      - name
+      - key
+      - settings
+  value:
+    description:
+      - A new value to replace the current one.
+    required: true
+  timeout:
+    description:
+      - How long in seconds to wait for a response before raising error
+    required: false
+    default: 10.0
+"""
+
+
+def _decorate_rancher_api_request(request_method):
+
+    @functools.wraps(request_method)
+    def wrap_request(*args, **kwargs):
+
+        response = request_method(*args, **kwargs)
+
+        if response.status_code != requests.codes.ok:
+            response.raise_for_status()
+
+        try:
+            json_data = response.json()
+        except Exception:
+            json_data = None
+
+        return json_data
+
+    return wrap_request
+
+
+@_decorate_rancher_api_request
+def get_rancher_api_value(url, headers=None, timeout=10.0,
+                          username=None, password=None):
+
+    if username and password:
+        return requests.get(url, headers=headers,
+                            timeout=timeout,
+                            allow_redirects=False,
+                            auth=(username, password))
+    else:
+        return requests.get(url, headers=headers,
+                            timeout=timeout,
+                            allow_redirects=False)
+
+
+@_decorate_rancher_api_request
+def set_rancher_api_value(url, payload, headers=None, timeout=10.0,
+                          username=None, password=None):
+
+    if username and password:
+        return requests.put(url, headers=headers,
+                            timeout=timeout,
+                            allow_redirects=False,
+                            data=json.dumps(payload),
+                            auth=(username, password))
+    else:
+        return requests.put(url, headers=headers,
+                            timeout=timeout,
+                            allow_redirects=False,
+                            data=json.dumps(payload))
+
+
+def create_rancher_api_payload(json_data, new_value):
+
+    payload = {}
+
+    try:
+        api_id = json_data['id']
+        api_activeValue = json_data['activeValue']
+        api_name = json_data['name']
+        api_source = json_data['source']
+    except Exception:
+        raise ValueError
+
+    payload.update({"activeValue": api_activeValue,
+                    "id": api_id,
+                    "name": api_name,
+                    "source": api_source,
+                    "value": new_value})
+
+    if api_activeValue != new_value:
+        differs = True
+    else:
+        differs = False
+
+    return differs, payload
+
+
+def is_valid_rancher_api_option(json_data):
+
+    try:
+        api_activeValue = json_data['activeValue']
+        api_source = json_data['source']
+    except Exception:
+        return False
+
+    if api_activeValue is None and api_source is None:
+        return False
+
+    return True
+
+
+def main():
+    module = AnsibleModule(
+        argument_spec=dict(
+            rancher=dict(type='str', required=True,
+                         aliases=['server',
+                                  'rancher_api',
+                                  'rancher_url',
+                                  'api',
+                                  'url']),
+            category=dict(type='str', default='settings',
+                          aliases=['rancher_category', 'api_category']),
+            option=dict(type='str', required=True,
+                        aliases=['name', 'key', 'settings']),
+            value=dict(type='str', required=True),
+            timeout=dict(type='float', default=10.0),
+        ),
+        supports_check_mode=True
+    )
+
+    rancher_url = module.params['rancher'].strip('/')
+    rancher_option = module.params['option'].strip('/')
+    rancher_category = module.params['category']
+    rancher_value = module.params['value']
+    rancher_timeout = module.params['timeout']
+    # cattle_access_key = ''
+    # cattle_secret_key = ''
+
+    # Assemble API url
+    request_url = rancher_url + '/v1/' + rancher_category + '/' \
+        + rancher_option
+
+    http_headers = {'Content-Type': 'application/json',
+                    'Accept': 'application/json'}
+
+    # API get current value
+    try:
+        json_response = get_rancher_api_value(request_url,
+                                              headers=http_headers,
+                                              timeout=rancher_timeout)
+    except requests.HTTPError as e:
+        module.fail_json(msg=str(e))
+    except requests.Timeout as e:
+        module.fail_json(msg=str(e))
+
+    if not json_response:
+        module.fail_json(msg='ERROR: BAD RESPONSE (GET) - no json value \
+                         in the response')
+
+    if is_valid_rancher_api_option(json_response):
+        valid = True
+        try:
+            differs, payload = create_rancher_api_payload(json_response,
+                                                          rancher_value)
+        except ValueError:
+            module.fail_json(msg='ERROR: INVALID JSON - missing json values \
+                             in the response')
+    else:
+        valid = False
+
+    if valid and differs and module.check_mode:
+        # ansible dry-run mode
+        changed = True
+    elif valid and differs:
+        # API set new value
+        try:
+            json_response = set_rancher_api_value(request_url,
+                                                  payload,
+                                                  headers=http_headers,
+                                                  timeout=rancher_timeout)
+        except requests.HTTPError as e:
+            module.fail_json(msg=str(e))
+        except requests.Timeout as e:
+            module.fail_json(msg=str(e))
+
+        if not json_response:
+            module.fail_json(msg='ERROR: BAD RESPONSE (PUT) - no json value \
+                             in the response')
+        else:
+            changed = True
+    else:
+        changed = False
+
+    if changed:
+        msg = "Option '%s' is now set to the new value: %s" \
+            % (rancher_option, rancher_value)
+    else:
+        msg = "Option '%s' is unchanged." % (rancher_option)
+
+    module.exit_json(changed=changed, msg=msg)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/ansible/roles/rancher/defaults/main.yml b/ansible/roles/rancher/defaults/main.yml
index 6ab52e6..67e581c 100644
--- a/ansible/roles/rancher/defaults/main.yml
+++ b/ansible/roles/rancher/defaults/main.yml
@@ -4,3 +4,20 @@
 rancher_redeploy_k8s_env: true
 rancher_cluster_health_state: healthy
 rancher_cluster_health_check_retries: 30
+rancher:
+  # The following variables can be set via the UI under advanced/settings.
+  # All of these affect tables in the cattle db and are uninteresting
+  # to the user (they serve the internal logic of the cattle), but
+  # they can eat a lot of space when a deployment is busy or faulty.
+  #
+  # Audit-Log is the only user-facing option here and it is represented
+  # in the UI.
+  #
+  # Auto-purge deleted entries from most tables after this long (seconds)
+  main_tables_purge_after_seconds: 28800  # 8 hours
+  # Auto-purge Event entries after this long (seconds)
+  events_purge_after_seconds: 28800       # 8 hours
+  # Auto-purge Service Log entries after this long (seconds)
+  service_log_purge_after_seconds: 86400  # 1 day
+  # Auto-purge Audit Log entries after this long (seconds)
+  audit_log_purge_after_seconds: 2592000  # 30 days
diff --git a/ansible/roles/rancher/tasks/rancher_server.yml b/ansible/roles/rancher/tasks/rancher_server.yml
index e1eb5a5..add7b3c 100644
--- a/ansible/roles/rancher/tasks/rancher_server.yml
+++ b/ansible/roles/rancher/tasks/rancher_server.yml
@@ -39,3 +39,26 @@
     key_private: "{{ env.data.apikey.private }}"
     rancher_agent_image: "{{ env.data.registration_tokens.image }}"
     rancher_agent_reg_url: "{{ env.data.registration_tokens.reg_url }}"
+
+- name: Configure the size of the rancher cattle db and logs
+  block:
+    - name: Main tables
+      rancher1_api:
+        server: "{{ rancher_server_url }}"
+        option: main_tables.purge.after.seconds
+        value: "{{ rancher.main_tables_purge_after_seconds }}"
+    - name: Events
+      rancher1_api:
+        server: "{{ rancher_server_url }}"
+        option: events.purge.after.seconds
+        value: "{{ rancher.events_purge_after_seconds }}"
+    - name: Service log
+      rancher1_api:
+        server: "{{ rancher_server_url }}"
+        option: service_log.purge.after.seconds
+        value: "{{ rancher.service_log_purge_after_seconds }}"
+    - name: Audit log
+      rancher1_api:
+        server: "{{ rancher_server_url }}"
+        option: audit_log.purge.after.seconds
+        value: "{{ rancher.audit_log_purge_after_seconds }}"