aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorMichal Jagiello <michal.jagiello@t-mobile.pl>2023-04-19 09:53:38 +0000
committerMichal Jagiello <michal.jagiello@t-mobile.pl>2023-04-19 09:58:02 +0000
commit6e88d548362b32a15a094fdf8d83f082107c7962 (patch)
tree8603bba306091d8a323ac4ed32920bc075998af5 /test
parentc57b24365c08afe394e52808d55e9b70ac878205 (diff)
Fix security versions script
That script was usused on security versions tests, so I updated it with the latest changes from repo which was really used, created needed files and after we merge it we could use that on security tests. Issue-ID: TEST-394 Signed-off-by: Michal Jagiello <michal.jagiello@t-mobile.pl> Change-Id: I8e5daa7d43e2723bbe3308cf85b1cae2b2f587ad
Diffstat (limited to 'test')
-rw-r--r--test/security/check_versions/.gitignore1
-rw-r--r--test/security/check_versions/README.md15
-rw-r--r--test/security/check_versions/env/Vagrantfile36
-rw-r--r--test/security/check_versions/env/configuration/namespaces.yaml45
-rw-r--r--test/security/check_versions/env/configuration/terminated.yaml17
-rw-r--r--test/security/check_versions/env/configuration/versions.yaml112
-rw-r--r--test/security/check_versions/env/requirements-dev.txt9
-rw-r--r--test/security/check_versions/env/requirements.txt6
-rw-r--r--test/security/check_versions/pyproject.toml21
-rw-r--r--test/security/check_versions/requirements.txt7
-rw-r--r--test/security/check_versions/tests/test_main.py4
-rw-r--r--test/security/check_versions/tests/test_verify_versions_acceptability.py4
-rw-r--r--test/security/check_versions/tox.ini10
-rw-r--r--test/security/check_versions/versions/__init__.py0
-rw-r--r--test/security/check_versions/versions/k8s_bin_versions_inspector.py (renamed from test/security/check_versions/src/k8s_bin_versions_inspector.py)140
-rw-r--r--test/security/check_versions/versions/k8s_bin_versions_inspector_test_case.py116
-rw-r--r--test/security/check_versions/versions/reporting.py265
-rw-r--r--test/security/check_versions/versions/templates/base.html.j2232
-rw-r--r--test/security/check_versions/versions/templates/versions.html.j285
19 files changed, 815 insertions, 310 deletions
diff --git a/test/security/check_versions/.gitignore b/test/security/check_versions/.gitignore
index db6444b3c..2b574f8c0 100644
--- a/test/security/check_versions/.gitignore
+++ b/test/security/check_versions/.gitignore
@@ -1,5 +1,4 @@
.pytest_cache/
__pycache__/
-/env/.vagrant
/temp/
/.tox/
diff --git a/test/security/check_versions/README.md b/test/security/check_versions/README.md
index 3934ca77a..399d10443 100644
--- a/test/security/check_versions/README.md
+++ b/test/security/check_versions/README.md
@@ -6,25 +6,12 @@ in the kubernetes cluster containers.
## Commands
-### Creating environment
-
-All development and testing process, should be done in prepared virtual machine,
-that is containing development environment for this project. Vagrant plugins,
-that are required to start virtual machine: `vagrant-libvirt`, `vagrant-reload`,
-`vagrant-sshfs`.
-
-```bash
-cd env
-vagrant up
-vagrant ssh
-```
-
### Install dependencies
To install dependencies for normal usage of script, run this command.
```bash
-pip3 install -r env/requirements.txt
+pip3 install -r requirements.txt
```
### Code formatting
diff --git a/test/security/check_versions/env/Vagrantfile b/test/security/check_versions/env/Vagrantfile
deleted file mode 100644
index 9753a74ad..000000000
--- a/test/security/check_versions/env/Vagrantfile
+++ /dev/null
@@ -1,36 +0,0 @@
-# -*- mode: ruby -*-
-# vi: set ft=ruby :
-
-Vagrant.configure("2") do |config|
-
- config.vm.provider :libvirt do |libvirt|
- libvirt.default_prefix = "k8s_bin_versions_inspector";
- libvirt.driver = "kvm";
- libvirt.cpus = 6;
- libvirt.memory = 12288;
- end
-
- config.vm.box = "generic/ubuntu1804";
- config.vm.hostname = "k8s-bin-versions-inspector";
- config.vm.synced_folder ".", "/vagrant", disabled: true;
- config.vm.synced_folder "..", "/home/vagrant/k8s_bin_versions_inspector", type: :sshfs;
-
- config.vm.provision "shell", inline: <<-end
- export DEBIAN_FRONTEND=noninteractive &&\
- apt-get update &&\
- apt-get upgrade -y &&\
- apt-get dist-upgrade -y &&\
- apt-get install -y python3 python3-pip snap git vim net-tools htop &&\
- pip3 install --system -r /home/vagrant/k8s_bin_versions_inspector/env/requirements-dev.txt &&\
- snap install --classic microk8s &&\
- usermod -a -G microk8s vagrant
- end
- config.vm.provision :reload;
- config.vm.provision "shell", privileged: false, inline: <<-end
- microk8s start &&\
- microk8s status --wait-ready &&\
- microk8s config > /home/vagrant/.kube/config &&\
- microk8s kubectl apply -f /home/vagrant/k8s_bin_versions_inspector/env/configuration
- end
-end
-
diff --git a/test/security/check_versions/env/configuration/namespaces.yaml b/test/security/check_versions/env/configuration/namespaces.yaml
deleted file mode 100644
index f300cc7da..000000000
--- a/test/security/check_versions/env/configuration/namespaces.yaml
+++ /dev/null
@@ -1,45 +0,0 @@
----
-apiVersion: v1
-kind: Namespace
-metadata:
- name: ingress-nginx
-
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-ingress-nginx
- namespace: ingress-nginx
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-ingress-nginx
- template:
- metadata:
- labels:
- app: kbvi-test-ingress-nginx
- spec:
- containers:
- - name: echo-server
- image: jmalloc/echo-server
-
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-kube-system
- namespace: kube-system
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-kube-system
- template:
- metadata:
- labels:
- app: kbvi-test-kube-system
- spec:
- containers:
- - name: echo-server
- image: jmalloc/echo-server
diff --git a/test/security/check_versions/env/configuration/terminated.yaml b/test/security/check_versions/env/configuration/terminated.yaml
deleted file mode 100644
index dd6ce829d..000000000
--- a/test/security/check_versions/env/configuration/terminated.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-terminated
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-terminated
- template:
- metadata:
- labels:
- app: kbvi-test-terminated
- spec:
- containers:
- - name: python
- image: python
diff --git a/test/security/check_versions/env/configuration/versions.yaml b/test/security/check_versions/env/configuration/versions.yaml
deleted file mode 100644
index 75b7f7b85..000000000
--- a/test/security/check_versions/env/configuration/versions.yaml
+++ /dev/null
@@ -1,112 +0,0 @@
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-python-jupyter
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-python-jupyter
- template:
- metadata:
- labels:
- app: kbvi-test-python-jupyter
- spec:
- containers:
- - name: jupyter
- image: jupyter/base-notebook
-
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-python-jupyter-old
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-python-jupyter-old
- template:
- metadata:
- labels:
- app: kbvi-test-python-jupyter-old
- spec:
- containers:
- - name: jupyter-old
- image: jupyter/base-notebook:ff922f8f533a
-
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-python-stderr-filebeat
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-python-stderr-filebeat
- template:
- metadata:
- labels:
- app: kbvi-test-python-stderr-filebeat
- spec:
- containers:
- - name: filebeat
- image: docker.elastic.co/beats/filebeat:5.5.0
-
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-java-keycloak
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-java-keycloak
- template:
- metadata:
- labels:
- app: kbvi-test-java-keycloak
- spec:
- containers:
- - name: keycloak
- image: jboss/keycloak
-
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-java-keycloak-old
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-java-keycloak-old
- template:
- metadata:
- labels:
- app: kbvi-test-java-keycloak-old
- spec:
- containers:
- - name: keycloak-old
- image: jboss/keycloak:8.0.0
-
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: kbvi-test-java-keycloak-very-old
-spec:
- replicas: 1
- selector:
- matchLabels:
- app: kbvi-test-java-keycloak-very-old
- template:
- metadata:
- labels:
- app: kbvi-test-java-keycloak-very-old
- spec:
- containers:
- - name: keycloak-very-old
- image: jboss/keycloak:2.0.0.Final
diff --git a/test/security/check_versions/env/requirements-dev.txt b/test/security/check_versions/env/requirements-dev.txt
deleted file mode 100644
index 1ced42c04..000000000
--- a/test/security/check_versions/env/requirements-dev.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-cerberus
-dataclasses
-kubernetes
-pyyaml
-tabulate
-black
-pylint
-pytest
-
diff --git a/test/security/check_versions/env/requirements.txt b/test/security/check_versions/env/requirements.txt
deleted file mode 100644
index e81358f72..000000000
--- a/test/security/check_versions/env/requirements.txt
+++ /dev/null
@@ -1,6 +0,0 @@
-cerberus
-dataclasses
-kubernetes
-pyyaml
-tabulate
-
diff --git a/test/security/check_versions/pyproject.toml b/test/security/check_versions/pyproject.toml
new file mode 100644
index 000000000..a9b5c540e
--- /dev/null
+++ b/test/security/check_versions/pyproject.toml
@@ -0,0 +1,21 @@
+[project]
+name = "check_versions"
+readme = "README.md"
+version = "1.0"
+requires-python = ">=3.7"
+dependencies = [
+ "kubernetes",
+ "jinja2",
+ "xtesting",
+ "tabulate",
+ "cerberus",
+ "packaging",
+ "wget"
+]
+
+[build-system]
+requires = ["setuptools"]
+build-backend = "setuptools.build_meta"
+
+[project.entry-points."xtesting.testcase"]
+versions = "versions.k8s_bin_versions_inspector_test_case:Inspector"
diff --git a/test/security/check_versions/requirements.txt b/test/security/check_versions/requirements.txt
new file mode 100644
index 000000000..8e46a3acf
--- /dev/null
+++ b/test/security/check_versions/requirements.txt
@@ -0,0 +1,7 @@
+kubernetes
+jinja2
+xtesting
+tabulate
+cerberus
+packaging
+wget
diff --git a/test/security/check_versions/tests/test_main.py b/test/security/check_versions/tests/test_main.py
index 0dff0b230..37ad45ee3 100644
--- a/test/security/check_versions/tests/test_main.py
+++ b/test/security/check_versions/tests/test_main.py
@@ -7,9 +7,7 @@ import yaml
def exec_main(pod_name_trimmer, acceptable_data):
-
with tempfile.NamedTemporaryFile() as output_temp, tempfile.NamedTemporaryFile() as acceptable_temp:
-
with open(acceptable_temp.name, "w") as stream:
yaml.safe_dump(acceptable_data, stream)
@@ -61,7 +59,6 @@ def exec_main(pod_name_trimmer, acceptable_data):
def test_main(pod_name_trimmer):
-
acceptable_data = {
"python": ["2.7.5", "3.6.6", "3.8.4"],
"java": ["11.0.5", "11.0.8"],
@@ -73,7 +70,6 @@ def test_main(pod_name_trimmer):
def test_main_neg(pod_name_trimmer):
-
acceptable_data = {
"python": ["3.6.6", "3.8.4"],
"java": ["11.0.5", "11.0.8"],
diff --git a/test/security/check_versions/tests/test_verify_versions_acceptability.py b/test/security/check_versions/tests/test_verify_versions_acceptability.py
index 5e2f0d2c8..1cb931679 100644
--- a/test/security/check_versions/tests/test_verify_versions_acceptability.py
+++ b/test/security/check_versions/tests/test_verify_versions_acceptability.py
@@ -7,7 +7,6 @@ import pathlib
def exec_verify_versions_acceptability(containers):
-
config = {
"python": ["1.1.1", "2.2.2"],
"java": ["3.3.3"],
@@ -23,7 +22,6 @@ def exec_verify_versions_acceptability(containers):
def test_verify_versions_acceptability():
-
containers = [
kbvi.ContainerInfo("a", "b", "c", None, kbvi.ContainerVersions([], [])),
kbvi.ContainerInfo(
@@ -37,7 +35,6 @@ def test_verify_versions_acceptability():
def test_verify_versions_acceptability_neg_1():
-
containers = [
kbvi.ContainerInfo("a", "b", "c", None, kbvi.ContainerVersions(["3.3.3"], []))
]
@@ -48,7 +45,6 @@ def test_verify_versions_acceptability_neg_1():
def test_verify_versions_acceptability_neg_2():
-
containers = [
kbvi.ContainerInfo("a", "b", "c", None, kbvi.ContainerVersions([], ["1.1.1"]))
]
diff --git a/test/security/check_versions/tox.ini b/test/security/check_versions/tox.ini
index 703ee280a..d2a007160 100644
--- a/test/security/check_versions/tox.ini
+++ b/test/security/check_versions/tox.ini
@@ -1,16 +1,18 @@
[tox]
-envlist = black, pylint
+envlist = black, pylint, pytest
skipsdist = true
[testenv]
basepython = python3.8
-deps = -r{toxinidir}/env/requirements-dev.txt
+deps = -r{toxinidir}/requirements.txt
[testenv:black]
-commands = black {toxinidir}/src tests
+commands = black {toxinidir}/versions tests
+deps = black
[testenv:pylint]
-commands = pylint -d C0330,W0511 {toxinidir}/src
+commands = pylint -d C0330,W0511 {toxinidir}/versions
+deps= pylint
[testenv:pytest]
setenv = PYTHONPATH = {toxinidir}/src
diff --git a/test/security/check_versions/versions/__init__.py b/test/security/check_versions/versions/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/test/security/check_versions/versions/__init__.py
diff --git a/test/security/check_versions/src/k8s_bin_versions_inspector.py b/test/security/check_versions/versions/k8s_bin_versions_inspector.py
index f5ff53714..bd3041d63 100644
--- a/test/security/check_versions/src/k8s_bin_versions_inspector.py
+++ b/test/security/check_versions/versions/k8s_bin_versions_inspector.py
@@ -40,17 +40,26 @@ import argparse
import dataclasses
import itertools
import json
+import logging
import pathlib
import pprint
import re
import string
import sys
+from typing import Iterable, List, Optional, Pattern, Union
import tabulate
import yaml
-import cerberus
import kubernetes
+RECOMMENDED_VERSIONS_FILE = "/tmp/recommended_versions.yaml"
+WAIVER_LIST_FILE = "/tmp/versions_xfail.txt"
+
+# Logger
+logging.basicConfig()
+LOGGER = logging.getLogger("onap-versions-status-inspector")
+LOGGER.setLevel("INFO")
+
def parse_argv(argv: Optional[List[str]] = None) -> argparse.Namespace:
"""Function for parsing command line arguments.
@@ -118,19 +127,19 @@ def parse_argv(argv: Optional[List[str]] = None) -> argparse.Namespace:
"-n",
"--namespace",
help="Namespace to use to list pods."
- "If empty pods are going to be listed from all namespaces"
+ "If empty pods are going to be listed from all namespaces",
)
parser.add_argument(
"--check-istio-sidecar",
action="store_true",
- help="Add if you want to check istio sidecars also"
+ help="Add if you want to check istio sidecars also",
)
parser.add_argument(
"--istio-sidecar-name",
default="istio-proxy",
- help="Name of istio sidecar to filter out"
+ help="Name of istio sidecar to filter out",
)
parser.add_argument(
@@ -148,6 +157,13 @@ def parse_argv(argv: Optional[List[str]] = None) -> argparse.Namespace:
)
parser.add_argument(
+ "-w",
+ "--waiver",
+ type=pathlib.Path,
+ help="Path of the waiver xfail file.",
+ )
+
+ parser.add_argument(
"-V",
"--version",
action="version",
@@ -221,7 +237,7 @@ def list_all_containers(
field_selector: str,
namespace: Union[None, str],
check_istio_sidecars: bool,
- istio_sidecar_name: str
+ istio_sidecar_name: str,
) -> Iterable[ContainerInfo]:
"""Get list of all containers names.
@@ -243,6 +259,13 @@ def list_all_containers(
else:
pods = api.list_pod_for_all_namespaces(field_selector=field_selector).items
+ # Filtering to avoid testing integration or replica pods
+ pods = [
+ pod
+ for pod in pods
+ if "replica" not in pod.metadata.name and "integration" not in pod.metadata.name
+ ]
+
containers_statuses = (
(pod.metadata.namespace, pod.metadata.name, pod.status.container_statuses)
for pod in pods
@@ -276,7 +299,9 @@ def list_all_containers(
)
if not check_istio_sidecars:
- container_items = filter(lambda container: container.container != istio_sidecar_name, container_items)
+ container_items = filter(
+ lambda container: container.container != istio_sidecar_name, container_items
+ )
yield from container_items
@@ -303,8 +328,13 @@ def sync_post_namespaced_pod_exec(
or -2 if other failure occurred.
"""
+ stdout = ""
+ stderr = ""
+ error = {}
+ code = -1
+ LOGGER.debug("sync_post_namespaced_pod_exec container= %s", container.pod)
try:
- client = kubernetes.stream.stream(
+ client_stream = kubernetes.stream.stream(
api.connect_post_namespaced_pod_exec,
namespace=container.namespace,
name=container.pod,
@@ -317,33 +347,13 @@ def sync_post_namespaced_pod_exec(
_request_timeout=1.0,
_preload_content=False,
)
- except (
- kubernetes.client.rest.ApiException,
- kubernetes.client.exceptions.ApiException,
- ):
-
- if container.extra.running:
- raise
-
- return {
- "stdout": "",
- "stderr": "",
- "error": {},
- "code": -1,
- }
-
- client.run_forever(timeout=5)
-
- stdout = client.read_stdout()
- stderr = client.read_stderr()
- error = yaml.safe_load(
- client.read_channel(kubernetes.stream.ws_client.ERROR_CHANNEL)
- )
+ client_stream.run_forever(timeout=5)
+ stdout = client_stream.read_stdout()
+ stderr = client_stream.read_stderr()
+ error = yaml.safe_load(
+ client_stream.read_channel(kubernetes.stream.ws_client.ERROR_CHANNEL)
+ )
- # TODO: Is there really no better way, to check
- # execution exit code in python k8s API client?
- code = -2
- try:
code = (
0
if error["status"] == "Success"
@@ -351,7 +361,13 @@ def sync_post_namespaced_pod_exec(
if error["reason"] != "NonZeroExitCode"
else int(error["details"]["causes"][0]["message"])
)
- except:
+ except (
+ kubernetes.client.rest.ApiException,
+ kubernetes.client.exceptions.ApiException,
+ ):
+ LOGGER.debug("Discard unexpected k8s client Error..")
+ except TypeError:
+ LOGGER.debug("Type Error, no error status")
pass
return {
@@ -498,7 +514,7 @@ def gather_containers_informations(
ignore_empty: bool,
namespace: Union[None, str],
check_istio_sidecars: bool,
- istio_sidecar_name: str
+ istio_sidecar_name: str,
) -> List[ContainerInfo]:
"""Get list of all containers names.
@@ -516,14 +532,20 @@ def gather_containers_informations(
List of initialized objects for containers in k8s cluster.
"""
- containers = list(list_all_containers(api, field_selector, namespace,
- check_istio_sidecars, istio_sidecar_name))
+ containers = list(
+ list_all_containers(
+ api, field_selector, namespace, check_istio_sidecars, istio_sidecar_name
+ )
+ )
+ LOGGER.info("List of containers: %s", containers)
# TODO: This loop should be parallelized
for container in containers:
+ LOGGER.info("Container -----------------> %s", container)
python_versions = determine_versions_of_python(api, container)
java_versions = determine_versions_of_java(api, container)
container.versions = ContainerVersions(python_versions, java_versions)
+ LOGGER.info("Container versions: %s", container.versions)
if ignore_empty:
containers = [c for c in containers if c.versions.python or c.versions.java]
@@ -635,14 +657,18 @@ def generate_and_handle_output(
"pprint": generate_output_pprint,
"json": generate_output_json,
}
+ LOGGER.debug("output_generators: %s", output_generators)
output = output_generators[output_format](containers)
if output_file:
- output_file.write_text(output)
+ try:
+ output_file.write_text(output)
+ except AttributeError:
+ LOGGER.error("Not possible to write_text")
if not quiet:
- print(output)
+ LOGGER.info(output)
def verify_versions_acceptability(
@@ -662,38 +688,32 @@ def verify_versions_acceptability(
if not acceptable:
return 0
+ try:
+ acceptable.is_file()
+ except AttributeError:
+ LOGGER.error("No acceptable file found")
+ return -1
+
if not acceptable.is_file():
raise FileNotFoundError(
"File with configuration for acceptable does not exists!"
)
- schema = {
- "python": {"type": "list", "schema": {"type": "string"}},
- "java": {"type": "list", "schema": {"type": "string"}},
- }
-
- validator = cerberus.Validator(schema)
-
with open(acceptable) as stream:
data = yaml.safe_load(stream)
- if not validator.validate(data):
- raise cerberus.SchemaError(
- "Schema of file with configuration for acceptable is not valid."
- )
-
- python_acceptable = data.get("python", [])
- java_acceptable = data.get("java", [])
+ python_acceptable = data.get("python3", [])
+ java_acceptable = data.get("java11", [])
python_not_acceptable = [
- (container, "python", version)
+ (container, "python3", version)
for container in containers
for version in container.versions.python
if version not in python_acceptable
]
java_not_acceptable = [
- (container, "java", version)
+ (container, "java11", version)
for container in containers
for version in container.versions.java
if version not in java_acceptable
@@ -705,7 +725,7 @@ def verify_versions_acceptability(
if quiet:
return 1
- print("List of not acceptable versions")
+ LOGGER.error("List of not acceptable versions")
pprint.pprint(python_not_acceptable)
pprint.pprint(java_not_acceptable)
@@ -728,8 +748,12 @@ def main(argv: Optional[List[str]] = None) -> str:
api.api_client.configuration.debug = args.debug
containers = gather_containers_informations(
- api, args.field_selector, args.ignore_empty, args.namespace,
- args.check_istio_sidecar, args.istio_sidecar_name
+ api,
+ args.field_selector,
+ args.ignore_empty,
+ args.namespace,
+ args.check_istio_sidecar,
+ args.istio_sidecar_name,
)
generate_and_handle_output(
diff --git a/test/security/check_versions/versions/k8s_bin_versions_inspector_test_case.py b/test/security/check_versions/versions/k8s_bin_versions_inspector_test_case.py
new file mode 100644
index 000000000..87516cb60
--- /dev/null
+++ b/test/security/check_versions/versions/k8s_bin_versions_inspector_test_case.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python3
+
+# COPYRIGHT NOTICE STARTS HERE
+#
+# Copyright 2020 Samsung Electronics Co., Ltd.
+#
+# 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.
+#
+# COPYRIGHT NOTICE ENDS HERE
+
+import logging
+import pathlib
+import time
+import os
+import wget
+from kubernetes import client, config
+from xtesting.core import testcase # pylint: disable=import-error
+
+import versions.reporting as Reporting
+from versions.k8s_bin_versions_inspector import (
+ gather_containers_informations,
+ generate_and_handle_output,
+ verify_versions_acceptability,
+)
+
+RECOMMENDED_VERSIONS_FILE = "/tmp/recommended_versions.yaml"
+WAIVER_LIST_FILE = "/tmp/versions_xfail.txt"
+
+# Logger
+logging.basicConfig()
+LOGGER = logging.getLogger("onap-versions-status-inspector")
+LOGGER.setLevel("INFO")
+
+
+class Inspector(testcase.TestCase):
+ """Inspector CLass."""
+
+ def __init__(self, **kwargs):
+ """Init the testcase."""
+ if "case_name" not in kwargs:
+ kwargs["case_name"] = "check_versions"
+ super().__init__(**kwargs)
+
+ version = os.getenv("ONAP_VERSION", "master")
+ base_url = "https://git.onap.org/integration/seccom/plain"
+
+ self.namespace = "onap"
+ # if no Recommended file found, download it
+ if pathlib.Path(RECOMMENDED_VERSIONS_FILE).is_file():
+ self.acceptable = pathlib.Path(RECOMMENDED_VERSIONS_FILE)
+ else:
+ self.acceptable = wget.download(
+ base_url + "/recommended_versions.yaml?h=" + version,
+ out=RECOMMENDED_VERSIONS_FILE,
+ )
+ self.output_file = "/tmp/versions.json"
+ # if no waiver file found, download it
+ if pathlib.Path(WAIVER_LIST_FILE).is_file():
+ self.waiver = pathlib.Path(WAIVER_LIST_FILE)
+ else:
+ self.waiver = wget.download(
+ base_url + "/waivers/versions/versions_xfail.txt?h=" + version,
+ out=WAIVER_LIST_FILE,
+ )
+ self.result = 0
+ self.start_time = None
+ self.stop_time = None
+
+ def run(self):
+ """Execute the version Inspector."""
+ self.start_time = time.time()
+ config.load_kube_config()
+ api = client.CoreV1Api()
+
+ field_selector = "metadata.namespace==onap"
+
+ containers = gather_containers_informations(api, field_selector, True)
+ LOGGER.info("gather_containers_informations")
+ LOGGER.info(containers)
+ LOGGER.info("---------------------------------")
+
+ generate_and_handle_output(
+ containers, "json", pathlib.Path(self.output_file), True
+ )
+ LOGGER.info("generate_and_handle_output in %s", self.output_file)
+ LOGGER.info("---------------------------------")
+
+ code = verify_versions_acceptability(containers, self.acceptable, True)
+ LOGGER.info("verify_versions_acceptability")
+ LOGGER.info(code)
+ LOGGER.info("---------------------------------")
+
+ # Generate reporting
+ test = Reporting.OnapVersionsReporting(result_file=self.output_file)
+ LOGGER.info("Prepare reporting")
+ self.result = test.generate_reporting(self.output_file)
+ LOGGER.info("Reporting generated")
+
+ self.stop_time = time.time()
+ if self.result >= 90:
+ return testcase.TestCase.EX_OK
+ return testcase.TestCase.EX_TESTCASE_FAILED
+
+ def set_namespace(self, namespace):
+ """Set namespace."""
+ self.namespace = namespace
diff --git a/test/security/check_versions/versions/reporting.py b/test/security/check_versions/versions/reporting.py
new file mode 100644
index 000000000..43ef26db8
--- /dev/null
+++ b/test/security/check_versions/versions/reporting.py
@@ -0,0 +1,265 @@
+#!/usr/bin/env python3
+
+# Copyright 2020 Orange, Ltd.
+#
+# 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.
+#
+"""
+Generate result page
+"""
+import logging
+import pathlib
+import json
+from dataclasses import dataclass
+import os
+import statistics
+import wget
+import yaml
+
+from packaging.version import Version
+
+from jinja2 import ( # pylint: disable=import-error
+ Environment,
+ select_autoescape,
+ PackageLoader,
+)
+
+# Logger
+LOG_LEVEL = "INFO"
+logging.basicConfig()
+LOGGER = logging.getLogger("onap-versions-status-reporting")
+LOGGER.setLevel(LOG_LEVEL)
+
+REPORTING_FILE = "/var/lib/xtesting/results/versions_reporting.html"
+# REPORTING_FILE = "/tmp/versions_reporting.html"
+RESULT_FILE = "/tmp/versions.json"
+RECOMMENDED_VERSIONS_FILE = "/tmp/recommended_versions.yaml"
+WAIVER_LIST_FILE = "/tmp/versions_xfail.txt"
+
+
+@dataclass
+class TestResult:
+ """Test results retrieved from xtesting."""
+
+ pod_name: str
+ container: str
+ image: str
+ python_version: str
+ python_status: int
+ java_version: str
+ java_status: int
+
+
+@dataclass
+class SerieResult:
+ """Serie of tests."""
+
+ serie_id: str
+ success_rate: int = 0
+ min: int = 0
+ max: int = 0
+ mean: float = 0.0
+ median: float = 0.0
+ nb_occurences: int = 0
+
+
+class OnapVersionsReporting:
+ """Build html summary page."""
+
+ def __init__(self, result_file) -> None:
+ """Initialization of the report."""
+ version = os.getenv("ONAP_VERSION", "master")
+ base_url = "https://git.onap.org/integration/seccom/plain"
+ if pathlib.Path(WAIVER_LIST_FILE).is_file():
+ self._waiver_file = pathlib.Path(WAIVER_LIST_FILE)
+ else:
+ self._waiver_file = wget.download(
+ base_url + "/waivers/versions/versions_xfail.txt?h=" + version,
+ out=WAIVER_LIST_FILE,
+ )
+ if pathlib.Path(RECOMMENDED_VERSIONS_FILE).is_file():
+ self._recommended_versions_file = pathlib.Path(RECOMMENDED_VERSIONS_FILE)
+ else:
+ self._recommended_versions_file = wget.download(
+ base_url + "/recommended_versions.yaml?h=" + version,
+ out=RECOMMENDED_VERSIONS_FILE,
+ )
+
+ def get_versions_scan_results(self, result_file, waiver_list):
+ """Get all the versions from the scan."""
+ testresult = []
+ # Get the recommended version list for java and python
+ min_java_version = self.get_recommended_version(
+ RECOMMENDED_VERSIONS_FILE, "java11"
+ )
+ min_python_version = self.get_recommended_version(
+ RECOMMENDED_VERSIONS_FILE, "python3"
+ )
+
+ LOGGER.info("Min Java recommended version: %s", min_java_version)
+ LOGGER.info("Min Python recommended version: %s", min_python_version)
+
+ with open(result_file) as json_file:
+ data = json.load(json_file)
+ LOGGER.info("Number of pods: %s", len(data))
+ for component in data:
+ if component["container"] not in waiver_list:
+ testresult.append(
+ TestResult(
+ pod_name=component["pod"],
+ container=component["container"],
+ image=component["extra"]["image"],
+ python_version=component["versions"]["python"],
+ java_version=component["versions"]["java"],
+ python_status=self.get_version_status(
+ component["versions"]["python"], min_python_version[0]
+ ),
+ java_status=self.get_version_status(
+ component["versions"]["java"], min_java_version[0]
+ ),
+ )
+ )
+ LOGGER.info("Nb of pods (after waiver filtering) %s", len(testresult))
+ return testresult
+
+ @staticmethod
+ def get_version_status(versions, min_version):
+ """Based on the min version set the status of the component version."""
+ # status_code
+ # 0: only recommended version found
+ # 1: recommended version found but not alone
+ # 2: recommended version not found but not far
+ # 3: recommended version not found but not far but not alone
+ # 4: recommended version not found
+ # we assume that versions are given accordign to usual java way
+ # X.Y.Z
+ LOGGER.debug("Version = %s", versions)
+ LOGGER.debug("Min Version = %s", min_version)
+ nb_versions_found = len(versions)
+ status_code = -1
+ LOGGER.debug("Nb versions found :%s", nb_versions_found)
+ # if no version found retrieved -1
+ if nb_versions_found > 0:
+ for version in versions:
+ clean_version = Version(version.replace("_", "."))
+ min_version_ok = str(min_version)
+
+ if clean_version >= Version(min_version_ok):
+ if nb_versions_found < 2:
+ status_code = 0
+ else:
+ status_code = 2
+ elif clean_version.major >= Version(min_version_ok).major:
+ if nb_versions_found < 2:
+ status_code = 1
+ else:
+ status_code = 3
+ else:
+ status_code = 4
+ LOGGER.debug("Version status code = %s", status_code)
+ return status_code
+
+ @staticmethod
+ def get_recommended_version(recommended_versions_file, component):
+ """Retrieve data from the json file."""
+ with open(recommended_versions_file) as stream:
+ data = yaml.safe_load(stream)
+ try:
+ recommended_version = data[component]["recommended_versions"]
+ except KeyError:
+ recommended_version = None
+ return recommended_version
+
+ @staticmethod
+ def get_waiver_list(waiver_file_path):
+ """Get the waiver list."""
+ pods_to_be_excluded = []
+ with open(waiver_file_path) as waiver_list:
+ for line in waiver_list:
+ line = line.strip("\n")
+ line = line.strip("\t")
+ if not line.startswith("#"):
+ pods_to_be_excluded.append(line)
+ return pods_to_be_excluded
+
+ @staticmethod
+ def get_score(component_type, scan_res):
+ # Look at the java and python results
+ # 0 = recommended version
+ # 1 = acceptable version
+ nb_good_versions = 0
+ nb_results = 0
+
+ for res in scan_res:
+ if component_type == "java":
+ if res.java_status >= 0:
+ nb_results += 1
+ if res.java_status < 2:
+ nb_good_versions += 1
+ elif component_type == "python":
+ if res.python_status >= 0:
+ nb_results += 1
+ if res.python_status < 2:
+ nb_good_versions += 1
+ try:
+ return round(nb_good_versions * 100 / nb_results, 1)
+ except ZeroDivisionError:
+ LOGGER.error("Impossible to calculate the success rate")
+ return 0
+
+ def generate_reporting(self, result_file):
+ """Generate HTML reporting page."""
+ LOGGER.info("Generate versions HTML report.")
+
+ # Get the waiver list
+ waiver_list = self.get_waiver_list(self._waiver_file)
+ LOGGER.info("Waiver list: %s", waiver_list)
+
+ # Get the Versions results
+ scan_res = self.get_versions_scan_results(result_file, waiver_list)
+
+ LOGGER.info("scan_res: %s", scan_res)
+
+ # Evaluate result
+ status_res = {"java": 0, "python": 0}
+ for component_type in "java", "python":
+ status_res[component_type] = self.get_score(component_type, scan_res)
+
+ LOGGER.info("status_res: %s", status_res)
+
+ # Calculate the average score
+ numbers = [status_res[key] for key in status_res]
+ mean_ = statistics.mean(numbers)
+
+ # Create reporting page
+ jinja_env = Environment(
+ autoescape=select_autoescape(["html"]),
+ loader=PackageLoader("onap_check_versions"),
+ )
+ page_info = {
+ "title": "ONAP Integration versions reporting",
+ "success_rate": status_res,
+ "mean": mean_,
+ }
+ jinja_env.get_template("versions.html.j2").stream(
+ info=page_info, data=scan_res
+ ).dump("{}".format(REPORTING_FILE))
+
+ return mean_
+
+
+if __name__ == "__main__":
+ test = OnapVersionsReporting(
+ RESULT_FILE, WAIVER_LIST_FILE, RECOMMENDED_VERSIONS_FILE
+ )
+ test.generate_reporting(RESULT_FILE)
diff --git a/test/security/check_versions/versions/templates/base.html.j2 b/test/security/check_versions/versions/templates/base.html.j2
new file mode 100644
index 000000000..025c0ad25
--- /dev/null
+++ b/test/security/check_versions/versions/templates/base.html.j2
@@ -0,0 +1,232 @@
+{% macro color(failing, total) %}
+{% if failing == 0 %}
+is-success
+{% else %}
+{% if (failing / total) <= 0.1 %}
+is-warning
+{% else %}
+is-danger
+{% endif %}
+{% endif %}
+{% endmacro %}
+
+{% macro percentage(failing, total) %}
+{{ ((total - failing) / total) | round }}
+{% endmacro %}
+
+{% macro statistic(resource_name, failing, total) %}
+{% set success = total - failing %}
+<div class="level-item has-text-centered">
+ <div>
+ <p class="heading">{{ resource_name | capitalize }}</p>
+ <p class="title">{{ success }}/{{ total }}</p>
+ <progress class="progress {{ color(failing, total) }}" value="{{ success }}" max="{{ total }}">{{ percentage(failing, total) }}</progress>
+ </div>
+ </div>
+{% endmacro %}
+
+{% macro pods_table(pods) %}
+<div id="pods" class="table-container">
+ <table class="table is-fullwidth is-striped is-hoverable">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Ready</th>
+ <th>Status</th>
+ <th>Reason</th>
+ <th>Restarts</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for pod in pods %}
+ <tr>
+ <td><a href="./pod-{{ pod.name }}.html" title="{{ pod.name }}">{{ pod.k8s.metadata.name }}</a></td>
+ {% if pod.init_done %}
+ <td>{{ pod.running_containers }}/{{ (pod.containers | length) }}</td>
+ {% else %}
+ <td>Init:{{ pod.runned_init_containers }}/{{ (pod.init_containers | length) }}</td>
+ {% endif %}
+ <td>{{ pod.k8s.status.phase }}</td>
+ <td>{{ pod.k8s.status.reason }}</td>
+ {% if pod.init_done %}
+ <td>{{ pod.restart_count }}</td>
+ {% else %}
+ <td>{{ pod.init_restart_count }}</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</div>
+{% endmacro %}
+
+{% macro key_value_description_list(title, dict) %}
+<dt><strong>{{ title | capitalize }}:</strong></dt>
+<dd>
+ {% if dict %}
+ {% for key, value in dict.items() %}
+ {% if loop.first %}
+ <dl>
+ {% endif %}
+ <dt>{{ key }}:</dt>
+ <dd>{{ value }}</dd>
+ {% if loop.last %}
+ </dl>
+ {% endif %}
+ {% endfor %}
+ {% endif %}
+</dd>
+{% endmacro %}
+
+{% macro description(k8s) %}
+<div class="container">
+ <h1 class="title is-1">Description</h1>
+ <div class="content">
+ <dl>
+ {% if k8s.spec.type %}
+ <dt><strong>Type:</strong></dt>
+ <dd>{{ k8s.spec.type }}</dd>
+ {% if (k8s.spec.type | lower) == "clusterip" %}
+ <dt><strong>Headless:</strong></dt>
+ <dd>{% if (k8s.spec.cluster_ip | lower) == "none" %}Yes{% else %}No{% endif %}</dd>
+ {% endif %}
+ {% endif %}
+ {{ key_value_description_list('Labels', k8s.metadata.labels) | indent(width=6) }}
+ {{ key_value_description_list('Annotations', k8s.metadata.annotations) | indent(width=6) }}
+ {% if k8s.spec.selector %}
+ {% if k8s.spec.selector.match_labels %}
+ {{ key_value_description_list('Selector', k8s.spec.selector.match_labels) | indent(width=6) }}
+ {% else %}
+ {{ key_value_description_list('Selector', k8s.spec.selector) | indent(width=6) }}
+ {% endif %}
+ {% endif %}
+ {% if k8s.phase %}
+ <dt><strong>Status:</strong></dt>
+ <dd>{{ k8s.phase }}</dd>
+ {% endif %}
+ {% if k8s.metadata.owner_references %}
+ <dt><strong>Controlled By:</strong></dt>
+ <dd>{{ k8s.metadata.owner_references[0].kind }}/{{ k8s.metadata.owner_references[0].name }}</dd>
+ {% endif %}
+ </dl>
+ </div>
+</div>
+{% endmacro %}
+
+{% macro pods_container(pods, parent, has_title=True) %}
+<div class="container">
+ {% if has_title %}
+ <h1 class="title is-1">Pods</h1>
+ {% endif %}
+ {% if (pods | length) > 0 %}
+ {{ pods_table(pods) | indent(width=2) }}
+ {% else %}
+ <div class="notification is-warning">{{ parent }} has no pods!</div>
+ {% endif %}
+</div>
+{% endmacro %}
+
+{% macro two_level_breadcrumb(title, name) %}
+<section class="section">
+ <div class="container">
+ <nav class="breadcrumb" aria-label="breadcrumbs">
+ <ul>
+ <li><a href="./index.html">Summary</a></li>
+ <li class="is-active"><a href="#" aria-current="page">{{ title | capitalize }} {{ name }}</a></li>
+ </ul>
+ </nav>
+ </div>
+</section>
+{% endmacro %}
+
+{% macro pod_parent_summary(title, name, failed_pods, pods) %}
+{{ summary(title, name, [{'title': 'Pod', 'failing': failed_pods, 'total': (pods | length)}]) }}
+{% endmacro %}
+
+{% macro number_ok(number, none_value, total=None) %}
+{% if number %}
+{% if total and number < total %}
+<span class="tag is-warning">{{ number }}</span>
+{% else %}
+{{ number }}
+{% endif %}
+{% else %}
+<span class="tag is-warning">{{ none_value }}</span>
+{% endif %}
+{% endmacro %}
+
+{% macro summary(title, name, statistics) %}
+<section class="hero is-light">
+ <div class="hero-body">
+ <div class="container">
+ <h1 class="title is-1">
+ {{ title | capitalize }} {{ name }} Summary
+ </h1>
+ <nav class="level">
+ {% for stat in statistics %}
+ {% if stat.total > 0 %}
+ {{ statistic(stat.title, stat.failing, stat.total) | indent(width=8) }}
+ {% endif %}
+ {% endfor %}
+ </nav>
+ </div>
+ </div>
+</section>
+{% endmacro %}
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+ <title>Tests results - {% block title %}{% endblock %}</title>
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.0/css/bulma.min.css">
+ <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
+ {% block more_head %}{% endblock %}
+ </head>
+ <body>
+ <nav class="navbar" role="navigation" aria-label="main navigation">
+ <div class="navbar-brand">
+ <a class="navbar-item" href="https://www.onap.org">
+ <img src="https://www.onap.org/wp-content/uploads/sites/20/2017/02/logo_onap_2017.png" width="234" height="50">
+ </a>
+
+ <a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
+ <span aria-hidden="true"></span>
+ <span aria-hidden="true"></span>
+ <span aria-hidden="true"></span>
+ </a>
+ </div>
+
+ <div id="navbarBasicExample" class="navbar-menu">
+ <div class="navbar-start">
+ <a class="navbar-item">
+ Summary
+ </a>
+ </div>
+ </div>
+ </nav>
+
+ {% block content %}{% endblock %}
+
+ <footer class="footer">
+ <div class="container">
+ <div class="columns">
+ <div class="column">
+ <p class="has-text-grey-light">
+ <a href="https://bulma.io/made-with-bulma/">
+ <img src="https://bulma.io/images/made-with-bulma.png" alt="Made with Bulma" width="128" height="24">
+ </a>
+ </div>
+ <div class="column">
+ <a class="has-text-grey" href="https://gitlab.com/Orange-OpenSource/lfn/tools/kubernetes-status" style="border-bottom: 1px solid currentColor;">
+ Improve this page on Gitlab
+ </a>
+ </p>
+ </div>
+ </div>
+ </div>
+ </footer>
+ </body>
+</html>
+
diff --git a/test/security/check_versions/versions/templates/versions.html.j2 b/test/security/check_versions/versions/templates/versions.html.j2
new file mode 100644
index 000000000..4860a72da
--- /dev/null
+++ b/test/security/check_versions/versions/templates/versions.html.j2
@@ -0,0 +1,85 @@
+{% extends "base.html.j2" %}
+{% block title %}ONAPTEST Bench{% endblock %}
+
+{% block content %}
+<h1 class="title is-1">{{ info.title }}</h1>
+
+<div class="container">
+
+<article class="message">
+<div class="message-header">
+ <p>Results</p>
+</div>
+<div class="message-body">
+SECCOM recommended versions (global success rate: {{ info.mean }}):
+ <ul>
+ <li>Java: {{ info.success_rate.java }}% </li>
+ <li>Python: {{ info.success_rate.python }}%</li>
+ </ul>
+</div>
+</article>
+
+<article class="message">
+ <div class="message-header">
+ <p>Legend</p>
+ </div>
+ <div class="message-body">
+ <div class="has-background-success">SECCOM recommended version</div>
+ <div class="has-background-success-light">Not the recommended version but at least the major version</div>
+ <div class="has-background-warning-light">Ambiguous versions but at least 1 is the SECCOM recommended version</div>
+ <div class="has-background-warning">Ambiguous versions but at least 1 is the major recommended version</div>
+ <div class="has-background-danger">Wrong Versions</div>
+ </div>
+</article>
+<br>
+
+<h2 class="title is-1">JAVA versions</h2>
+
+<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
+ <thead>
+ <tr>
+ <th>Component</th>
+ <th>Versions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for component in data %}
+ <tr {% if component.java_status == 4 %} class="has-background-danger" {%elif component.java_status == 0 %} class="has-background-success" {%elif component.java_status == 1 %} class="has-background-success-light" {%elif component.java_status == 2 %} class="has-background-warning-light" {%elif component.java_status == 3 %} class="has-background-warning" {% endif %}>
+
+ {% if component.java_version is defined and component.java_version|length > 0 %}
+ <td>{{ component.container }}</td>
+ <td>{{ component.java_version}}</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</div>
+<br>
+
+<div class="container">
+<h2 class="title is-1">Python versions</h2>
+
+<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth">
+ <thead>
+ <tr>
+ <th>Component</th>
+ <th>Versions</th>
+ </tr>
+ </thead>
+ <tbody>
+ {% for component in data %}
+ <tr {% if component.python_status == 4 %} class="has-background-danger" {%elif component.python_status == 0 %} class="has-background-success" {%elif component.python_status == 1 %} class="has-background-success-light" {%elif component.python_status == 2 %} class="has-background-warning-light" {%elif component.python_status == 3 %} class="has-background-warning" {% endif %}>
+ {% if component.python_version is defined and component.python_version|length > 0 %}
+ <td>{{ component.container }}</td>
+ <td>{{ component.python_version}}</td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </tbody>
+ </table>
+</div>
+
+{% endblock %}
+</div>
+</section>