From f1e09812261b1784ecdd44f0a021a5a5c1478968 Mon Sep 17 00:00:00 2001 From: Vasyl Saienko Date: Tue, 17 Sep 2024 07:43:38 +0000 Subject: [PATCH] [helm-toolkit] Add daemonset_overrides_root util The helm-toolkit.utils.daemonset_overrides function have some limitations: * it allows to override only conf values specifid in configmap-etc * it doesn't allow to override values for daemonsets passed via env variables or via damoenset definition. As result it is impossible to have mixed deployment when one compute is configured with dpdk while other not. * it is impossible to override interface names/other information stored in -bin configmap * It allows to schedule on both hosts and labels, which adds some uncertainty This implementation is intended to handle those limitations: * it allows to schedule only based on labels * it creates -bin per daemonset override * it allows to override values when rendering daemonsets It picks data from the following structure: .Values: overrides: mychart_mydaemonset: labels: label::value: values: override_root_option: override_root_value conf: ovs_dpdk: enabled: true neutron: DEFAULT: foo: bar Change-Id: I5ff0f5deb34c74ca95c141f2402f375f6d926533 --- helm-toolkit/Chart.yaml | 2 +- .../utils/_daemonset_overrides_root.tpl | 279 ++++++++++++++++++ releasenotes/notes/helm-toolkit.yaml | 1 + 3 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 helm-toolkit/templates/utils/_daemonset_overrides_root.tpl diff --git a/helm-toolkit/Chart.yaml b/helm-toolkit/Chart.yaml index 638dfe339..04f22bec1 100644 --- a/helm-toolkit/Chart.yaml +++ b/helm-toolkit/Chart.yaml @@ -15,7 +15,7 @@ apiVersion: v1 appVersion: v1.0.0 description: OpenStack-Helm Helm-Toolkit name: helm-toolkit -version: 0.2.74 +version: 0.2.75 home: https://docs.openstack.org/openstack-helm icon: https://www.openstack.org/themes/openstack/images/project-mascots/OpenStack-Helm/OpenStack_Project_OpenStackHelm_vertical.png sources: diff --git a/helm-toolkit/templates/utils/_daemonset_overrides_root.tpl b/helm-toolkit/templates/utils/_daemonset_overrides_root.tpl new file mode 100644 index 000000000..bdb28c331 --- /dev/null +++ b/helm-toolkit/templates/utils/_daemonset_overrides_root.tpl @@ -0,0 +1,279 @@ +{{/* +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. +*/}} + +{{/* + +The helm-toolkit.utils.daemonset_overrides function have some limitations: + + * it allows to override only conf values specifid in configmap-etc + * it doesn't allow to override values for daemonsets passed via env variables + or via damoenset definition. As result it is impossible to have mixed + deployment when one compute is configured with dpdk while other not. + * it is impossible to override interface names/other information stored in + -bin configmap + * It allows to schedule on both hosts and labels, which adds some + uncertainty + +This implementation is intended to handle those limitations: + + * it allows to schedule only based on labels + * it creates -bin per daemonset override + * it allows to override values when rendering daemonsets + + It picks data from the following structure: + + .Values: + overrides: + mychart_mydaemonset: + labels: + label::value: + values: + override_root_option: override_root_value + conf: + ovs_dpdk: + enabled: true + neutron: + DEFAULT: + foo: bar + +*/}} + +{{- define "helm-toolkit.utils.daemonset_overrides_root" }} + {{- $daemonset := index . 0 }} + {{- $daemonSetTemplateName := index . 1 }} + {{ $serviceAccountName := index . 2 }} + {{- $configmap_include := index . 3 }} + {{- $configmap_name := index . 4 }} + {{- $configbin_include := index . 5 }} + {{- $configbin_name := index . 6 }} + {{- $context := index . 7 }} + + {{- $_ := unset $context ".Files" }} + {{- $daemonset_root_name := printf (print $context.Chart.Name "_" $daemonset) }} + {{- $_ := set $context.Values "__daemonset_list" list }} + {{- $_ := set $context.Values "__default" dict }} + + {{- $default_enabled := true }} + {{- if hasKey $context.Values "overrides" }} + {{- range $key, $val := $context.Values.overrides }} + + {{- if eq $key $daemonset_root_name }} + {{- range $type, $type_data := . }} + {{- if eq $type "overrides_default" }} + {{- $default_enabled = $type_data }} + {{- end }} + + {{- if eq $type "labels" }} + {{- $_ := set $context.Values "__label_dict" . }} + {{- range $lname, $ldata := . }} + {{ $label_name := (split "::" $lname)._0 }} + {{ $label_value := (split "::" $lname)._1 }} + {{/* dictionary that will contain all info needed to generate this + iteration of the daemonset. */}} + {{- $_ := set $context.Values "__current_label" dict }} + + {{/* set daemonset name */}} + {{- $_ := set $context.Values.__current_label "name" $label_name }} + + {{/* set daemonset metadata annotation */}} + {{- $_ := set $context.Values.__current_label "daemonset_override" $lname }} + + {{/* apply overrides */}} + + + {{- $override_root_copy := $ldata.values }} + {{/* Deep copy to prevent https://storyboard.openstack.org/#!/story/2005936 */}} + {{- $root_copy := omit ($context.Values | toYaml | fromYaml) "overrides" }} + {{- $merged_dict := mergeOverwrite $root_copy $override_root_copy }} + + {{- $root_conf_copy2 := dict "values" $merged_dict }} + {{- $context_values := omit (omit ($context.Values | toYaml | fromYaml) "values") "__daemonset_list" }} + {{- $root_conf_copy3 := mergeOverwrite $context_values $root_conf_copy2.values }} + {{- $root_conf_copy4 := dict "Values" $root_conf_copy3 }} + {{- $_ := set $context.Values.__current_label "nodeData" $root_conf_copy4 }} + + + {{/* Schedule to the provided label value(s) */}} + {{- $label_dict := dict "key" $label_name }} + {{- $_ := set $label_dict "values" (list $label_value) }} + {{- $_ := set $label_dict "operator" "In" }} + {{- $list_aggregate := list $label_dict }} + {{- $_ := set $context.Values.__current_label "matchExpressions" $list_aggregate }} + + {{/* Do not schedule to other specified labels, with higher + precedence as the list position increases. Last defined label + is highest priority. */}} + {{- $other_labels := omit $context.Values.__label_dict $lname }} + {{- range $lname2, $ldata2 := $other_labels }} + {{ $label_name2 := (split "::" $lname2)._0 }} + {{ $label_value2 := (split "::" $lname2)._1 }} + + {{- $label_dict := dict "key" $label_name2 }} + {{- $_ := set $label_dict "values" (list $label_value2) }} + {{- $_ := set $label_dict "operator" "NotIn" }} + + {{- $list_aggregate := append $context.Values.__current_label.matchExpressions $label_dict }} + {{- $_ := set $context.Values.__current_label "matchExpressions" $list_aggregate }} + {{- end }} + + {{/* store completed daemonset entry/info into global list */}} + {{- $list_aggregate := append $context.Values.__daemonset_list $context.Values.__current_label }} + {{- $_ := set $context.Values "__daemonset_list" $list_aggregate }} + {{- $_ := unset $context.Values "__current_label" }} + + {{- end }} + {{- end }} + {{- end }} + + {{/* scheduler exceptions for the default daemonset */}} + {{- $_ := set $context.Values.__default "matchExpressions" list }} + + {{- range $type, $type_data := . }} + {{/* Do not schedule to other specified labels */}} + {{- if eq $type "labels" }} + {{- range $lname, $ldata := . }} + {{ $label_name := (split "::" $lname)._0 }} + {{ $label_value := (split "::" $lname)._1 }} + + {{- $default_dict := dict "key" $label_name }} + {{- $_ := set $default_dict "values" (list $label_value) }} + {{- $_ := set $default_dict "operator" "NotIn" }} + + {{- $list_aggregate := append $context.Values.__default.matchExpressions $default_dict }} + {{- $_ := set $context.Values.__default "matchExpressions" $list_aggregate }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + + {{/* generate the default daemonset */}} + + {{/* set name */}} + {{- $_ := set $context.Values.__default "name" "default" }} + + {{/* no overrides apply, so copy as-is */}} + {{- $root_conf_copy1 := omit $context.Values.conf "overrides" }} + {{- $root_conf_copy2 := dict "conf" $root_conf_copy1 }} + {{- $context_values := omit $context.Values "conf" }} + {{- $root_conf_copy3 := mergeOverwrite $context_values $root_conf_copy2 }} + {{- $root_conf_copy4 := dict "Values" $root_conf_copy3 }} + {{- $_ := set $context.Values.__default "nodeData" $root_conf_copy4 }} + + {{/* add to global list */}} + {{- if $default_enabled }} + {{- $list_aggregate := append $context.Values.__daemonset_list $context.Values.__default }} + {{- $_ := set $context.Values "__daemonset_list" $list_aggregate }} + {{- end }} + + {{- range $current_dict := $context.Values.__daemonset_list }} + + {{- $context_novalues := omit $context "Values" }} + {{- $merged_dict := mergeOverwrite $context_novalues $current_dict.nodeData }} + {{- $_ := set $current_dict "nodeData" $merged_dict }} + {{/* Deep copy original daemonset_yaml */}} + {{- $daemonset_yaml := list $daemonset $configmap_name $serviceAccountName $merged_dict | include $daemonSetTemplateName | toString | fromYaml }} + {{- $_ := set $context.Values "__daemonset_yaml" ($daemonset_yaml | toYaml | fromYaml) }} + + {{/* Use the following name format $daemonset_root_name + sha256summ($current_dict.matchExpressions) + as labels might be too long and contain wrong characters like / */}} + {{- $_ := set $current_dict "dns_1123_name" dict }} + {{- $name_format := "" }} + {{- if eq $current_dict.name "default" }} + {{- $name_format = (printf "%s-%s" $daemonset_root_name "default") | replace "_" "-" }} + {{- else }} + {{- $name_format = (printf "%s-%s" $daemonset_root_name ($current_dict.matchExpressions | quote | sha256sum | trunc 16)) | replace "_" "-" }} + {{- end }} + {{- $_ := set $current_dict "dns_1123_name" $name_format }} + + {{/* set daemonset metadata name */}} + {{- if not $context.Values.__daemonset_yaml.metadata }}{{- $_ := set $context.Values.__daemonset_yaml "metadata" dict }}{{- end }} + {{- if not $context.Values.__daemonset_yaml.metadata.name }}{{- $_ := set $context.Values.__daemonset_yaml.metadata "name" dict }}{{- end }} + {{- $_ := set $context.Values.__daemonset_yaml.metadata "name" $current_dict.dns_1123_name }} + + {{/* cross-reference configmap name to container volume definitions */}} + {{- $_ := set $context.Values "__volume_list" list }} + {{- range $current_volume := $context.Values.__daemonset_yaml.spec.template.spec.volumes }} + {{- $_ := set $context.Values "__volume" $current_volume }} + {{- if hasKey $context.Values.__volume "secret" }} + {{- if eq $context.Values.__volume.secret.secretName $configmap_name }} + {{- $_ := set $context.Values.__volume.secret "secretName" (printf "%s-etc" $current_dict.dns_1123_name) }} + {{- end }} + {{- end }} + {{- if hasKey $context.Values.__volume "configMap" }} + {{- if eq $context.Values.__volume.configMap.name $configbin_name }} + {{- $_ := set $context.Values.__volume.configMap "name" (printf "%s-bin" $current_dict.dns_1123_name) }} + {{- end }} + {{- end }} + {{- $updated_list := append $context.Values.__volume_list $context.Values.__volume }} + {{- $_ := set $context.Values "__volume_list" $updated_list }} + {{- end }} + {{- $_ := set $context.Values.__daemonset_yaml.spec.template.spec "volumes" $context.Values.__volume_list }} + + + {{/* populate scheduling restrictions */}} + {{- if hasKey $current_dict "matchExpressions" }} + {{- $length := len $current_dict.matchExpressions }} + {{- if gt $length 0 }} + {{- if not $context.Values.__daemonset_yaml.spec.template.spec }}{{- $_ := set $context.Values.__daemonset_yaml.spec.template "spec" dict }}{{- end }} + {{- if not $context.Values.__daemonset_yaml.spec.template.spec.affinity }}{{- $_ := set $context.Values.__daemonset_yaml.spec.template.spec "affinity" dict }}{{- end }} + {{- if not $context.Values.__daemonset_yaml.spec.template.spec.affinity.nodeAffinity }}{{- $_ := set $context.Values.__daemonset_yaml.spec.template.spec.affinity "nodeAffinity" dict }}{{- end }} + {{- if not $context.Values.__daemonset_yaml.spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution }}{{- $_ := set $context.Values.__daemonset_yaml.spec.template.spec.affinity.nodeAffinity "requiredDuringSchedulingIgnoredDuringExecution" dict }}{{- end }} + + {{- $expressions_modified := list }} + {{- if hasKey $context.Values.__daemonset_yaml.spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution "nodeSelectorTerms" }} + {{- range $orig_expression := $context.Values.__daemonset_yaml.spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms }} + {{- $match_expressions_modified := list }} + {{- $match_expressions_modified = concat $match_expressions_modified $current_dict.matchExpressions }} + {{- if hasKey $orig_expression "matchExpressions" }} + {{- $match_expressions_modified = concat $match_expressions_modified $orig_expression.matchExpressions }} + {{- $expressions_modified = append $expressions_modified (dict "matchExpressions" $match_expressions_modified) }} + {{- end }} + {{- end }} + {{- else }} + {{- $expressions_modified = (list (dict "matchExpressions" $current_dict.matchExpressions)) }} + {{- end }} + {{- $_ := set $context.Values.__daemonset_yaml.spec.template.spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution "nodeSelectorTerms" $expressions_modified }} + {{- end }} + {{- end }} + + {{/* input value hash for current set of values overrides */}} + {{- if not $context.Values.__daemonset_yaml.spec }}{{- $_ := set $context.Values.__daemonset_yaml "spec" dict }}{{- end }} + {{- if not $context.Values.__daemonset_yaml.spec.template }}{{- $_ := set $context.Values.__daemonset_yaml.spec "template" dict }}{{- end }} + {{- if not $context.Values.__daemonset_yaml.spec.template.metadata }}{{- $_ := set $context.Values.__daemonset_yaml.spec.template "metadata" dict }}{{- end }} + {{- if not $context.Values.__daemonset_yaml.spec.template.metadata.annotations }}{{- $_ := set $context.Values.__daemonset_yaml.spec.template.metadata "annotations" dict }}{{- end }} + {{- $cmap := list (printf "%s-etc" $current_dict.dns_1123_name) $current_dict.nodeData | include $configmap_include }} + {{- $cmap_bin := list (printf "%s-bin" $current_dict.dns_1123_name) $current_dict.nodeData | include $configbin_include }} + {{- $values_cmap_hash := $cmap | quote | sha256sum }} + {{- $values_cmap_bin_hash := $cmap_bin | quote | sha256sum }} + {{- $_ := set $context.Values.__daemonset_yaml.spec.template.metadata.annotations "configmap-etc-hash" $values_cmap_hash }} + {{- $_ := set $context.Values.__daemonset_yaml.spec.template.metadata.annotations "configmap-bin-hash" $values_cmap_bin_hash }} + + {{/* Do not set override for default daemonset */}} + {{- if $current_dict.daemonset_override }} + {{- $_ := set $context.Values.__daemonset_yaml.metadata.annotations "daemonset_override" $current_dict.daemonset_override }} + {{- end }} + +{{/* generate configmap */}} +--- +{{ $cmap }} + {{/* generate -bin yaml */}} +--- +{{ $cmap_bin }} + {{/* generate daemonset yaml */}} +--- +{{ $context.Values.__daemonset_yaml | toYaml }} + {{- end }} +{{- end }} diff --git a/releasenotes/notes/helm-toolkit.yaml b/releasenotes/notes/helm-toolkit.yaml index 95678e255..aff910652 100644 --- a/releasenotes/notes/helm-toolkit.yaml +++ b/releasenotes/notes/helm-toolkit.yaml @@ -81,4 +81,5 @@ helm-toolkit: - 0.2.72 Add snippet configmap_oslo_policy - 0.2.73 Add ability to get multiple hosts endpoint - 0.2.74 Remove trailing slash in endpoinds + - 0.2.75 Add daemonset_overrides_root util ...