Skip to content

Commit f611358

Browse files
authored
Enhance ODLM create/update logic by adding hash values (#1086)
* hash comparison and deep merge all the resources Signed-off-by: YuChen <[email protected]> * update hash number Signed-off-by: YuChen <[email protected]> --------- Signed-off-by: YuChen <[email protected]>
1 parent 2d8d9d3 commit f611358

File tree

4 files changed

+95
-28
lines changed

4 files changed

+95
-28
lines changed

controllers/constant/constant.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ const (
6464
//HashedData is the key for checking the checksum of data section
6565
HashedData string = "hashedData"
6666

67+
//HashedData is the key for k8s Resource
68+
K8sHashedData string = "operator.ibm.com/operand-depoyment-lifecycle-manager.hashedData"
69+
70+
//RouteHash is the key for hash value of route
71+
RouteHash string = "operator.ibm.com/odlm.route.hashedData"
72+
6773
//DefaultRequestTimeout is the default timeout for kube request
6874
DefaultRequestTimeout = 5 * time.Second
6975

controllers/operandrequest/reconcile_operand.go

Lines changed: 55 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1014,14 +1014,26 @@ func (r *Reconciler) createK8sResource(ctx context.Context, k8sResTemplate unstr
10141014
k8sResTemplate.Object[k] = v
10151015
}
10161016

1017+
k8sResConfigBytes, err := json.Marshal(k8sResConfigDecoded)
1018+
if err != nil {
1019+
return errors.Wrap(err, "failed to marshal k8sResConfigDecoded")
1020+
}
1021+
1022+
// Caculate the hash number of the new created template
1023+
_, templateHash := util.CalculateResHashes(nil, k8sResConfigBytes)
1024+
1025+
newAnnotations = util.AddHashAnnotation(&k8sResTemplate, constant.K8sHashedData, templateHash, newAnnotations)
1026+
10171027
if kind == "Route" {
10181028
if host, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found {
1019-
hostHash := util.CalculateHash(host)
1029+
hostHash := util.CalculateHash([]byte(host))
1030+
1031+
// if newAnnotations == nil {
1032+
// newAnnotations = make(map[string]string)
1033+
// }
1034+
// newAnnotations[constant.RouteHash] = hostHash
1035+
newAnnotations = util.AddHashAnnotation(&k8sResTemplate, constant.RouteHash, hostHash, newAnnotations)
10201036

1021-
if newAnnotations == nil {
1022-
newAnnotations = make(map[string]string)
1023-
}
1024-
newAnnotations["operator.ibm.com/odlm.route.hashedData"] = hostHash
10251037
} else {
10261038
klog.Warningf("spec.host not found in Route %s/%s", namespace, name)
10271039
}
@@ -1064,29 +1076,30 @@ func (r *Reconciler) updateK8sResource(ctx context.Context, existingK8sRes unstr
10641076
return errors.Wrap(err, "failed to update Route")
10651077
}
10661078

1079+
// update the annotations of the Route host if the host is changed
10671080
if k8sResConfig != nil {
10681081
k8sResConfigDecoded := make(map[string]interface{})
10691082
if k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded); k8sResConfigUnmarshalErr != nil {
10701083
return errors.Wrap(k8sResConfigUnmarshalErr, "failed to unmarshal k8s Resource Config")
10711084
}
10721085

10731086
if host, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found {
1074-
hostHash := util.CalculateHash(host)
1087+
hostHash := util.CalculateHash([]byte(host))
10751088

10761089
if newAnnotations == nil {
10771090
newAnnotations = make(map[string]string)
10781091
}
1079-
newAnnotations["operator.ibm.com/odlm.route.hashedData"] = hostHash
1092+
newAnnotations[constant.RouteHash] = hostHash
10801093
} else {
10811094
klog.Warningf("spec.host not found in Route %s/%s", namespace, name)
10821095
}
10831096
}
10841097
}
10851098

1086-
// Update the k8s res
1099+
// Update the k8s resource
10871100
err := wait.PollImmediate(constant.DefaultCRFetchPeriod, constant.DefaultCRFetchTimeout, func() (bool, error) {
10881101

1089-
existingK8sRes := unstructured.Unstructured{
1102+
existingRes := unstructured.Unstructured{
10901103
Object: map[string]interface{}{
10911104
"apiVersion": apiversion,
10921105
"kind": kind,
@@ -1096,40 +1109,56 @@ func (r *Reconciler) updateK8sResource(ctx context.Context, existingK8sRes unstr
10961109
err := r.Client.Get(ctx, types.NamespacedName{
10971110
Name: name,
10981111
Namespace: namespace,
1099-
}, &existingK8sRes)
1100-
1112+
}, &existingRes)
11011113
if err != nil {
11021114
return false, errors.Wrapf(err, "failed to get k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name)
11031115
}
1116+
resourceVersion := existingRes.GetResourceVersion()
11041117

1105-
if !r.CheckLabel(existingK8sRes, map[string]string{constant.OpreqLabel: "true"}) && (newLabels == nil || newLabels[constant.OpreqLabel] != "true") {
1118+
if !r.CheckLabel(existingRes, map[string]string{constant.OpreqLabel: "true"}) && (newLabels == nil || newLabels[constant.OpreqLabel] != "true") {
11061119
return true, nil
11071120
}
11081121

11091122
if k8sResConfig != nil {
11101123

11111124
// Convert existing k8s resource to string
1112-
existingK8sResRaw, err := json.Marshal(existingK8sRes.Object)
1125+
existingResRaw, err := json.Marshal(existingRes.Object)
11131126
if err != nil {
11141127
return false, errors.Wrapf(err, "failed to marshal existing k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name)
11151128
}
11161129

1117-
// Merge the existing CR and the CR from the OperandConfig
1118-
updatedExistingK8sRes := util.MergeCR(existingK8sResRaw, k8sResConfig.Raw)
1119-
// Update the existing k8s resource with the merged CR
1120-
existingK8sRes.Object = updatedExistingK8sRes
1130+
// Caculate the hash number of the new created template
1131+
existingHash, templateHash := util.CalculateResHashes(&existingRes, k8sResConfig.Raw)
11211132

1122-
r.EnsureAnnotation(existingK8sRes, newAnnotations)
1123-
r.EnsureLabel(existingK8sRes, newLabels)
1124-
if err := r.setOwnerReferences(ctx, &existingK8sRes, ownerReferences); err != nil {
1133+
// If the hash number of the existing k8s resource is different from the hash number of template, update the k8s resource
1134+
if existingHash != templateHash {
1135+
k8sResConfigDecoded := make(map[string]interface{})
1136+
k8sResConfigUnmarshalErr := json.Unmarshal(k8sResConfig.Raw, &k8sResConfigDecoded)
1137+
if k8sResConfigUnmarshalErr != nil {
1138+
klog.Errorf("failed to unmarshal k8s Resource Config: %v", k8sResConfigUnmarshalErr)
1139+
}
1140+
for k, v := range k8sResConfigDecoded {
1141+
existingRes.Object[k] = v
1142+
}
1143+
newAnnotations = util.AddHashAnnotation(&existingRes, constant.K8sHashedData, templateHash, newAnnotations)
1144+
1145+
} else {
1146+
// If the hash number are the same, then do the deep merge
1147+
// Merge the existing CR and the CR from the OperandConfig
1148+
updatedExistingRes := util.MergeCR(existingResRaw, k8sResConfig.Raw)
1149+
// Update the existing k8s resource with the merged CR
1150+
existingRes.Object = updatedExistingRes
1151+
}
1152+
1153+
r.EnsureAnnotation(existingRes, newAnnotations)
1154+
r.EnsureLabel(existingRes, newLabels)
1155+
if err := r.setOwnerReferences(ctx, &existingRes, ownerReferences); err != nil {
11251156
return false, errors.Wrapf(err, "failed to set ownerReferences for k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name)
11261157
}
11271158

11281159
klog.Infof("updating k8s resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name)
11291160

1130-
resourceVersion := existingK8sRes.GetResourceVersion()
1131-
err = r.Update(ctx, &existingK8sRes)
1132-
1161+
err = r.Update(ctx, &existingRes)
11331162
if err != nil {
11341163
return false, errors.Wrapf(err, "failed to update k8s resource -- Kind: %s, NamespacedName: %s/%s", kind, namespace, name)
11351164
}
@@ -1156,6 +1185,7 @@ func (r *Reconciler) updateK8sResource(ctx context.Context, existingK8sRes unstr
11561185
} else {
11571186
klog.Infof("No updates on k8s resource with apiversion: %s, kind: %s, %s/%s", apiversion, kind, namespace, name)
11581187
}
1188+
11591189
}
11601190
return true, nil
11611191
})
@@ -1253,7 +1283,7 @@ func (r *Reconciler) updateK8sRoute(ctx context.Context, existingK8sRes unstruct
12531283
}
12541284

12551285
existingAnnos := existingRes.GetAnnotations()
1256-
existingHostHash := existingAnnos["operator.ibm.com/odlm.route.hashedData"]
1286+
existingHostHash := existingAnnos[constant.RouteHash]
12571287

12581288
if k8sResConfig != nil {
12591289
k8sResConfigDecoded := make(map[string]interface{})
@@ -1264,7 +1294,7 @@ func (r *Reconciler) updateK8sRoute(ctx context.Context, existingK8sRes unstruct
12641294

12651295
// Read the host from the OperandConfig
12661296
if newHost, found := k8sResConfigDecoded["spec"].(map[string]interface{})["host"].(string); found {
1267-
newHostHash := util.CalculateHash(newHost)
1297+
newHostHash := util.CalculateHash([]byte(newHost))
12681298

12691299
// Only re-create the route if the custom host has been removed
12701300
if newHost == "" && existingHostHash != newHostHash {

controllers/util/merge.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ func MergeCR(defaultCR, changedCR []byte) map[string]interface{} {
2929
return make(map[string]interface{})
3030
}
3131

32+
// Handle when only one CR is provided
3233
defaultCRDecoded := make(map[string]interface{})
3334
changedCRDecoded := make(map[string]interface{})
3435
if len(defaultCR) != 0 && len(changedCR) == 0 {
@@ -52,9 +53,12 @@ func MergeCR(defaultCR, changedCR []byte) map[string]interface{} {
5253
if changedCRUnmarshalErr != nil {
5354
klog.Errorf("failed to unmarshal service spec: %v", changedCRUnmarshalErr)
5455
}
56+
57+
// Merge both specs
5558
for key := range defaultCRDecoded {
5659
checkKeyBeforeMerging(key, defaultCRDecoded[key], changedCRDecoded[key], changedCRDecoded)
5760
}
61+
5862
return changedCRDecoded
5963
}
6064

controllers/util/util.go

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ import (
3737
"k8s.io/apimachinery/pkg/runtime"
3838
"k8s.io/client-go/discovery"
3939
"k8s.io/client-go/util/jsonpath"
40+
41+
constant "github.com/IBM/operand-deployment-lifecycle-manager/v4/controllers/constant"
4042
)
4143

4244
type TemplateValueRef struct {
@@ -154,14 +156,39 @@ func StringSliceContentEqual(a, b []string) bool {
154156
return true
155157
}
156158

157-
func CalculateHash(input string) string {
158-
if input == "" {
159+
// CalculateHash calculates the hash value for single resource
160+
func CalculateHash(input []byte) string {
161+
if len(input) == 0 {
159162
return ""
160163
}
161-
hashedData := sha256.Sum256([]byte(input))
164+
hashedData := sha256.Sum256(input)
162165
return hex.EncodeToString(hashedData[:7])
163166
}
164167

168+
// CalculateResHashes calculates the hash for the existing cluster resource and the new template resource
169+
func CalculateResHashes(fromCluster *unstructured.Unstructured, fromTemplate []byte) (string, string) {
170+
templateHash := CalculateHash(fromTemplate)
171+
172+
if fromCluster != nil {
173+
clusterAnnos := fromCluster.GetAnnotations()
174+
clusterHash := ""
175+
if clusterAnnos != nil {
176+
clusterHash = clusterAnnos[constant.K8sHashedData]
177+
}
178+
return clusterHash, templateHash
179+
}
180+
return "", templateHash
181+
}
182+
183+
// SetHashAnnotation sets the hash annotation in the object
184+
func AddHashAnnotation(obj *unstructured.Unstructured, key, hash string, newAnnotations map[string]string) map[string]string {
185+
if newAnnotations == nil {
186+
newAnnotations = make(map[string]string)
187+
}
188+
newAnnotations[key] = hash
189+
return newAnnotations
190+
}
191+
165192
// WaitTimeout waits for the waitgroup for the specified max timeout.
166193
// Returns true if waiting timed out.
167194
func WaitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool {

0 commit comments

Comments
 (0)