aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorParth Yadav <parth.yadav@ramanujan.du.ac.in>2020-08-07 18:16:54 +0530
committerParth Yadav <parth.yadav@ramanujan.du.ac.in>2020-08-17 19:11:14 +0530
commit69f4c631e49c359338cff5c9f5b2c96c6fe6b280 (patch)
treed7fc7e207aca6ae5b9899d1f1eba27fe29c4c273
parent42af6fa2ef682d28d12952831aa0c74fd647daad (diff)
Init SDV-State
This patch adds post cloud-software deployment state validation tool. Currently supports pod_health_check for Airship deployment. Signed-off-by: Parth Yadav<parthyadav3105@gmail.com> Change-Id: I28eeff520f7a00419620bf50cc38fd4793aa31b8
-rw-r--r--pylintrc2
-rw-r--r--sdv/docker/sdvstate/.dockerignore23
-rw-r--r--sdv/docker/sdvstate/Dockerfile25
-rw-r--r--sdv/docker/sdvstate/core/__init__.py21
-rw-r--r--sdv/docker/sdvstate/core/load_pdf.py40
-rw-r--r--sdv/docker/sdvstate/example/kubepod1020
-rw-r--r--sdv/docker/sdvstate/example/kubepod1520
-rw-r--r--sdv/docker/sdvstate/example/state.yml17
-rw-r--r--sdv/docker/sdvstate/requirements.txt4
-rwxr-xr-xsdv/docker/sdvstate/server162
-rw-r--r--sdv/docker/sdvstate/settings/common.yml27
-rw-r--r--sdv/docker/sdvstate/settings/installer.yml29
-rw-r--r--sdv/docker/sdvstate/settings/result.yml19
-rwxr-xr-xsdv/docker/sdvstate/state237
-rw-r--r--sdv/docker/sdvstate/tools/conf/__init__.py120
-rw-r--r--sdv/docker/sdvstate/tools/kube_utils.py85
-rw-r--r--sdv/docker/sdvstate/tools/result_api/__init__.py24
-rw-r--r--sdv/docker/sdvstate/tools/result_api/result_api.py73
-rw-r--r--sdv/docker/sdvstate/tools/result_api/rfile.py52
-rw-r--r--sdv/docker/sdvstate/tools/result_api/storage/__init__.py18
-rw-r--r--sdv/docker/sdvstate/tools/result_api/storage/local/__init__.py17
-rw-r--r--sdv/docker/sdvstate/tools/result_api/storage/local/local.py133
-rw-r--r--sdv/docker/sdvstate/tools/result_api/storage/storage_api.py46
-rw-r--r--sdv/docker/sdvstate/validator/__init__.py21
-rw-r--r--sdv/docker/sdvstate/validator/airship/airship.py51
-rw-r--r--sdv/docker/sdvstate/validator/airship/pod_health_check.py94
-rw-r--r--sdv/docker/sdvstate/validator/validator.py27
-rw-r--r--sdv/docs/docker/state/development/design/index.rst0
-rw-r--r--sdv/docs/docker/state/development/overview/index.rst0
-rw-r--r--sdv/docs/docker/state/development/overview/result-api-architecture.pngbin0 -> 24756 bytes
-rw-r--r--sdv/docs/docker/state/development/overview/result_api.rst178
-rw-r--r--sdv/docs/docker/state/development/overview/settings.rst38
-rw-r--r--sdv/docs/docker/state/release/configguide/index.rst0
-rw-r--r--sdv/docs/docker/state/release/userguide/index.rst0
34 files changed, 1623 insertions, 0 deletions
diff --git a/pylintrc b/pylintrc
index 4900d67..513ba1d 100644
--- a/pylintrc
+++ b/pylintrc
@@ -234,6 +234,8 @@ expected-line-ending-format=
# Logging modules to check that the string format arguments are in logging
# function parameter format
logging-modules=logging
+logging-format-style=fstr
+
[MISCELLANEOUS]
diff --git a/sdv/docker/sdvstate/.dockerignore b/sdv/docker/sdvstate/.dockerignore
new file mode 100644
index 0000000..1ed3459
--- /dev/null
+++ b/sdv/docker/sdvstate/.dockerignore
@@ -0,0 +1,23 @@
+# Byte-compiled / optimized / DLL files
+**/__pycache__/
+**/*.py[cod]
+**/*.confc
+
+# C extensions
+**/*.so
+
+
+# Installer logs
+**/pip-log.txt
+**/pip-delete-this-directory.txt
+
+
+# Log files:
+**/*.log
+
+# SublimeText
+**/*.sublime-project
+**/*.sublime-workspace
+
+**/*.md
+**/*.png
diff --git a/sdv/docker/sdvstate/Dockerfile b/sdv/docker/sdvstate/Dockerfile
new file mode 100644
index 0000000..7d18e7a
--- /dev/null
+++ b/sdv/docker/sdvstate/Dockerfile
@@ -0,0 +1,25 @@
+FROM python:3.8-slim-buster
+
+MAINTAINER Parth Yadav <parthyadav3105@gmail.com>
+
+WORKDIR /state/
+
+COPY requirements.txt /state/requirements.txt
+RUN pip install -r requirements.txt
+RUN rm requirements.txt
+
+COPY core/ /state/core/
+COPY settings/ /state/settings/
+COPY tools/ /state/tools/
+COPY validator/ /state/validator/
+COPY state /state/
+COPY server /state/
+
+ENV RESULTS_PATH /tmp/
+ENV RESULTS_FILENAME results.json
+ENV LOG_VERBOSITY info
+ENV SAVE_RESULTS_LOCALLY True
+
+WORKDIR /data/
+
+CMD [ "python", "/state/server" ]
diff --git a/sdv/docker/sdvstate/core/__init__.py b/sdv/docker/sdvstate/core/__init__.py
new file mode 100644
index 0000000..ed33752
--- /dev/null
+++ b/sdv/docker/sdvstate/core/__init__.py
@@ -0,0 +1,21 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Core package
+contains all program specific dependencies
+"""
+
+from .load_pdf import load_pdf
diff --git a/sdv/docker/sdvstate/core/load_pdf.py b/sdv/docker/sdvstate/core/load_pdf.py
new file mode 100644
index 0000000..6ce22f0
--- /dev/null
+++ b/sdv/docker/sdvstate/core/load_pdf.py
@@ -0,0 +1,40 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""Loads PDF file into settings
+"""
+
+import json
+import yaml
+
+from tools.conf import settings
+
+def load_pdf():
+ """
+ Updates settings with PDF data
+ """
+ filename = settings.getValue('pdf_file')
+ with open(filename) as handle:
+ data = handle.read()
+
+ try:
+ pdf = json.loads(data)
+ except json.decoder.JSONDecodeError:
+ try:
+ pdf = yaml.safe_load(data)
+ except yaml.parser.ParserError:
+ raise Exception(f"Invalid PDF file: {filename}")
+
+ settings.setValue('pdf_file', pdf)
diff --git a/sdv/docker/sdvstate/example/kubepod10 b/sdv/docker/sdvstate/example/kubepod10
new file mode 100644
index 0000000..2717fc6
--- /dev/null
+++ b/sdv/docker/sdvstate/example/kubepod10
@@ -0,0 +1,20 @@
+---
+apiVersion: v1
+clusters:
+- cluster:
+ server: https://10.10.100.21:6553
+ insecure-skip-tls-verify: true
+ name: kubernetes
+contexts:
+- context:
+ cluster: kubernetes
+ user: admin
+ name: admin@kubernetes
+current-context: admin@kubernetes
+kind: Config
+preferences: {}
+users:
+- name: admin
+ user:
+ client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURZRENDQWtpZ0F3SUJBZ0lVQmN1akh5bmUzMFBnMUw5MnNJZERmWEtlVm5Vd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0tqRVRNQkVHQTFVRUNoTUtTM1ZpWlhKdVpYUmxjekVUTUJFR0ExVUVBeE1LYTNWaVpYSnVaWFJsY3pBZQpGdzB4T1RFd01UY3hOakUzTURCYUZ3MHlNREV3TVRZeE5qRTNNREJhTUNreEZ6QVZCZ05WQkFvVERuTjVjM1JsCmJUcHRZWE4wWlhKek1RNHdEQVlEVlFRREV3VmhaRzFwYmpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVAKQURDQ0FRb0NnZ0VCQUxRVnpUeG1UTUZHRkdiWks1OGwyUXZkT2hUZVZ2dzVKTWJyVE8wY2hhd1BtdmJXeXczSApMeUFpNTJsZkU5VGdONXBBVzVrVzJmS2tkREMwRnNXZXF2VDV4SFVvbVFGa3RRM2RWMEJnMXRXYVNIdnVHMXQwCndac2hIQWN6RTl0ZS93dFR6ajhkdFl0ZXdIbXpzd1J1bk9sRnFaUVZZT1hReENPYkEvZ2Z1V0o5RUFKNlduZDcKcUhZdEJvbzR0RkhVTmFocDRwUXNNS1VlbDZPUnA4NEM0WnNIenYyZm9Jb2pYd1V2TmJMNUE1VlZjallrK0taZwpCc3IyMWowT0c4N1F3Q0ZuOThMelJqUU92L01FTFRPOEoxemIrK3pvbkg0ZkpDckc1Q2RKNUFQbU81UnBEMGluCmJKNnFOR2QyY0kxaGdVWWx2aWI4QURXc21VelRkWU5wa0JrQ0F3RUFBYU4vTUgwd0RnWURWUjBQQVFIL0JBUUQKQWdXZ01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFNQmdOVkhSTUJBZjhFQWpBQQpNQjBHQTFVZERnUVdCQlRKSXRERnJwcGR3RExOTWFWY2IzQ1JiNVhBZURBZkJnTlZIU01FR0RBV2dCUjcwU1Z4Ck8wVlpzNXVRQkpaOEZ0cmluT25EaERBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQUFBRUQrQXNaaEFsWnRucTkKR0w5a1dvWDd5d0ZmR2FRdEgvSnFaT0VGYkx0bFdLVmlHc3gyVWlSRTN2U3l6VlFpMVBhNGR3cXF1MXc4bVNIVworc1REVlN1aGE5Q2NlbzcvT3F4dnl3ME43c0t2L0NPeml6YWF5djlXTmlBYXhFNjRMSk1sTWlrS2wrZG1zSlVMCktVUXJLVzhvcnlhZk4zSzZnd3NLS2FtU2Mzb1J4TG9wWHVveEo5a2lyVG5DOWpMVGdWSU1EM0I5aEtleEtLQ3YKb1hKVkUyMWViVnNiOExiSUcyaldRcWlnVktxWEFRN3gwcEt6RFcvN1dIc1JyRFRkbFpYU0ZUZS9IQUpZd2tuVwp1cmd2blJkZ1BYUHl6cHJhWU9iTCtTV3dvejRTS216OGV5TWpQcFd0TkFZQTdIYm5XT3RqU2NXNFJKWnpaQ1V3CldicStNZz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
+ client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBdEJYTlBHWk13VVlVWnRrcm55WFpDOTA2Rk41Vy9Ea2t4dXRNN1J5RnJBK2E5dGJMCkRjY3ZJQ0xuYVY4VDFPQTNta0JibVJiWjhxUjBNTFFXeFo2cTlQbkVkU2laQVdTMURkMVhRR0RXMVpwSWUrNGIKVzNUQm15RWNCek1UMjE3L0MxUE9QeDIxaTE3QWViT3pCRzZjNlVXcGxCVmc1ZERFSTVzRCtCKzVZbjBRQW5wYQpkM3VvZGkwR2lqaTBVZFExcUduaWxDd3dwUjZYbzVHbnpnTGhtd2ZPL1orZ2lpTmZCUzgxc3ZrRGxWVnlOaVQ0CnBtQUd5dmJXUFE0Ynp0REFJV2Yzd3ZOR05BNi84d1F0TTd3blhOdjc3T2ljZmg4a0tzYmtKMG5rQStZN2xHa1AKU0tkc25xbzBaM1p3aldHQlJpVytKdndBTmF5WlROTjFnMm1RR1FJREFRQUJBb0lCQUYxcnlmS0JaMlFFUk9VdAoyQkZSZ3cxQ2tMVHV1dFZSbDZDUnhIQmxFWHMzQlQ3MElwWmRORGRKcEI3bnNkUUhGUkV5UGRKbkpsMVhydWJ0Ckpic1RHc0RIS1lGVnREb2kwa0lGQnhSZ3FGSmJIU3NkVkpmWE0vQ1Q5b1JOblFsNmVIaVoyeTZtN04wR0pIZCsKSDJvM0w3TmI3aUxpREVoc1NyUGw0T05CSWR6VEFQYy91b3hQbVQxQ2ZiQ3hVU051d1EzOS9mWHJVNzJTOFU4ZAoybXd2dDZpczQ2c09IWkNkNG0xNGJENE11Y2VsUG83V2ptT0hRZlUzd0g1NTE5Q0FtV0hDRFA0ZndNY3dYWlJUClZWUHcyU1VZRW9lMS9DM083cVVqMlRTMldJdysveHZOQml6WFpqajZTdmd5ME1FREtzamhsbWM2OE81MVAvajgKcmh3dFp1RUNnWUVBeTE1c2NuRFVidWFob1dJNzlCVzAxdWlyY0V1Vnp2TXhmc3ptbEJhZnp3dDZPL0FNQ3l1NwpKS2ZNR1JFQmxXR1RGMUhiUlREbGZzK3lTNFpjKzRQUDlmZVNVNFI1NWJkczlZRU44K3liSjJvbzVPcFlGOVFkCmtoL2JQRUZkN3pTbVQ0R2l2V2lxNklqVlFrTGNOVWFlczN4WlZ2d2NqUXd5cENtTU1aRlJtN1VDZ1lFQTRyREsKSTZITUdDcTR4eWZuaUhhT0ZkNlVEYjdmS3ZJNTJZYnU5R0IyR0JXdmkrcEhOZ1V1dU44SG9GT0hQS2NuYnkweQoyS0FwRjVaYTFSZUNHNGdSTE8vMjMybU42VnVCMERGWlNnUEFITTJKd1BtOFUrYjlWZFZaZEI1ZWJrTDhxNXlkCmZqM3F3S2NRVTZMR09wVy83ZEhsMUVUWE9kMUYzTi8wNFdzcGlWVUNnWUJtb0UrNXNKYURJSCtRSVRLQUNqUW4KLzJJRVdTQlFQd2xMSTd0NEg3S2xtUFVtS2d6cDFqZXFWOEwzSTAzWlJGUW1BSGpXZ2NaT0tDR2hXenl3NytPUwpERTBiT0U4TFRYVCtyeEdMZG1zVmlNejZPQWdjZmo0dDcwV0RNcmxrYlAxQVFmc04ralBGQk1nWm1BUG9IcXNYCmlEak5YSXhMNFV2czY4cURlUUhsd1FLQmdGTGs3UFg4cTJKRzlReTJsZDc3NDFjeDdoZmNyVVRLRU1kdnBSK3QKeW1GaVJMQTRPbFFScnhVaFVXdWFQOEM1S3gxbmZNbGtQOEtGVTYvS2llUkJiRzV2VFdwQzhnYmNWR3JxTU1sMAo5NkpRc3NmalNxK3Zyd0hkSTNubnhRWXk3cXhlZCtUN0JVWHZrWFBUK1FMaFVhN0lhMitrd01OREc5SDUvMVVTCjE3eUZBb0dBZjBubW53RjJRMTZVYXhhanI2M3hjUFlQS09FY1pHTFcxalhoMHVpNFJnK3lscEdSZ25xdVJzMk8KL3RDYTlUYm1JcG9KZHA3aWFPOTIzenI1MWphcnlBOCtuWWhoZ2dRQ29IdWNIY0ZBR213Ryt6R2NyMlBZYklseAo5TkVsUEFZM2pndFNWTW4yUkhMak0wUWVuTUQ1aG1HcHQvWVJOd3hPNkNBdXhaNUhzOTQ9Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg==
diff --git a/sdv/docker/sdvstate/example/kubepod15 b/sdv/docker/sdvstate/example/kubepod15
new file mode 100644
index 0000000..7710cbc
--- /dev/null
+++ b/sdv/docker/sdvstate/example/kubepod15
@@ -0,0 +1,20 @@
+---
+apiVersion: v1
+clusters:
+- cluster:
+ server: https://10.10.150.21:6553
+ insecure-skip-tls-verify: true
+ name: kubernetes
+contexts:
+- context:
+ cluster: kubernetes
+ user: admin
+ name: admin@kubernetes
+current-context: admin@kubernetes
+kind: Config
+preferences: {}
+users:
+- name: admin
+ user:
+ client-certificate-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURZRENDQWtpZ0F3SUJBZ0lVRFZ1T2IvczEyKzR1dGNTRnBuOEhQbFlIVWFBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd0tqRVRNQkVHQTFVRUNoTUtTM1ZpWlhKdVpYUmxjekVUTUJFR0ExVUVBeE1LYTNWaVpYSnVaWFJsY3pBZQpGdzB4T1RFeU1UVXdNelExTURCYUZ3MHlNREV5TVRRd016UTFNREJhTUNreEZ6QVZCZ05WQkFvVERuTjVjM1JsCmJUcHRZWE4wWlhKek1RNHdEQVlEVlFRREV3VmhaRzFwYmpDQ0FTSXdEUVlKS29aSWh2Y05BUUVCQlFBRGdnRVAKQURDQ0FRb0NnZ0VCQUo0Z3R2ZUMyVktnV1BwRHRMTWE3d2k0aE1hZlBEZUVveUg2LzBrWUdKWEF6TTRuVE55NApZdXVldCtBZUdDNnJ6cHNDRG1GcVBrVkRRM1ZkMEsrd05VSXZmOGpZVXlMbWVXUEZxNStqV25SaHpSbUVyT2VBCk9UK0lMa0pFMUN3T2hPbEtjMlB0TjhPUzdFbVR1NmxkZHQ4OXM5Z1M5aXNmbm5JQmY2YkhNdGdqWWJrZEEzbEQKb0VLL282VS9LdkpydTN2L01IRXl6VUwwbjB4UHpHK3ZPVDRpRVZRV3A3M3o0d2gzalN4SENvQmJ4RU9hTk5mSgpoQjNFMUZhSTZMY3U4VHdWdnZ3WStHc3Z3NURXblJ5VXczL0REUXpNMGNQZkc2WUNmeWhjQkVJSUJ5ZEtwUTdYCi9NZ0p4MWV1QmRHdVFheHNaNHhvS0taZW4vQWhCbWZDTUVjQ0F3RUFBYU4vTUgwd0RnWURWUjBQQVFIL0JBUUQKQWdXZ01CMEdBMVVkSlFRV01CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRY0RBakFNQmdOVkhSTUJBZjhFQWpBQQpNQjBHQTFVZERnUVdCQlJMYUVVWXRRaGxMQnFCQUtJdTRrUDRwWWhRTlRBZkJnTlZIU01FR0RBV2dCU2g3bE54CmJXZ1pUSjZKRkUwdHhTdGdIS3hqd3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQXBpWXFVaUMyNFVLM2YxUW8KMnp1YTlTKzVaeWZXOTgvcG9zWDVFZ0x2c25uYmJoaXRFeXptc1RQaG1yNTZ2WmNkVlVjb3B4NFc2M0luaDRUYQpHQlBUMjVwdGZIVEE1bTNURDIrb1dFQXlKMHhBbjR3M0VpdzRhYmY0aCs1Q0JlTm9ldXJlOXhMYlIzNnZZSG9aCnQ0aVk1Q3BraHhud3VLV0FZTnE4a2lsQTlvUzV2bm5ndUMxYVJEckQ5bTJLZlk1aWtiRndGWWUzRzRLTXAyaUgKWVpiMUxhZ3BlZHRjbTJSNnhNZ0RVSktKbkN5WFpIcXp1WHMzT1h1TTFRVzZlMVl2VU1aQUdMV25NYkJ2S3MzNQpyMUdsdFY5OUh0WHBoTnBqeFd6a1RNS0s2K0wwQ0xxNXducVZjVzNUK1Y1V05HbkhWMThBMkhEM0NUc3NRWmxBCm5pbGVXdz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
+ client-key-data: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFb2dJQkFBS0NBUUVBbmlDMjk0TFpVcUJZK2tPMHN4cnZDTGlFeHA4OE40U2pJZnIvU1JnWWxjRE16aWRNCjNMaGk2NTYzNEI0WUxxdk9td0lPWVdvK1JVTkRkVjNRcjdBMVFpOS95TmhUSXVaNVk4V3JuNk5hZEdITkdZU3MKNTRBNVA0Z3VRa1RVTEE2RTZVcHpZKzAzdzVMc1NaTzdxVjEyM3oyejJCTDJLeCtlY2dGL3BzY3kyQ05odVIwRAplVU9nUXIranBUOHE4bXU3ZS84d2NUTE5RdlNmVEUvTWI2ODVQaUlSVkJhbnZmUGpDSGVOTEVjS2dGdkVRNW8wCjE4bUVIY1RVVm9qb3R5N3hQQlcrL0JqNGF5L0RrTmFkSEpURGY4TU5ETXpSdzk4YnBnSi9LRndFUWdnSEowcWwKRHRmOHlBbkhWNjRGMGE1QnJHeG5qR2dvcGw2ZjhDRUdaOEl3UndJREFRQUJBb0lCQUJ5cFkyQ3p0LzZSRCsrMAo3QUQyNWRMWDEwRkZSWjN1amI4d0JxdlNFVXE3bXFQWFhjZzRKNzM3aytxc3FjZHozc3diOEUxWis1V0VYcXJjCmFXSWU5MWhhMGJldTlrckNLY2lhNE1QYjBSNTlSN2JUWkovRmp4cmo3VGFYMFRsM0hFSkkrMmRtYlJBbkJtdEQKdXVVMUNzSG1KajRKR2RPeE5JQUhvNEt3WXBmb2NPME9acFhVZFlOQktwSUhseFhOWjJ4RkJiVzh6a1FRekZ6MAplWmQ3YzZNUmlaZFRPd0pqNWl4c0FWSytBTXVGRkFSamNmc1FBZktlS2J2YUdDTFBvQmFSak5US0Z6MEVhWmlZClNTM2NYMDRCTnk0NWNPVWlHK3RsSlgxeEhGT04weittMlArZWQ1dCtHSHB3UUg4ak1ZQzhkZlJCUVdSeGNCTjcKemd3NWp5RUNnWUVBeEM4REVPWm9QNXhOcFZwbXB6OVAvY2NiVE9XekdUTlZpZzFhcmdSMC90RWRRK0lKQlpDdgpqMzlWVk9FeDUrSlJHVmhmSG9NczlXZVZuSmJSZTFyN1QrWU1WOVFCOVYvbDVhdkJoc0Vhc1NpaThsVEpzT3dXCmJRTDQyMDd1QVhGemFoMHhPS253T2gwN3p0TTBFMXQwWFVrR1BFSzE2bldPbFc4K3IyOEEvSmNDZ1lFQXpsZEgKSlo5RTUxT0dmZU96Y2VQdWV0ZU9PYnNvWVU4eHhOaUg0MitKWVpKNFVqNVY2RGw4OHdEaS91NVNVOVBtUWM1dAozOFpncXdRRjRFWklTbk0rbElKZnluUmhmWU9YZXB4bEJnVFBVb2dUeGY1bm5jZjNOWGZrVVJxUWViZURqTEdjClBrbU1LbE9kK21jRnYxdGI4eXhzbVEwbjhFVWplVkdLT3JKVjc5RUNnWUJ1NC9QanRaanZlN1lYNVFWcE84eEgKTWlnb1N4MzAvS001S1ZzOFNhQ24rQ09HbjFsaUgrcGNQaWxKbFJEVWRZUkp3ejNnelZ5NFNoaXpManl5Y1RiawpickJEWkw3R3A3SVhKQUo3M09MdGlINnlZMkt0OG9TcWthZUFyeGl4RUNPZ3MyZURFK3VKcmNTRW43VXJ5K0gyCmFMUnhrM09vVjFLRS9TQjlvVXo1ZVFLQmdFZ1g4b0hRbmhCOC9IYXJ3aHkrMktvTyttQnRaZlJwNlNldngvck4KRTZFRnZnaHVRekc2TkUvck5XU0EvRDdSd0plcGVuWS9KN05ZMm55NzBiSkJoZEg1bzJKbk8xRFJVM0hCaHdLTgpWNnFzWk13KzBSRXR0cy8xcmM0d2k5NGJJbGxjRFEwdVFVemdua2ZKQ3hjSzRwdWFIKzl4eTB5RnU1azl4aUF3CkF4cWhBb0dBUlc3Qno1UjlSOWZKVUp3ZEhvMGpRVmhrL3lzSWlmQWRxQ3d6blpOcVM4cU9KMXpsSExhWkozMXcKbVdjNzA3UUN6Q3BOMk1YV2lnMzc2VVJpdXFtcEJTZW14bzFRendhQWJhK0Yvd1I1VzlncndzTmZ2RDR6TkVHbgp2dFllSS9taXlJOVFaay9PVkcrblRLL1ZIZExha3FOVFNKQUl6WSttZ2Y4SWphUTUrVW89Ci0tLS0tRU5EIFJTQSBQUklWQVRFIEtFWS0tLS0tCg== \ No newline at end of file
diff --git a/sdv/docker/sdvstate/example/state.yml b/sdv/docker/sdvstate/example/state.yml
new file mode 100644
index 0000000..1ca61e1
--- /dev/null
+++ b/sdv/docker/sdvstate/example/state.yml
@@ -0,0 +1,17 @@
+# This is a comment
+
+# values are stored in key:value format
+## keys are case-insensitive
+## values can be int, float, string, dict, list, bool
+
+
+## Path to PDF file
+PDF_FILE: example/intel-pod10.json
+
+#############
+# Airship arguments
+#############
+
+# Path to kube-config file
+KUBE_CONFIG : example/kubepod10
+
diff --git a/sdv/docker/sdvstate/requirements.txt b/sdv/docker/sdvstate/requirements.txt
new file mode 100644
index 0000000..415b2ae
--- /dev/null
+++ b/sdv/docker/sdvstate/requirements.txt
@@ -0,0 +1,4 @@
+PyYAML == 5.3.1
+pylint == 2.4.4
+tornado == 6.0.4
+kubernetes == 11.0.0
diff --git a/sdv/docker/sdvstate/server b/sdv/docker/sdvstate/server
new file mode 100755
index 0000000..ca37eca
--- /dev/null
+++ b/sdv/docker/sdvstate/server
@@ -0,0 +1,162 @@
+#!/usr/bin/env python3
+
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Server
+"""
+
+
+import logging
+import subprocess
+
+from tornado.web import Application
+from tornado.ioloop import IOLoop
+import tornado.concurrent
+import tornado.httpserver
+import tornado.ioloop
+import tornado.options
+import tornado.web
+import tornado.log
+
+
+# pylint: disable=W0223
+
+class SwPostStateValidator(tornado.web.RequestHandler):
+ """
+ Post Cloud Software Deployment State Validation Tool REST Request Handler
+ """
+
+ # def set_default_headers(self):
+ # """
+ # """
+ # self.set_header('Content-Type', 'application/json')
+
+
+ def get(self):
+ """
+ get request
+
+ usage:
+ option 1: passing parameters as key=value list
+ /?params='KEY1=value1,KEY2=value2'
+
+ option 2: passing parameters in conf-file
+ /?config=filename
+
+ :return: logs from test results
+ """
+ conf_params = self.get_argument('params', None)
+ if conf_params is not None:
+ conf_params = conf_params.replace(',', ';')
+ conf_file = self.get_argument('config', None)
+ pdf_file = self.get_argument('pdf', None)
+
+ cmd = ['python3', '/state/state']
+
+ if conf_params is not None:
+ cmd.extend(['--conf-params', f'{conf_params}'])
+
+ if conf_file is not None:
+ cmd.extend(['--conf-file', f'/data/{conf_file}'])
+
+ if pdf_file is not None:
+ cmd.extend(['--pdf-file', f'/data/{pdf_file}'])
+
+
+ logger = logging.getLogger(__name__)
+ command = " ".join(cmd)
+ logger.info(f'# Executing: [ {command} ]')
+
+
+ process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
+ output = process.communicate()[0]
+ returncode = process.returncode
+
+ if returncode == 0:
+ results = output
+ else:
+ results = {'error': 'failed to retrieve results'}
+
+ self.finish(results)
+
+
+
+class UploadHandler(tornado.web.RequestHandler):
+ """
+ Upload File
+ """
+
+ def post(self):
+ """
+ post request
+
+ usage:
+
+ /upload
+ pass a file in `file` variable in header
+ """
+ data = self.request.files['file'][0]
+ fname = data['filename']
+
+ with open("/data/" + fname, 'wb+') as ofile:
+ ofile.write(data['body'])
+
+ self.finish("{upload : ok}")
+
+
+
+def server_main_block():
+ """
+ Main Function
+ """
+
+ app = Application([('/', SwPostStateValidator),
+ ('/upload', UploadHandler)])
+
+ # Cli Config
+ tornado.options.define("port", default=80, help="run on the given port", type=int)
+ tornado.options.parse_command_line()
+
+
+ # Server Config
+ http_server = tornado.httpserver.HTTPServer(app)
+ http_server.listen(tornado.options.options.port)
+
+
+ # Tornado's event loop handles it from here
+ print("# Servering.... \n [Ctrl + C] to quit")
+
+
+ # Logging
+ log_file_filename = "/var/log/tornado.log"
+ handler = logging.FileHandler(log_file_filename)
+ app_log = logging.getLogger("tornado.general")
+ tornado.log.enable_pretty_logging()
+ app_log.addHandler(handler)
+
+
+ try:
+ tornado.ioloop.IOLoop.instance().start()
+ except KeyboardInterrupt:
+ tornado.ioloop.IOLoop.instance().stop()
+
+ # start
+ IOLoop.instance().start()
+
+
+if __name__ == "__main__":
+ server_main_block()
diff --git a/sdv/docker/sdvstate/settings/common.yml b/sdv/docker/sdvstate/settings/common.yml
new file mode 100644
index 0000000..65f131c
--- /dev/null
+++ b/sdv/docker/sdvstate/settings/common.yml
@@ -0,0 +1,27 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+##################################
+# Program behavior configurations
+##################################
+
+# Logging
+log_filename: state-validation.log
+log_verbosity: info
+
+# Results
+results_dir: /tmp/state/
+save_results_locally: True \ No newline at end of file
diff --git a/sdv/docker/sdvstate/settings/installer.yml b/sdv/docker/sdvstate/settings/installer.yml
new file mode 100644
index 0000000..6a97c6b
--- /dev/null
+++ b/sdv/docker/sdvstate/settings/installer.yml
@@ -0,0 +1,29 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+#####################
+## Airship
+######################
+
+# Default Airship namespace list
+# list all kubernetes namespace that have Airship components
+airship_namespace_list:
+ - kube-system
+ - ceph
+ - ucp
+ - osh-infra
+ - tenant-ceph
+ - openstack \ No newline at end of file
diff --git a/sdv/docker/sdvstate/settings/result.yml b/sdv/docker/sdvstate/settings/result.yml
new file mode 100644
index 0000000..409fd0f
--- /dev/null
+++ b/sdv/docker/sdvstate/settings/result.yml
@@ -0,0 +1,19 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+# Note all paths must end with '/'
+results_path: /tmp/state/
+results_filename: results.json \ No newline at end of file
diff --git a/sdv/docker/sdvstate/state b/sdv/docker/sdvstate/state
new file mode 100755
index 0000000..41d17a4
--- /dev/null
+++ b/sdv/docker/sdvstate/state
@@ -0,0 +1,237 @@
+#!/usr/bin/env python3
+
+
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+"""(Post)Deployment State Validation tool
+"""
+
+import argparse
+import logging
+import os
+import re
+import ast
+import sys
+from datetime import datetime
+
+from tools.conf import settings
+from tools.result_api import result_api, Local
+from core import load_pdf
+from validator import AirshipValidator
+
+
+VERBOSITY_LEVELS = {
+ 'debug': logging.DEBUG,
+ 'info': logging.INFO,
+ 'warning': logging.WARNING,
+ 'error': logging.ERROR,
+ 'critical': logging.CRITICAL
+}
+
+_LOGGER = logging.getLogger()
+_CURR_DIR = os.path.dirname(os.path.realpath(__file__))
+
+
+
+def parse_param_string(values):
+ """
+ Parse and split a single '--test-params' argument.
+
+ This expects either 'x=y', 'x=y,z' or 'x' (implicit true)
+ values. For multiple overrides use a ; separated list for
+ e.g. --test-params 'x=z; y=(a,b)'
+ """
+ results = {}
+
+ if values == '':
+ return {}
+
+ for param, _, value in re.findall('([^;=]+)(=([^;]+))?', values):
+ param = param.strip()
+ value = value.strip()
+ if param:
+ if value:
+ # values are passed inside string from CLI, so we must retype them accordingly
+ try:
+ results[param] = ast.literal_eval(value)
+ except ValueError:
+ # for backward compatibility, we have to accept strings without quotes
+ _LOGGER.warning(f'Adding missing quotes around string value: {param} = {str(value)}')
+ results[param] = str(value)
+ else:
+ results[param] = True
+ return results
+
+def parse_arguments():
+ """
+ Parse command line arguments.
+ """
+
+ class _SplitTestParamsAction(argparse.Action):
+ """
+ Parse and split '--conf-params' arguments.
+
+ This expects either a single list of ; separated overrides
+ as 'x=y', 'x=y,z' or 'x' (implicit true) values.
+ e.g. --test-params 'x=z; y=(a,b)'
+ Or a list of these ; separated lists with overrides for
+ multiple tests.
+ e.g. --test-params "['x=z; y=(a,b)','x=z']"
+ """
+ def __call__(self, parser, namespace, values, option_string=None):
+ if values[0] == '[':
+ input_list = ast.literal_eval(values)
+ parameter_list = []
+ for test_params in input_list:
+ parameter_list.append(parse_param_string(test_params))
+ else:
+ parameter_list = parse_param_string(values)
+ results = parameter_list
+ setattr(namespace, self.dest, results)
+
+
+ class _ValidateFileAction(argparse.Action):
+ """Validate a file can be read from before using it.
+ """
+ def __call__(self, parser, namespace, values, option_string=None):
+ if not os.path.isfile(values):
+ raise argparse.ArgumentTypeError(
+ 'the path \'%s\' is not a valid path' % values)
+ if not os.access(values, os.R_OK):
+ raise argparse.ArgumentTypeError(
+ 'the path \'%s\' is not accessible' % values)
+
+ setattr(namespace, self.dest, values)
+
+ def list_logging_levels():
+ """Give a summary of all available logging levels.
+
+ :return: List of verbosity level names in decreasing order of
+ verbosity
+ """
+ return sorted(VERBOSITY_LEVELS.keys(),
+ key=lambda x: VERBOSITY_LEVELS[x])
+
+ parser = argparse.ArgumentParser(description='(Post)Deployment State Validation tool')
+
+ parser.add_argument('--version', action='version', version='%(prog)s 0.1')
+
+ parser.add_argument('--log-verbosity', choices=list_logging_levels(),
+ help='logging level')
+ parser.add_argument('-s', '--save-results-locally', action="store_true",
+ default=None, help='turn on local storage of results')
+
+ group = parser.add_argument_group('test configuration options')
+ group.add_argument('--conf-file', action=_ValidateFileAction,
+ help='settings file')
+ group.add_argument('--conf-params', action=_SplitTestParamsAction,
+ help='csv list of conf-file parameters: key=val; e.g. '
+ 'KUBE_CONFIG=/path/to/kubeconfig/file;PDF_FILE=path/to/pdf/file'
+ ' or a list of csv lists of conf-file parameters: key=val; e.g. '
+ '[\'KUBE_CONFIG=/path/to/kubeconfig/file\','
+ '\'PDF_FILE=path/to/pdf/file\']')
+
+ group = parser.add_argument_group('override conf-file options')
+ group.add_argument('--pdf-file', help='Path to PDF file')
+
+
+
+ args = vars(parser.parse_args())
+
+ return args
+
+
+def main():
+ """Main Function
+ """
+ args = parse_arguments()
+
+ ##################################
+ # Load settings:
+ # precedence: cli > env > conf-file > settings-dir
+ ##################################
+
+ settings.load_from_dir(os.path.join(_CURR_DIR, 'settings'))
+
+ if args["conf_file"]:
+ settings.load_from_file(os.path.join(_CURR_DIR, args["conf_file"]))
+
+ settings.load_from_env()
+
+ if args['conf_params']:
+ args = {**args.pop('conf_params'), **args} #merge 2 dicts
+ for key in args:
+ settings.setValue(key, args[key])
+
+
+ ##################################
+ # Results settings:
+ ##################################
+ now = datetime.now().strftime("%d-%m-%Y_%H-%M-%S/")
+ results_path = settings.getValue('results_dir') + now
+ settings.setValue('results_path', results_path)
+ if not os.path.exists(results_path):
+ os.makedirs(results_path)
+
+
+ ##################################
+ # Logs settings:
+ ##################################
+
+ log_file = results_path + settings.getValue('log_filename')
+ stream_handler = logging.StreamHandler(sys.stdout)
+ file_handler = logging.FileHandler(filename=log_file)
+
+ level = settings.getValue('LOG_VERBOSITY')
+ _LOGGER.setLevel(logging.DEBUG)
+ stream_handler.setLevel(VERBOSITY_LEVELS[level])
+ file_handler.setLevel(VERBOSITY_LEVELS[level])
+
+ form = '[%(levelname)-8s] : %(msg)s'
+ if level == 'debug':
+ form = '["%(asctime)s"][%(levelname)-8s][%(pathname)s:%(funcName)s:%(lineno)-4s] : %(msg)s'
+ stream_handler.setFormatter(logging.Formatter(form))
+ file_handler.setFormatter(logging.Formatter(form))
+
+ _LOGGER.addHandler(stream_handler)
+ _LOGGER.addHandler(file_handler)
+
+
+ ##################################
+ # ResultAPI settings:
+ ##################################
+
+ if settings.getValue('save_results_locally'):
+ result_api.register_storage(Local())
+
+
+ ####
+ # Still Developing
+ ####
+ load_pdf()
+
+ installer = settings.getValue('pdf_file')["deployment_info"]["installer_used"].lower()
+
+ if installer == 'airship':
+ airship = AirshipValidator()
+ airship.validate()
+
+
+
+
+if __name__ == "__main__":
+ main()
diff --git a/sdv/docker/sdvstate/tools/conf/__init__.py b/sdv/docker/sdvstate/tools/conf/__init__.py
new file mode 100644
index 0000000..67c808f
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/conf/__init__.py
@@ -0,0 +1,120 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+
+"""Settings and configuration handlers.
+
+Settings will be loaded from several .yaml or .yml files
+and any user provided settings file.
+"""
+
+import os
+import ast
+
+import yaml
+
+# pylint: disable=invalid-name
+
+class Settings():
+ """Holding class for settings.
+ """
+ def __init__(self):
+ pass
+
+
+ def getValue(self, attr):
+ """
+ Return a settings item value
+ """
+ try:
+ attr = attr.lower()
+ return getattr(self, attr)
+ except AttributeError:
+ raise AttributeError("{obj} object has no attribute \
+ {attr}".format(obj=self.__class__, attr=attr))
+
+
+ def setValue(self, name, value):
+ """Set a value
+ """
+ if name is not None and value is not None:
+ super(Settings, self).__setattr__(name.lower(), value)
+
+
+ def load_from_file(self, path):
+ """Update ``settings`` with values found in module at ``path``.
+ """
+ with open(path) as file:
+ configs = yaml.load_all(file, Loader=yaml.SafeLoader)
+ for conf in configs:
+ for name, value in conf.items():
+ self.setValue(name, value)
+
+
+ def load_from_env(self):
+ """
+ Update ``settings`` with values found in the environment.
+ """
+ for key in os.environ:
+ value = os.environ[key]
+
+ #evaluate string to python type
+ try:
+ value = ast.literal_eval(os.environ[key])
+ except (ValueError, SyntaxError):
+ pass #already string
+
+ self.setValue(key, value)
+
+
+ def load_from_dir(self, dir_path):
+ """Update ``settings`` with contents of the yaml files at ``path``.
+
+ Files are read in ascending order, hence if a configuration item
+ exists in more than one file, then the setting in the file that
+ occurs in the last read file will have high precedence and
+ overwrite previous values.
+
+ Same precedence logic for sub-directories.
+ Also, child directory will have more precedence than it's parent
+
+ :param dir_path: The full path to the dir from which to load the
+ yaml files.
+
+ :returns: None
+ """
+ files = list_yamls(dir_path)
+
+ for file in files:
+ self.load_from_file(file)
+
+
+settings = Settings()
+
+
+def list_yamls(dir_path):
+ """Get all yaml files recursively in ``dir_path``
+ """
+ files = []
+ dir_list = [x[0] for x in os.walk(dir_path)]
+ dir_list.sort()
+ for path in dir_list:
+ dir_files = [path+'/'+f for f in os.listdir(path)
+ if f.endswith('.yaml') or f.endswith('.yml')]
+ if dir_files is not None:
+ dir_files.sort()
+ files.extend(dir_files)
+ return files
diff --git a/sdv/docker/sdvstate/tools/kube_utils.py b/sdv/docker/sdvstate/tools/kube_utils.py
new file mode 100644
index 0000000..3468ac6
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/kube_utils.py
@@ -0,0 +1,85 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+"""
+Kubernetes cluster api helper functions
+"""
+
+from kubernetes import client, config
+from kubernetes.stream import stream
+
+from tools.conf import settings # pylint: disable=import-error
+
+
+def load_kube_api():
+ """
+ Loads kubernetes api
+ """
+ config.load_kube_config(settings.getValue('kube_config'))
+ api = client.CoreV1Api()
+ settings.setValue('kube_api', api)
+
+
+def kube_api():
+ """
+ Returns kube_api object
+ """
+ return settings.getValue('kube_api')
+
+
+def get_pod_with_labels(labels):
+ """
+ Returns json details any one pod with matching labels
+
+ :param labels: labels to find matching pod
+ :return: pod details
+ """
+ api = kube_api()
+ pod = api.list_pod_for_all_namespaces(label_selector=labels).items[0]
+ return pod
+
+
+def kube_exec(pod, cmd):
+ """
+ Executes `cmd` inside `pod` and returns response
+
+ :param pod: pod object
+ :param cmd: command to execute inside pod
+ :return: response from pod
+ """
+ api = kube_api()
+ response = stream(api.connect_get_namespaced_pod_exec,
+ pod.metadata.name, pod.metadata.namespace, command=cmd,
+ stderr=True, stdin=False, stdout=True, tty=False)
+ return response
+
+
+def kube_curl(*args):
+ """
+ executes curl cmd in kubernetes network
+
+ :param args: comma separated list of args to pass to curl
+ :return: http response
+ """
+ args = list(args)
+ args.insert(0, "curl")
+
+ labels = "application=prometheus-openstack-exporter,component=exporter"
+ pod = get_pod_with_labels(labels)
+
+ response = kube_exec(pod, args)
+
+ return response
diff --git a/sdv/docker/sdvstate/tools/result_api/__init__.py b/sdv/docker/sdvstate/tools/result_api/__init__.py
new file mode 100644
index 0000000..0633103
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/result_api/__init__.py
@@ -0,0 +1,24 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+"""
+Result Manager Package
+"""
+from .result_api import result_api
+from .rfile import rfile
+
+from .storage.storage_api import StorageApi
+from .storage.local.local import Local
diff --git a/sdv/docker/sdvstate/tools/result_api/result_api.py b/sdv/docker/sdvstate/tools/result_api/result_api.py
new file mode 100644
index 0000000..95dfd56
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/result_api/result_api.py
@@ -0,0 +1,73 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Result API
+Main entry point to use results manager
+"""
+
+import logging
+from .storage.storage_api import StorageApi
+
+class ResultApi():
+ """
+ Result API
+ Provides various storage options to use implemented as
+ plugin to storage api
+ """
+
+ def __init__(self):
+ """
+ Initialization function
+ """
+ self._logger = logging.getLogger(__name__)
+ self._storage_handles = []
+
+ def register_storage(self, storage_api):
+ """
+ Registers ``storage_api`` as an active storage option to use.
+ """
+ self._logger.debug("Loading new Storage API...")
+ if not isinstance(storage_api, StorageApi):
+ raise TypeError("incorrect storage type, Required StorageAPI obj")
+
+ storage_api.load_settings()
+ self._storage_handles.append(storage_api)
+ self._logger.info(f'{storage_api.name} api registered')
+
+ def unregister_storage(self, storage_api):
+ """
+ Removes registered ``storage_api`` if exists
+ """
+ while storage_api in self._storage_handles:
+ self._storage_handles.remove(storage_api)
+
+ def unregister_all(self):
+ """
+ Removes all registered storage endpoints
+ """
+ for storage_api in self._storage_handles:
+ self.unregister_storage(storage_api)
+
+ def store(self, data):
+ """
+ Calls all active storage_api and stores ``data`` in all of them
+ """
+ for api in self._storage_handles:
+ api.store(data)
+
+
+# pylint: disable=invalid-name
+result_api = ResultApi()
diff --git a/sdv/docker/sdvstate/tools/result_api/rfile.py b/sdv/docker/sdvstate/tools/result_api/rfile.py
new file mode 100644
index 0000000..71a0924
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/result_api/rfile.py
@@ -0,0 +1,52 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+"""rfile is Object representation of a file for Result_api and storage_api
+"""
+
+import logging
+
+# pylint: disable=invalid-name
+
+class rfile():
+ """
+ rfile object to represent files in Result API
+ """
+
+ def __init__(self, data):
+ """
+ Initialisation function
+ """
+ self._logger = logging.getLogger(__name__)
+ self.hold_data(data)
+
+ def get_data(self):
+ """
+ Returns stored data
+ """
+ if self._data == '':
+ self._logger.warning('Reading from a empty \'rfile\'')
+ return self._data
+
+
+ def hold_data(self, data):
+ """
+ Holds data of a file
+ """
+ if data is None:
+ self._logger.warning('Storing an empty \'rfile\'')
+ data = ''
+ self._data = data
diff --git a/sdv/docker/sdvstate/tools/result_api/storage/__init__.py b/sdv/docker/sdvstate/tools/result_api/storage/__init__.py
new file mode 100644
index 0000000..d647f0f
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/result_api/storage/__init__.py
@@ -0,0 +1,18 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Abstract class for Storage API
+"""
diff --git a/sdv/docker/sdvstate/tools/result_api/storage/local/__init__.py b/sdv/docker/sdvstate/tools/result_api/storage/local/__init__.py
new file mode 100644
index 0000000..185ec01
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/result_api/storage/local/__init__.py
@@ -0,0 +1,17 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Local storage api
+"""
diff --git a/sdv/docker/sdvstate/tools/result_api/storage/local/local.py b/sdv/docker/sdvstate/tools/result_api/storage/local/local.py
new file mode 100644
index 0000000..d7dc67b
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/result_api/storage/local/local.py
@@ -0,0 +1,133 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Local Storage Api
+"""
+
+import logging
+import os
+import random
+import string
+import json
+from tools.conf import settings # pylint: disable=import-error
+
+from ..storage_api import StorageApi
+from ... import rfile
+
+
+
+class Local(StorageApi):
+ """
+ Storage API
+ Provides abstract class for various storage options to implement as
+ plugin to storage api
+ """
+
+ def __init__(self):
+ """
+ Initialization function
+ """
+ super(Local, self).__init__()
+ self.name = 'Local Storage'
+ self._logger = logging.getLogger(__name__)
+ self._path = ''
+ self._filename = ''
+
+ def store(self, data):
+ """
+ stores ``data``, ``data`` should be a dict
+
+ :param data: dict object to store
+ """
+ if not isinstance(data, dict):
+ raise TypeError("incorrect data type to store, dict required")
+
+ if not os.path.isfile(self._filename):
+ with open(self._filename, 'w') as fhandle:
+ json.dump([], fhandle)
+
+ with open(self._filename, 'r') as fhandle:
+ records = json.load(fhandle)
+ temp_data = data.copy()
+ eval_rfile(temp_data)
+ records.append(temp_data)
+
+ with open(self._filename, 'w') as fhandle:
+ self._logger.info(f'{self.name}: New record saved')
+ json.dump(records, fhandle, indent=4, sort_keys=True, default=str)
+
+
+ def load_settings(self):
+ """
+ Load all required settings otherwise set to default
+ Settings to load:
+ * ``result_path`` (default: /tmp/local/)
+ * ``results_filename`` (default: results.json)
+ """
+ try:
+ path = settings.getValue('results_path')
+ except AttributeError:
+ path = '/tmp/local/'
+ settings.setValue('results_path', path)
+
+ try:
+ filename = settings.getValue('results_filename')
+ except AttributeError:
+ filename = 'results.json'
+ settings.setValue('results_filename', filename)
+
+ if not os.path.exists(path):
+ os.makedirs(path)
+
+ self._path = path
+ self._filename = path + filename
+
+
+
+
+
+def eval_rfile(data):
+ """
+ Find all values of a type rfile in data dict/list and evals them into
+ actual file
+ """
+ if isinstance(data, dict):
+ for key, value in data.items():
+ if isinstance(value, rfile):
+ data[key] = rfile_save(value, key)
+ else:
+ eval_rfile(value)
+ if isinstance(data, list):
+ for i, _ in enumerate(data):
+ if isinstance(data[i], rfile):
+ data[i] = rfile_save(data[i])
+ else:
+ eval_rfile(data[i])
+
+
+def rfile_save(rfile_obj, prefix='zz'):
+ """
+ Takes rfile Object and stores it into random file returning filename
+ """
+ letters = string.ascii_lowercase
+ suffix = ''.join(random.choice(letters) for i in range(6))
+ filename = settings.getValue('results_path') + f'{prefix}-{suffix}.txt'
+ if os.path.isfile(filename):
+ return rfile_save(rfile_obj, prefix)
+ else:
+ with open(filename, 'w') as fhandle:
+ fhandle.write(rfile_obj.get_data())
+ return f'{prefix}-{suffix}.txt'
diff --git a/sdv/docker/sdvstate/tools/result_api/storage/storage_api.py b/sdv/docker/sdvstate/tools/result_api/storage/storage_api.py
new file mode 100644
index 0000000..5ff0d8f
--- /dev/null
+++ b/sdv/docker/sdvstate/tools/result_api/storage/storage_api.py
@@ -0,0 +1,46 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Abstract class for Storage API
+"""
+
+import logging
+
+
+class StorageApi():
+ """
+ Storage API
+ Provides abstract class for various storage options to implement as
+ plugin to storage api
+ """
+
+ def __init__(self):
+ """
+ Initialization function
+ """
+ self._logger = logging.getLogger(__name__)
+
+ def store(self, data):
+ """
+ stores ``data``
+ """
+ raise NotImplementedError()
+
+ def load_settings(self):
+ """
+ Load all required settings
+ """
+ raise NotImplementedError()
diff --git a/sdv/docker/sdvstate/validator/__init__.py b/sdv/docker/sdvstate/validator/__init__.py
new file mode 100644
index 0000000..0e1fb38
--- /dev/null
+++ b/sdv/docker/sdvstate/validator/__init__.py
@@ -0,0 +1,21 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Package for Validators
+"""
+
+from .validator import Validator
+from .airship.airship import AirshipValidator
diff --git a/sdv/docker/sdvstate/validator/airship/airship.py b/sdv/docker/sdvstate/validator/airship/airship.py
new file mode 100644
index 0000000..e77f06f
--- /dev/null
+++ b/sdv/docker/sdvstate/validator/airship/airship.py
@@ -0,0 +1,51 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Airship Validator
+"""
+
+import logging
+import ast
+import json
+
+from tools.conf import settings
+from tools.result_api import result_api, rfile
+from tools.kube_utils import *
+from validator.validator import Validator
+
+## Checks
+from .pod_health_check import pod_health_check
+
+
+
+class AirshipValidator(Validator):
+ """Class for Airship Validation
+ """
+
+ def __init__(self):
+ """
+ Initialisation function.
+ """
+ super(AirshipValidator, self).__init__()
+ self._logger = logging.getLogger(__name__)
+
+ load_kube_api()
+
+
+ def validate(self):
+ """
+ """
+ pod_health_check()
diff --git a/sdv/docker/sdvstate/validator/airship/pod_health_check.py b/sdv/docker/sdvstate/validator/airship/pod_health_check.py
new file mode 100644
index 0000000..34a6747
--- /dev/null
+++ b/sdv/docker/sdvstate/validator/airship/pod_health_check.py
@@ -0,0 +1,94 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+
+import logging
+from kubernetes import client, config
+
+from tools.kube_utils import kube_api
+from tools.conf import settings
+from tools.result_api import result_api, rfile
+
+
+
+def pod_health_check():
+ """
+ Check health of all pods and get logs of failed pods
+ """
+ api = kube_api()
+ namespace_list = settings.getValue('airship_namespace_list')
+ for namespace in namespace_list:
+ pod_list = api.list_namespaced_pod(namespace)
+ for pod in pod_list.items:
+ result = pod_status(pod)
+ if result['state'] == 'fail':
+ result['logs'] = get_logs(pod)
+ result_api.store(result)
+
+
+
+def pod_status(pod):
+ """
+ Check health of a pod and returns it's status as result
+ """
+ result = {'state': 'ok',
+ 'kind': 'pod',
+ 'name': pod.metadata.name,
+ 'namespace': pod.metadata.namespace,
+ 'node': pod.spec.node_name}
+
+ if pod.status.container_statuses is None:
+ result['state'] = 'fail'
+ result['pod_details'] = rfile(str(pod))
+ else:
+ for container in pod.status.container_statuses:
+ if container.state.running is not None:
+ status = 'Running'
+ if container.state.terminated is not None:
+ status = container.state.terminated.reason
+ if container.state.waiting is not None:
+ status = container.state.waiting.reason
+
+ if status not in ('Running', 'Completed'):
+ result['state'] = 'fail'
+ result['pod_details'] = rfile(str(pod))
+
+ info = f'[Health: {result["state"]}] Name: {result["name"]}, '
+ info = info + f'Namespace: {result["namespace"]}, Node: {result["node"]}'
+
+ logger = logging.getLogger(__name__)
+ logger.info(info)
+ return result
+
+
+def get_logs(pod):
+ """
+ Collects logs of all containers in ``pod``
+ """
+ api = kube_api()
+ logs = []
+ if pod.status.container_statuses is not None:
+ for container in pod.status.container_statuses:
+ con = {'container': container.name}
+ if container.state.waiting is not None and \
+ container.state.waiting.reason == 'PodInitializing':
+ log = 'Not found, status: waiting, reason: PodInitializing'
+ else:
+ log = api.read_namespaced_pod_log(name=pod.metadata.name,
+ namespace=pod.metadata.namespace,
+ container=container.name)
+ con['log'] = rfile(log)
+ logs.append(con)
+ return logs
diff --git a/sdv/docker/sdvstate/validator/validator.py b/sdv/docker/sdvstate/validator/validator.py
new file mode 100644
index 0000000..4f36008
--- /dev/null
+++ b/sdv/docker/sdvstate/validator/validator.py
@@ -0,0 +1,27 @@
+# Copyright 2020 University Of Delhi.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+"""
+Interface for Software Validators
+"""
+
+class Validator():
+ """
+ Interface for Software to Validate
+ """
+ def __init__(self):
+ """Initialisation function.
+ """
+ \ No newline at end of file
diff --git a/sdv/docs/docker/state/development/design/index.rst b/sdv/docs/docker/state/development/design/index.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sdv/docs/docker/state/development/design/index.rst
diff --git a/sdv/docs/docker/state/development/overview/index.rst b/sdv/docs/docker/state/development/overview/index.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sdv/docs/docker/state/development/overview/index.rst
diff --git a/sdv/docs/docker/state/development/overview/result-api-architecture.png b/sdv/docs/docker/state/development/overview/result-api-architecture.png
new file mode 100644
index 0000000..e464187
--- /dev/null
+++ b/sdv/docs/docker/state/development/overview/result-api-architecture.png
Binary files differ
diff --git a/sdv/docs/docker/state/development/overview/result_api.rst b/sdv/docs/docker/state/development/overview/result_api.rst
new file mode 100644
index 0000000..12fd62c
--- /dev/null
+++ b/sdv/docs/docker/state/development/overview/result_api.rst
@@ -0,0 +1,178 @@
+Result API
+==========
+
+Table of contents
+^^^^^^^^^^^^^^^^^
+
+- `Description <#Description>`__
+- `Architecture <#Architecture>`__
+- `Managing configuration <#Managing%20configuration>`__
+- `How to Use <#How%20to%20Use>`__
+
+----------------------
+
+Description
+~~~~~~~~~~~
+
+Result API is very simple write-only API for storing results of any size
+to multiple storage endpoints.
+
+Results API focuses on only sending test results to required
+destinations so that they can be later viewed by user but do not
+implements read functionality as this is not required while running
+tests
+
+Usage is very simple:
+'''''''''''''''''''''
+
+.. code:: python
+
+ from result_api import result_api as api
+ from result_api import Local
+
+ # First register storage enpoints
+ endpoint = Local()
+ api.register_storage(endpoint)
+
+ # Now, use anywhere in program
+ api.store(data)
+
+Note: In above example we used only one storage endpoints so, whenever
+``api`` will make call to ``store()`` method it will be used. But if
+register multiple storage endpoints then whenever ``api`` will make call
+to ``store()`` method all endpoints will be called to store data. This
+package doesn't allows to selectively make calls to different storage
+endpoint for storing. Instead follows **one api -> many endpoint (one to
+many)** design.
+
+----------------------
+
+Architecture
+~~~~~~~~~~~~
+
+|img|
+
+*ResultsAPI exports data to various storage endpoints like harddisk,
+SQL, Mongo, etc. exposed by StorageAPI in write-only mode*
+
+----------------------
+
+Managing configuration
+~~~~~~~~~~~~~~~~~~~~~~
+
+Result API uses `Conf: Program Settings handler <../conf/readme.md>`__
+to manage all it's settings. So, user can change any settings of
+ResultAPI as they need. ``Conf`` allows to change settings in two ways
+either read from file ``settings.load_from_file(path/to/file)`` or set
+inside program ``settings.setValue(key, Value)``
+
+Configurations available:
+
++-----------------+---------------------+-------------------------------------+
+| Storage | setting | optional |
++=================+=====================+=====================================+
+| Local Storage | results\_path | Yes, defaults to ``/tmp/local/`` |
++-----------------+---------------------+-------------------------------------+
+| Local Storage | results\_filename | Yes, defaults to ``results.json`` |
++-----------------+---------------------+-------------------------------------+
+
+----------------------
+
+How to Use
+~~~~~~~~~~
+
+For using ResultAPI successfully the following steps are required
+
+#. **Import result\_api instance** from ResultAPI package as this
+ instance will be commonly used across our program.
+#. **Import required StorageAPI**
+#. **Set Storage settings** or load settings from file
+#. **Create Storage Endpoints from StorageAPI**
+#. **Register Storage Endpoints with result\_api**
+
+Now, result\_api is ready to use. We can send values to all registered
+storage endpoints by making a simple call to ```store()``
+method <#store()%20method>`__
+
+store() method
+''''''''''''''
+
+``result_api`` has ``store(dict)`` method for storing data.
+
+.. code:: python
+
+ # data must be a dict
+ data = { 'key1': "value1", 'key2': 5, 'dumykeytext': "dummy string value"}
+ result_api.store(data)
+
+rfile
+'''''
+
+rfile stands for result file. When you have extremely big value for a
+key you would like to tell your storage endpoint to store them in
+separate file and refer them in actual key. For storing value of type
+file in result\_api use rfile construct.
+
+.. code:: python
+
+ data = { 'name': 'example', 'myfile': rfile('Text to store in this file')}
+ result_api.store(data)
+ # Respective StorageAPI will evaluate "data" for all rfile values and store their text in some separate file/storage-object and put there refernece in "data"
+
+Use Local Storage with Result API
+'''''''''''''''''''''''''''''''''
+
+.. code:: python
+
+ from result_api import result_api as api
+ from result_api import Local
+ from result_api import rfile
+
+ def main():
+ # Update settings required for Local storage
+ settings.setValue('results_path', '/tmp/myresults/')
+ settings.setValue('results_filename', 'results.json')
+
+ # Register Local() to result_api, this will load above settings automatically
+ api.register_storage(Local())
+
+ data = { 'testcase': "RA1.24", 'value': 'Pass', 'logs': rfile('These are logs')}
+
+ # Now, store any data
+ api.store(data)
+
+Register Storage Endpoint
+'''''''''''''''''''''''''
+
+.. code:: python
+
+ from result_api.storage.mystorageapi import MyStorageAPI
+
+ # Set required settings values for storage endpoint
+ settings.setValue('mysetting1', 'value')
+ settings.setValue('mysetting2', 'value2')
+
+ #Now Register StorageAPI to ResultAPI
+ endpoint = MyStorageAPI()
+ api.register_storage(endpoint)
+
+Changing settings
+'''''''''''''''''
+
+.. code:: python
+
+ # Set values in a yaml file and load it
+ settings.load_from_file('result.yml')
+
+ # Or set in program
+ settings.setValue('key', 'value')
+
+ # Note above steps will only change settings values but will not impact any previously registered storage endpoints
+ # To use endpoints with new value, register new endpoints
+ endpoint = MyStorageAPI()
+ api.register_storage(endpoint)
+
+ # And do not forget to unregister old endpoint as they have old settings
+ api.unregister_storage(old_endpoint)
+
+.. |img| image:: result-api-architecture.png
diff --git a/sdv/docs/docker/state/development/overview/settings.rst b/sdv/docs/docker/state/development/overview/settings.rst
new file mode 100644
index 0000000..c70f21e
--- /dev/null
+++ b/sdv/docs/docker/state/development/overview/settings.rst
@@ -0,0 +1,38 @@
+Program Settings handler
+------------------------
+
+``from tools.conf import settings``
+
+Settings will be loaded from several ``.yaml`` or ``.yml`` files and any
+user provided settings files.
+
+So, that user can use these settings values across program.
+
+This utility loads settings from yaml files in form of key and value
+where key is always ``string`` while value can be of type python:
+
+- ``int`` e.g. 5, 45, 1234
+- ``str`` e.g. hello, world
+- ``float`` e.g. 34.56, 12.7
+- ``list`` e.g. [ ‘month’ , ‘is’, 45 ]
+- ``dict`` e.g. {‘program’: ‘sdv’, ‘language’: ‘python’}
+- ``bool`` e.g. True, False
+
+keys are case-insensitive
+^^^^^^^^^^^^^^^^^^^^^^^^^
+
+The utility is case-insensitive to keys used as it automatically
+converts all keys to lower case.
+
+E.g. ``Program : sdv``, ``program : sdv``, ``PrOgRam : sdv`` all are
+same.
+
+- ``settings.load_from_file(path/to/file)``
+- ``settings.load_from_env()``
+- ``settings.load_from_dir(directory/to/search/yamls)``
+
+``settings.load_from_dir()`` reads all yaml files in given directory and
+all it’s sub-directory recursively in ascending order, hence if a
+configuration item exists in more than one file, then the setting in the
+file that occurs in the last read file will have high precedence and
+overwrite previous values. . \ No newline at end of file
diff --git a/sdv/docs/docker/state/release/configguide/index.rst b/sdv/docs/docker/state/release/configguide/index.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sdv/docs/docker/state/release/configguide/index.rst
diff --git a/sdv/docs/docker/state/release/userguide/index.rst b/sdv/docs/docker/state/release/userguide/index.rst
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/sdv/docs/docker/state/release/userguide/index.rst