Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate cray-product-catalog ConfigMap to multiple product ConfigMaps #292

Merged
merged 21 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
[`catalog_delete.py`](cray_product_catalog/catalog_delete.py), because we need not retry 100
times when read ConfigMap fails for a product ConfigMap.
- CASM-4504: Added label "type=cray-product-catalog" to all cray-product-catalog related ConfigMaps

- Implemented migration of cray-product-catalog ConfigMap to multiple ConfigMaps as part of pre-upgrade steps
- Added migration job to the configmap-hook
### Dependencies

- Bump `jsonschema` from 4.18.3 to 4.18.4 (#270)
Expand Down Expand Up @@ -513,4 +514,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

[1.4.4]: https://github.com/Cray-HPE/cray-product-catalog/compare/v1.4.3...v1.4.4

[1.4.3]: https://github.com/Cray-HPE/cray-product-catalog/compare/v1.4.2...v1.4.3
[1.4.3]: https://github.com/Cray-HPE/cray-product-catalog/compare/v1.4.2...v1.4.3
42 changes: 38 additions & 4 deletions charts/cray-product-catalog/templates/configmap-hook.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{{/*
MIT License

(C) Copyright 2021-2022 Hewlett Packard Enterprise Development LP
(C) Copyright 2021-2023 Hewlett Packard Enterprise Development LP

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
Expand Down Expand Up @@ -43,10 +43,11 @@ spec:
command: ["/bin/sh"]
args:
- "-c"
- "kubectl get cm -n services cpc-backup -o yaml |
- "kubectl delete cm -n services cray-product-catalog &&
kubectl get cm -n services cpc-backup -o yaml |
yq eval '.metadata.name = \"cray-product-catalog\"' - |
yq eval 'del(.metadata.resourceVersion, .metadata.uid, .metadata.annotations, .metadata.creationTimestamp, .metadata.selfLink, .metadata.managedFields)' - |
kubectl apply -f - && kubectl delete cm -n services cpc-backup"
kubectl create -f - && kubectl delete cm -n services cpc-backup"

---
apiVersion: batch/v1
Expand All @@ -56,6 +57,7 @@ metadata:
namespace: services
annotations:
"helm.sh/hook": pre-upgrade,pre-rollback
"helm.sh/hook-weight": "2"
spec:
template:
metadata:
Expand All @@ -73,4 +75,36 @@ spec:
- "kubectl get cm -n services cray-product-catalog -o yaml |
yq eval '.metadata.name = \"cpc-backup\"' - |
yq eval 'del(.metadata.resourceVersion, .metadata.uid, .metadata.annotations, .metadata.creationTimestamp, .metadata.selfLink, .metadata.managedFields)' - |
kubectl apply -f -"
kubectl create -f -"


---
apiVersion: batch/v1
kind: Job
metadata:
name: {{ .Release.Name }}-my-job
annotations:
"helm.sh/hook": pre-upgrade
"helm.sh/hook-weight": "1"
spec:
backoffLimit: 0
template:
spec:
containers:
- args:
- -c
- /usr/bin/catalog_migrate
command:
- /bin/sh
env:
- name: CONFIG_MAP
value: {{ .Values.migration.configMap }}
- name: CONFIG_MAP_NAMESPACE
value: {{ .Values.migration.configMapNamespace }}
image: "{{ .Values.migration.image.repository }}:{{ .Values.global.appVersion }}"
imagePullPolicy: IfNotPresent
name: migrate-catalog
restartPolicy: Never
serviceAccount: {{ .Values.migration.serviceAccount }}
serviceAccountName: {{ .Values.migration.serviceAccount }}

16 changes: 15 additions & 1 deletion charts/cray-product-catalog/values.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#
# MIT License
#
# (C) Copyright 2021-2022 Hewlett Packard Enterprise Development LP
# (C) Copyright 2021-2023 Hewlett Packard Enterprise Development LP
#
# Permission is hereby granted, free of charge, to any person obtaining a
# copy of this software and associated documentation files (the "Software"),
Expand All @@ -25,3 +25,17 @@ kubectl:
image:
repository: artifactory.algol60.net/csm-docker/stable/docker-kubectl
tag: 1.19.15
migration:
image:
repository: artifactory.algol60.net/csm-docker/unstable/cray-product-catalog-update
tag: $DOCKER_VERSION
configMap: cray-product-catalog
configMapNamespace: services
serviceAccount: cray-product-catalog


global:
appVersion: 1.10.0-add-rollback-pending.106_d9b8b23

global:
appVersion: 1.10.0-add-rollback-pending.106_d9b8b23
4 changes: 2 additions & 2 deletions cray_product_catalog/catalog_update.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,10 +145,10 @@ def create_config_map(api_instance, name, namespace):
api_instance.create_namespaced_config_map(
namespace=namespace, body=new_cm
)
LOGGER.debug("Created product ConfigMap %s/%s", namespace, name)
LOGGER.info("Created product ConfigMap %s/%s", namespace, name)
return True
except ApiException:
LOGGER.warning("Error calling create_namespaced_config_map")
LOGGER.error("Error calling create_namespaced_config_map")
return False


Expand Down
2 changes: 1 addition & 1 deletion cray_product_catalog/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@
PRODUCT_CATALOG_CONFIG_MAP_LABEL_STR = "{0}={1}".format(
PRODUCT_CATALOG_CONFIG_MAP_LABEL_KEY, PRODUCT_CATALOG_CONFIG_MAP_NAME
)
PRODUCT_CATALOG_CONFIG_MAP_REPLICA = 'cray-product-catalog-temp'
PRODUCT_CATALOG_CONFIG_MAP_REPLICA = 'cray-product-catalog-temp'
6 changes: 5 additions & 1 deletion cray_product_catalog/migration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,24 @@
File defines few constants
"""

import os
from cray_product_catalog.constants import PRODUCT_CATALOG_CONFIG_MAP_LABEL_STR
from re import compile

# ConfigMap name for temporary many config map
CONFIG_MAP_TEMP = "cray-product-catalog-temp"

# namespace for ConfigMaps
CONFIG_MAP_NAMESPACE = "service"
PRODUCT_CATALOG_CONFIG_MAP_NAME = os.environ.get("CONFIG_MAP_NAME", "cray-product-catalog").strip()
PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE = os.environ.get("CONFIG_MAP_NAMESPACE", "services").strip()

# config map names
CRAY_DATA_CATALOG_LABEL = PRODUCT_CATALOG_CONFIG_MAP_LABEL_STR

# product ConfigMap pattern
PRODUCT_CONFIG_MAP_PATTERN = compile('^(cray-product-catalog)-([a-z0-9.-]+)$')
RESOURCE_VERSION = 'resource_version'

retry_count = 10
role_name = 'cray-product-catalog'
action = 'update'
134 changes: 73 additions & 61 deletions cray_product_catalog/migration/config_map_data_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,14 @@
from cray_product_catalog.logging import configure_logging
from cray_product_catalog.util.catalog_data_helper import split_catalog_data, format_product_cm_name
from cray_product_catalog.migration.kube_apis import KubernetesApi
from cray_product_catalog.constants import (
PRODUCT_CATALOG_CONFIG_MAP_NAME, PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE,
PRODUCT_CATALOG_CONFIG_MAP_LABEL, PRODUCT_CATALOG_CONFIG_MAP_REPLICA
)
from cray_product_catalog.constants import PRODUCT_CATALOG_CONFIG_MAP_LABEL
from cray_product_catalog.migration import (
CONFIG_MAP_TEMP, PRODUCT_CATALOG_CONFIG_MAP_NAME, PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, action
)

LOGGER = logging.getLogger(__name__)


class ConfigMapDataHandler:
""" Class to migrate ConfigMap data to multiple ConfigMaps """

Expand All @@ -61,67 +62,29 @@ def create_product_config_maps(self, product_config_map_data_list):
product_name = list(product_data.keys())[0]
LOGGER.debug("Creating ConfigMap for product %s", product_name)
prod_cm_name = format_product_cm_name(PRODUCT_CATALOG_CONFIG_MAP_NAME, product_name)
try:
if self.k8s_obj.create_config_map(prod_cm_name, PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, product_data,
PRODUCT_CATALOG_CONFIG_MAP_LABEL):
LOGGER.debug("Created product ConfigMap %s/%s", PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, prod_cm_name)
else:
LOGGER.info("Calling rollback handler")
except ApiException:
LOGGER.error("Error calling create_namespaced_config_map, exiting with rollback action")
LOGGER.info("Calling rollback handler")

if not self.k8s_obj.create_config_map(prod_cm_name, PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, product_data,
PRODUCT_CATALOG_CONFIG_MAP_LABEL):
LOGGER.info("Failed to create product ConfigMap %s/%s", PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE,
prod_cm_name)
return False
LOGGER.info("Created product ConfigMap %s/%s", PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, prod_cm_name)
return True

def create_temp_config_map(self, config_map_data):
"""Create temporary main ConfigMap `cray-product-catalog-temp`

Args:
config_map_data (dict): Data to be stored in the ConfigMap `cray-product-catalog-temp`
"""
try:
if self.k8s_obj.create_config_map(PRODUCT_CATALOG_CONFIG_MAP_REPLICA,
PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE,
config_map_data, PRODUCT_CATALOG_CONFIG_MAP_LABEL):
LOGGER.DEBUG("Created temp ConfigMap %s/%s",
PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, PRODUCT_CATALOG_CONFIG_MAP_REPLICA)
else:
LOGGER.info("Create ConfigMap failed, Calling rollback handler")
except ApiException:
LOGGER.error("Error calling create_namespaced_config_map, exiting with rollback action")
LOGGER.info("Calling rollback handler")

def create_main_config_map(self):
"""Create main ConfigMap `cray-product-catalog` using the data in
`cray-product-catalog-temp`"""
while True:
try:
self.k8s_obj.rename_config_map(PRODUCT_CATALOG_CONFIG_MAP_REPLICA,
PRODUCT_CATALOG_CONFIG_MAP_NAME, PRODUCT_CATALOG_CONFIG_MAP_LABEL)
LOGGER.info("Migration successful")
break
except ApiException:
LOGGER.warning("Failed to rename, retrying...")

def read_config_map_data(self):
"""Read main ConfigMap data

Returns:
{Dictionary}: data reperesenting the ConfigMap output

"""
try:
response = self.k8s_obj.read_config_map(PRODUCT_CATALOG_CONFIG_MAP_NAME,
PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE)
if response:
return response.data
LOGGER.info("No data available so exiting...")
raise SystemExit(1)
except ApiException as err:
LOGGER.exception("Error calling read_namespaced_config_map")
if err.status == 404:
LOGGER.error("ConfigMap %s/%s doesn't exist, exiting data migration",
PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, PRODUCT_CATALOG_CONFIG_MAP_NAME)
LOGGER.info("Calling rollback handler")
raise SystemExit(1)
if self.k8s_obj.create_config_map(CONFIG_MAP_TEMP, PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE,
config_map_data, PRODUCT_CATALOG_CONFIG_MAP_LABEL):
LOGGER.info("Created temp ConfigMap %s/%s",
PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, CONFIG_MAP_TEMP)
return True
LOGGER.error("Creating ConfigMap %s/%s failed", PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE, CONFIG_MAP_TEMP)
return False

def migrate_config_map_data(self, config_map_data):
"""Migrate cray-product-catalog ConfigMap data to multiple product ConfigMaps with
Expand Down Expand Up @@ -158,13 +121,62 @@ def migrate_config_map_data(self, config_map_data):
main_versions_data[version_data] = main_cm_data
# If `component_versions` data exists for a product, create new product config map
if product_versions_data:
product_config_map_data[product] = yaml.safe_dump( product_versions_data, default_flow_style=False)
#create_product_config_map(k8s_obj, product, product_config_map_data)
product_config_map_data[product] = yaml.safe_dump(product_versions_data, default_flow_style=False)
# create_product_config_map(k8s_obj, product, product_config_map_data)
product_config_map_data_list.append(product_config_map_data)
# Data with key other than `component_versions` should be updated to config_map_data,
# so that new main ConfigMap will not have data with key `component_versions`
if main_versions_data:
config_map_data[product] = yaml.safe_dump( main_versions_data, default_flow_style=False)
config_map_data[product] = yaml.safe_dump(main_versions_data, default_flow_style=False)
else:
config_map_data[product] = ""
config_map_data[product] = ''
return config_map_data, product_config_map_data_list

def rename_config_map(self, rename_from, rename_to, namespace, label):
""" Renaming is actually deleting one Config Map and then updating the name of other Config Map and patch it.
:param str rename_from: Name of Config Map to rename
:param str rename_to: Name of Config Map to be renamed to
:param str namespace: namespace in which Config Map has to be updated
:param dict label: label of config map to be renamed
:return: bool, If Success True else False
"""

if not self.k8s_obj.delete_config_map(rename_to, namespace):
logging.error("Failed to delete ConfigMap %s", rename_to)
return False
attempt = 0
del_failed = False

while attempt < 10:
attempt += 1
response = self.k8s_obj.read_config_map(rename_from, namespace)
if not response:
LOGGER.error("Failed to read ConfigMap %s, retrying..", rename_from)
continue
cm_data = response.data

if self.k8s_obj.create_config_map(rename_to, namespace, cm_data, label):
if self.k8s_obj.delete_config_map(rename_from, namespace):
LOGGER.info("Renaming ConfigMap successful")
return True
else:
LOGGER.error("Failed to delete ConfigMap %s, retrying..", rename_from)
del_failed = True
break
else:
LOGGER.error("Failed to create ConfigMap %s, retrying..", rename_to)
continue
# Since only delete of backed up ConfigMap failed, retrying only delete operation
attempt = 0
if del_failed:
while attempt < 10:
lathanm marked this conversation as resolved.
Show resolved Hide resolved
attempt += 1
if self.k8s_obj.delete_config_map(rename_from, namespace):
break
else:
LOGGER.error("Failed to delete ConfigMap %s, retrying..", rename_from)
continue
# Returning success as migration is successful only backed up ConfigMap is not deleted.
LOGGER.info("Renaming ConfigMap successful")
return True
return False
19 changes: 7 additions & 12 deletions cray_product_catalog/migration/exit_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@
from re import fullmatch


from cray_product_catalog.migration import CONFIG_MAP_TEMP, CONFIG_MAP_NAMESPACE, CRAY_DATA_CATALOG_LABEL, \
from cray_product_catalog.migration import CONFIG_MAP_TEMP, CRAY_DATA_CATALOG_LABEL, \
PRODUCT_CONFIG_MAP_PATTERN
from cray_product_catalog.migration import PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE
from cray_product_catalog.migration.kube_apis import KubernetesApi

LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -67,9 +68,9 @@ def exception_exit() -> None:
def __get_all_created_product_config_maps(self) -> List:
"""Get all created product config maps"""
cm_name = filter(_is_product_config_map,
self.k8api.list_config_map(
self.k8api.list_config_map_names(
label=CRAY_DATA_CATALOG_LABEL,
namespace=CONFIG_MAP_NAMESPACE)
namespace=PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE)
)
return list(cm_name)

Expand All @@ -81,22 +82,16 @@ def rollback(self):
LOGGER.warning("Initiating rollback")
product_config_maps = self.__get_all_created_product_config_maps() # collecting product config map

LOGGER.info("deleting ConfigMap %s", CONFIG_MAP_TEMP) # attempting to delete temp config map
if not self.k8api.delete_config_map(name=CONFIG_MAP_TEMP, namespace=CONFIG_MAP_NAMESPACE):
LOGGER.error("Error in deleting ConfigMap %s. Delete this manually along with these %s",
CONFIG_MAP_TEMP, product_config_maps)
return

LOGGER.info("deleting Product ConfigMaps") # attempting to delete product config maps
non_deleted_product_config_maps = []
for config_map in product_config_maps:
LOGGER.debug("deleting Product ConfigMap %s", config_map)
if not self.k8api.delete_config_map(name=config_map, namespace=CONFIG_MAP_NAMESPACE):
if not self.k8api.delete_config_map(name=config_map, namespace=PRODUCT_CATALOG_CONFIG_MAP_NAMESPACE):
non_deleted_product_config_maps.append(config_map)

if len(non_deleted_product_config_maps) > 0: # checking if any product config map is not deleted
LOGGER.error("Error in deleting ConfigMap/s %s. Delete this/these manually", non_deleted_product_config_maps)
LOGGER.error("Error in deleting ConfigMap/s %s. Delete this/these manually",
non_deleted_product_config_maps)
return

LOGGER.info("rollback successful")

Loading