aboutsummaryrefslogtreecommitdiffstats
path: root/doc/generate_docker_version.py
diff options
context:
space:
mode:
Diffstat (limited to 'doc/generate_docker_version.py')
-rw-r--r--doc/generate_docker_version.py437
1 files changed, 437 insertions, 0 deletions
diff --git a/doc/generate_docker_version.py b/doc/generate_docker_version.py
new file mode 100644
index 0000000..fa13229
--- /dev/null
+++ b/doc/generate_docker_version.py
@@ -0,0 +1,437 @@
+#!/usr/bin/python
+#
+# This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+'''Docker version comparison generator.'''
+
+import argparse
+import logging
+import json
+import re
+from collections import Counter
+from dataclasses import dataclass
+import os
+import requests
+from bs4 import BeautifulSoup # sudo apt-get install python3-bs4 if pip doesn't work
+from deepdiff import DeepDiff
+from jinja2 import Environment, FileSystemLoader, select_autoescape
+
+PROXY = {}
+# PROXY = {'http': 'socks5h://127.0.0.1:8080',
+# 'https': 'socks5h://127.0.0.1:8080'}
+
+
+BASE_URL = "https://logs.onap.org/onap-integration/daily/"
+ONAP_VERSION = 'master'
+component_versions = {}
+versions_to_be_compared_with_master = ['jakarta', 'istanbul', 'honolulu_MR1', 'honolulu', 'guilin_MR1']
+VERSION_URL = []
+END_URL = "infrastructure-healthcheck/k8s/kubernetes-status/onap_versions.json"
+
+# first date where file is present for each version
+ONAP_RELEASE_DATE = {
+ "jakarta": "onap-daily-dt-oom-jakarta/2022-07/19_03-53/",
+ "istanbul": "onap_daily_pod4_istanbul/2021-11/15_03-55/",
+ "honolulu": "onap_daily_pod4_honolulu/2021-05/28_01-00/",
+ "honolulu_MR1": "onap_daily_pod4_honolulu/2021-08/12_20-15/",
+ "guilin_MR1": "onap_daily_pod4_guilin/2021-05/27_03-23/"
+}
+
+# Logger
+logging.basicConfig()
+LOGGER = logging.getLogger("Docker-Version-Status")
+LOGGER.setLevel("INFO")
+
+# Arg
+PARSER = argparse.ArgumentParser()
+PARSER.add_argument('-t', '--test', action='store_true' )
+# PARSER.add_argument('-t', '--test', default=True, type=bool, help='Execute with test dataset')
+ARGS = PARSER.parse_args()
+
+DATA_VERSIONS = []
+
+@dataclass
+class ComponentVersion:
+ """A component version."""
+ component: str
+ container: str
+ image: str
+ current_version: str
+ other_version: {}
+ status: int
+
+# ***********************************************************************
+# functions
+# ***********************************************************************
+def get_months(url):
+ """Load and parse list of months"""
+ months = []
+ response_months = requests.get(url, proxies=PROXY)
+ soup = BeautifulSoup(response_months.text, "lxml")
+
+ for link in soup.find_all('a'):
+ pattern = bool(re.match("[0-9]{4}-[0-9]{2}", link.contents[0]))
+ if pattern:
+ months.append(link.contents[0])
+ return months
+
+def get_days(url):
+ """Load and parse list of days"""
+ days = []
+ response_days = requests.get(url, proxies=PROXY)
+ soup = BeautifulSoup(response_days.text, "lxml")
+
+ for link in soup.find_all('a'):
+ pattern = bool(
+ re.match("[0-9]{2}_[0-9]{2}-[0-9]{2}", link.contents[0]))
+ if pattern:
+ days.append(link.contents[0])
+ return days
+
+def get_diff_index(change):
+ """Search Index of the Diff table to get the container related to the detected diff."""
+ local_index = re.findall(r"\[([0-9]+)\]", change)
+ return int(local_index[0])
+
+def is_it_a_simple_version_change(change, delta_values_changed):
+ """Detect if it is a simple version change or a subtitution."""
+ # retrieve the change index
+ change_index = get_diff_index(change)
+ # count the number of occurence
+ # if 1 => simplie version change
+ return str(delta_values_changed).count("root[" + str(change_index) + "]") < 2
+
+def get_component_status(container, delta_versions, version_master, other_version):
+ """Get component status."""
+ LOGGER.debug(container)
+ container_status = "unchanged"
+ # Look in values_changed
+ for change_type in ['values_changed', 'iterable_item_added', 'iterable_item_removed']:
+ # We test the change type as they may not occur, or only 1 type is possible
+ if change_type in delta_versions:
+ # Dive into the change found
+ for change in delta_versions[change_type]:
+ if change_type == 'values_changed':
+ # 2 cases
+ # - a simple version change for the same component (only version)
+ # in this case only the other_version shall be changed
+ # - a substitution (version, container, image,...)
+ # in this case a new component must be added and another removed
+ # it may be a little bit misleading
+ # as it could courrespond to an offset (not really a new component)
+ if is_it_a_simple_version_change(change, delta_versions[change_type]):
+ # simple replacement case
+ ## change_index = int(change[5:-12])
+ change_index = get_diff_index(change)
+ if container == other_version[change_index]['container']:
+ LOGGER.debug("Component version change: %s",
+ other_version[change_index]['container'])
+ container_status = "version_changed"
+ else:
+ if 'container' in change:
+ ## change_index = int(change[5:-14])
+ change_index = get_diff_index(change)
+ # substitution case
+ for substitution_change in delta_versions['values_changed']:
+ if str(change_index) in substitution_change:
+ LOGGER.debug("From %s to %s",
+ delta_versions['values_changed'][substitution_change]['old_value'],
+ delta_versions['values_changed'][substitution_change]['new_value'])
+ if container == delta_versions['values_changed'][substitution_change]['old_value']:
+ container_status = "component_removed"
+ if container == delta_versions['values_changed'][substitution_change]['new_value']:
+ container_status = "component_added"
+ elif change_type == 'iterable_item_added':
+ ## change_index = int(change[5:-1])
+ change_index = get_diff_index(change)
+ LOGGER.debug(
+ "New component added: %s", version_master[change_index]['container'])
+ if container == version_master[change_index]['container']:
+ container_status = "component_added"
+ elif change_type == 'iterable_item_removed':
+ ## change_index = int(change[5:-1])
+ change_index = get_diff_index(change)
+ LOGGER.debug(
+ "Component removed: %s", other_version[change_index]['container'])
+ if container == other_version[change_index]['container']:
+ container_status = "component_removed"
+ return container_status
+
+def get_old_version_component(container, component_versions):
+ """Retrieve the version of an old container."""
+ container_version = "unknown"
+ for component in component_versions:
+ if component['container'] == container:
+ return component['version']
+ return container_version
+
+def get_removed_container_list(delta_versions, component_versions, master_container):
+ """Retrieve the removed containers."""
+ removed_container_list = []
+ # Look in the removed list
+ try:
+ for change in delta_versions['iterable_item_removed']:
+ removed_container_list.append(component_versions[get_diff_index(change)])
+ except KeyError:
+ LOGGER.info("No Item in the removed section, look at the possible substitution")
+
+ # Consider the subsitution case
+ for delta in delta_versions['values_changed']:
+ if "container" in delta:
+ index_container = get_diff_index(delta)
+ removed_container_list.append(component_versions[index_container])
+ return removed_container_list
+
+def get_data_version_container_index(container, dataset):
+ """Get the index of a container from a dataset."""
+ for data in dataset:
+ if data.container == container:
+ return dataset.index(data)
+ return -1
+
+def get_json_master_components():
+ """Retrieve the json master description from LF backend."""
+ local_url = BASE_URL + "onap_daily_pod4_master/"
+ months_with_results = get_months(local_url)
+ LOGGER.debug("months_with_results: %s", months_with_results)
+ month = months_with_results[-1]
+
+ for day in get_days(local_url + month):
+ response_day = requests.get(local_url + month + day + END_URL, proxies=PROXY)
+ if response_day.status_code == 404:
+ LOGGER.debug("%s : does not exist", local_url + month + day + END_URL)
+ else:
+ version_infos = {"month": month,
+ "day": day,
+ "file": local_url + month + day + END_URL}
+ VERSION_URL.append(version_infos)
+
+ # load latest version json from LFN backend
+ version_file = VERSION_URL[-1]["file"]
+ LOGGER.debug(version_file)
+ # retrieve the file
+ response_latest = requests.get(version_file, proxies=PROXY)
+ return json.loads(response_latest.text)
+
+def get_json_version_components(version):
+ """Retrieve the versions of the component from LF Backend."""
+ local_url = BASE_URL + ONAP_RELEASE_DATE[version] + END_URL
+ local_response = requests.get(local_url, proxies=PROXY)
+ return json.loads(local_response.text)
+
+def compare_func(x, y, level=None):
+ try:
+ res = x["container"] == y["container"] and \
+ x["component"] == y["component"]
+ return res
+ except Exception:
+ raise CannotCompare()
+
+
+LOGGER.info("*********************************************************************")
+LOGGER.info("*********************************************************************")
+LOGGER.info("******************* Retrieve Raw Data ***************")
+LOGGER.info("*********************************************************************")
+LOGGER.info("*********************************************************************")
+MONTHS = ['2021-08/']
+LOGGER.info("Retrieve the Raw Data.")
+
+# Retrieve the last available version file
+# - we fist list the url of all the daily results on the given version
+# - we check that the onap_versions.json exist
+# - we take the last existing one VERSION_URL[-1]
+# - we download this particular file
+
+
+# Master is the ref
+if ARGS.test:
+ with open('./artifacts/versions/test_master.json') as json_file:
+ LATEST_VERSION = json.load(json_file)
+else:
+ LATEST_VERSION = get_json_master_components()
+CLEAN_LATEST_VERSION = [k for j, k in enumerate(
+ LATEST_VERSION) if k not in LATEST_VERSION[j + 1:]]
+SORTED_MASTER = sorted(CLEAN_LATEST_VERSION, key=lambda k: k['container'])
+LOGGER.info("Versions of the latest run ================> %s", SORTED_MASTER)
+
+for version in versions_to_be_compared_with_master:
+ # Retrieve versions of official release (release or maintenance release)
+ LOCAL_PATH = "./artifacts/versions/test_" + version + ".json"
+ if ARGS.test:
+ with open(LOCAL_PATH) as json_file:
+ local_json_file = json.load(json_file)
+ else:
+ local_json_file = get_json_version_components(version)
+
+ # remove duplicates
+ local_clean = [k for j, k in enumerate(
+ local_json_file) if k not in local_json_file[j + 1:]]
+ # sort
+ component_versions[version] = sorted(
+ local_clean, key=lambda k: k['container'])
+ LOGGER.debug("Versions of the release run %s", version)
+ LOGGER.debug("================> %s", component_versions[version])
+
+# # Post processing on the loaded versions
+# # we got
+# # - the last version
+# # - the Honolulu versions
+# # - the Guilin MR versions
+# # we need first to cleanup (avoid duplicate)
+# # then we need to build the object for the jinja template
+
+LOGGER.info("*********************************************************************")
+LOGGER.info("*********************************************************************")
+LOGGER.info("******************* Process the Data ***************")
+LOGGER.info("*********************************************************************")
+LOGGER.info("*********************************************************************")
+
+NB_ITERATION_DATASET = 0
+# we compare Master with a bunch of official release or maintenance releases
+for version in versions_to_be_compared_with_master:
+ LOGGER.info("----------------------------------------------------")
+ LOGGER.info("----------------------------------------------------")
+ LOGGER.info("-----Comparison Master versus %s --------", version)
+ LOGGER.info("----------------------------------------------------")
+ LOGGER.info("----------------------------------------------------")
+
+ # we use DeeDiff to get the difference between the 2 json list
+ delta_versions = DeepDiff(
+ component_versions[version],
+ SORTED_MASTER,
+ ignore_order=True,
+ iterable_compare_func=compare_func,
+ cutoff_distance_for_pairs=0.1,
+ cutoff_intersection_for_pairs=1.0,
+ get_deep_distance=True)
+ LOGGER.info("Delta between Master and %s", version)
+ LOGGER.info("Delta = %s", delta_versions)
+
+ # Manage containers found in Master
+ for master_component in SORTED_MASTER:
+ # test if container mentioned in delta
+ # possible cases
+ # * values_changed:
+ # - simple version change for the same component
+ # - substitution
+ # * iterable_item_removed (remove in master)
+ # * iterable_item_added (added in master)
+ # create ComponentVersion accordingly
+ INDEX_STATUS = 0
+ other_version = {}
+ CONTAINER_STATUS = get_component_status(
+ master_component['container'],
+ delta_versions, SORTED_MASTER,
+ component_versions[version])
+ LOGGER.info("Container status: %s", CONTAINER_STATUS)
+
+ if CONTAINER_STATUS == 'version_changed':
+ other_version[version] = get_old_version_component(
+ master_component['container'], component_versions[version])
+ INDEX_STATUS = 1
+ elif CONTAINER_STATUS == 'component_added':
+ # no other version
+ INDEX_STATUS = 2
+ else:
+ other_version[version] = master_component['version']
+ # need post processing see oldest version to see
+ # if there is no changes since more than 2 release or not
+ # temp index = -1
+ # post processing shall allow to say if
+ # index = 0: no changes since last release
+ # index = 3: no changes since more than 2 release
+
+ # As we are testing several releases, check if the object already exists
+ # If so complete it, do not create a new one..
+ if NB_ITERATION_DATASET < 1:
+ LOGGER.info("Creation of the dataset for %s", master_component['container'])
+ version_object = ComponentVersion(
+ component=master_component['component'],
+ container=master_component['container'],
+ image=master_component['image'],
+ current_version=master_component['version'],
+ other_version=other_version,
+ status=INDEX_STATUS)
+ DATA_VERSIONS.append(version_object)
+ else:
+ # we already compared master with a release
+ # the DATASET is initiated
+ # we start the second comparison
+
+ # First consider the changes between master and the new release
+ LOGGER.info("Update of the dataset for %s", master_component['container'])
+ index_data = get_data_version_container_index(
+ master_component['container'], DATA_VERSIONS)
+
+ local_other_version = DATA_VERSIONS[index_data].other_version
+ if get_old_version_component(
+ master_component['container'],
+ component_versions[version]) != "unknown":
+ local_other_version[version] = get_old_version_component(
+ master_component['container'], component_versions[version])
+ DATA_VERSIONS[index_data].other_version = local_other_version
+
+ # postprocessing to detect if the version of the container
+ # did not change for at least 2 releases
+ count_versions = Counter(local_other_version.values())
+ for count_version in count_versions.values():
+ if (count_version > 1 and
+ INDEX_STATUS < 1):
+ DATA_VERSIONS[index_data].status = 3
+ NB_ITERATION_DATASET += 1
+
+ # manage removed pods
+ for removed_container in get_removed_container_list(
+ delta_versions, component_versions[version], master_component):
+
+ index_data = get_data_version_container_index(
+ removed_container['container'], DATA_VERSIONS)
+ LOGGER.info("Removed container index data in DATASET: %s", index_data)
+ # "new" removed container, add it to DATASET
+ if index_data < 0 or index_data is None:
+ version_object = ComponentVersion(
+ component=removed_container['component'],
+ container=removed_container['container'],
+ image=removed_container['image'],
+ current_version="",
+ other_version={version: removed_container['version']},
+ status=4)
+ DATA_VERSIONS.append(version_object)
+ else:
+ # removed container already seen as removed in another old release
+ # just amend the DATASET to indicate the old versions
+ LOGGER.info(
+ "Update of the dataset for a removed container in master %s",
+ removed_container['container'])
+ index_data = get_data_version_container_index(
+ removed_container['container'], DATA_VERSIONS)
+ local_other_version = DATA_VERSIONS[index_data].other_version
+ local_other_version[version] = get_old_version_component(
+ removed_container['container'], component_versions[version])
+ DATA_VERSIONS[index_data].other_version = local_other_version
+
+# Exclude filebeat dockers
+CLEAN_DATA_VERSIONS = []
+for data in DATA_VERSIONS:
+ # an xfail list coudl be used here
+ if "filebeat" not in data.image:
+ CLEAN_DATA_VERSIONS.append(data)
+
+#LOGGER.info(str(CLEAN_DATA_VERSIONS))
+
+LOGGER.info("*********************************************************************")
+LOGGER.info("*********************************************************************")
+LOGGER.info("****************** Generate Reporting ***************")
+LOGGER.info("*********************************************************************")
+LOGGER.info("*********************************************************************")
+
+jinja_env = Environment(
+ autoescape=select_autoescape(['html']),
+ loader=FileSystemLoader('./template'))
+jinja_env.get_template('docker-version-tmpl.html').stream(
+ data=CLEAN_DATA_VERSIONS).dump(
+ '{}'.format("index-versions.html"))