diff --git a/bundle/manifests/ibm-websphere-liberty.clusterserviceversion.yaml b/bundle/manifests/ibm-websphere-liberty.clusterserviceversion.yaml index c3e4e1fc..385e5ebb 100644 --- a/bundle/manifests/ibm-websphere-liberty.clusterserviceversion.yaml +++ b/bundle/manifests/ibm-websphere-liberty.clusterserviceversion.yaml @@ -1086,6 +1086,18 @@ spec: - patch - update - watch + - apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - image.openshift.io resources: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 81858a0b..001150dd 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -86,6 +86,18 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - image.openshift.io resources: diff --git a/go.mod b/go.mod index ba76260f..8641484b 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,14 @@ module github.com/WASdev/websphere-liberty-operator go 1.24 require ( - github.com/OpenLiberty/open-liberty-operator v0.8.1-0.20250417153026-02cfbf34f67c - github.com/application-stacks/runtime-component-operator v1.0.0-20220602-0850.0.20250417152956-6554621c89ab + github.com/OpenLiberty/open-liberty-operator v0.8.1-0.20250505185151-9968c73cb7cc + github.com/application-stacks/runtime-component-operator v1.0.0-20220602-0850.0.20250502192232-9e8e8d1ebd27 github.com/cert-manager/cert-manager v1.14.7 github.com/go-logr/logr v1.4.1 github.com/openshift/api v0.0.0-20230928134114-673ed0cfc7f1 github.com/openshift/library-go v0.0.0-20231002074440-3f69f773d102 github.com/pkg/errors v0.9.1 github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring v0.74.0 - gopkg.in/yaml.v2 v2.4.0 k8s.io/api v0.29.5 k8s.io/apimachinery v0.29.5 k8s.io/client-go v0.29.5 @@ -69,6 +68,7 @@ require ( gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/protobuf v1.34.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect k8s.io/apiextensions-apiserver v0.29.5 // indirect k8s.io/component-base v0.29.5 // indirect diff --git a/go.sum b/go.sum index 771f2a05..5825ad1b 100644 --- a/go.sum +++ b/go.sum @@ -2,10 +2,10 @@ contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d h contrib.go.opencensus.io/exporter/ocagent v0.7.1-0.20200907061046-05415f1de66d/go.mod h1:IshRmMJBhDfFj5Y67nVhMYTTIze91RUeT73ipWKs/GY= contrib.go.opencensus.io/exporter/prometheus v0.4.2 h1:sqfsYl5GIY/L570iT+l93ehxaWJs2/OwXtiWwew3oAg= contrib.go.opencensus.io/exporter/prometheus v0.4.2/go.mod h1:dvEHbiKmgvbr5pjaF9fpw1KeYcjrnC1J8B+JKjsZyRQ= -github.com/OpenLiberty/open-liberty-operator v0.8.1-0.20250417153026-02cfbf34f67c h1:m+KMnwXnIawszaR40hMfKDLGKZ/6LhKbU4OFmC/17tI= -github.com/OpenLiberty/open-liberty-operator v0.8.1-0.20250417153026-02cfbf34f67c/go.mod h1:Vuhp8dJFmkuaFCekwVRd9zRS19vrr7qJe8406nlXRBI= -github.com/application-stacks/runtime-component-operator v1.0.0-20220602-0850.0.20250417152956-6554621c89ab h1:UrCenySbY8pJJkwZe+xBNlGqdVn5TFvAW3OiyqXT3BU= -github.com/application-stacks/runtime-component-operator v1.0.0-20220602-0850.0.20250417152956-6554621c89ab/go.mod h1:AWpfq1fnI8tB4zVM7un+pTeTbasLVmjSlA55kXQGNGA= +github.com/OpenLiberty/open-liberty-operator v0.8.1-0.20250505185151-9968c73cb7cc h1:YQ6grPY9Rkr65m05LffR9TsLuWkI4LXsOp8IL3/g8Eg= +github.com/OpenLiberty/open-liberty-operator v0.8.1-0.20250505185151-9968c73cb7cc/go.mod h1:aZ6kbfmHPvnmVrNHujX/bMuLLGQLLaATIFbb2jbs+uc= +github.com/application-stacks/runtime-component-operator v1.0.0-20220602-0850.0.20250502192232-9e8e8d1ebd27 h1:bp+5vrK0Uz7lwK388ZO4X6QTnkFRxz0RUFnWVTertak= +github.com/application-stacks/runtime-component-operator v1.0.0-20220602-0850.0.20250502192232-9e8e8d1ebd27/go.mod h1:AWpfq1fnI8tB4zVM7un+pTeTbasLVmjSlA55kXQGNGA= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/internal/controller/assets/trace-decision-tree.yaml b/internal/controller/assets/trace-decision-tree.yaml new file mode 100644 index 00000000..a155df2c --- /dev/null +++ b/internal/controller/assets/trace-decision-tree.yaml @@ -0,0 +1,7 @@ +# This file enables resource sharing for day-2 operation Trace CR instances that share the same name +# +# '*' wildcard will map to a list of strings generated from the leader tracker at runtime +tree: + v1_4_2: + name: "*" +replace: {} \ No newline at end of file diff --git a/internal/controller/assets/trace-signature.yaml b/internal/controller/assets/trace-signature.yaml new file mode 100644 index 00000000..0ad8c27d --- /dev/null +++ b/internal/controller/assets/trace-signature.yaml @@ -0,0 +1,4 @@ +apiVersion: liberty.websphere.ibm.com/v1 +kind: WebSphereLibertyTrace +name: "{0}" +rootName: "" # empty root name is used because pod names are determined by the cluster \ No newline at end of file diff --git a/internal/controller/ltpa_keys_sharing.go b/internal/controller/ltpa_keys_sharing.go index 1158b6a5..4ad2834a 100644 --- a/internal/controller/ltpa_keys_sharing.go +++ b/internal/controller/ltpa_keys_sharing.go @@ -25,6 +25,7 @@ import ( "sync" "time" + "github.com/OpenLiberty/open-liberty-operator/utils/leader" tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" lutils "github.com/WASdev/websphere-liberty-operator/utils" @@ -49,18 +50,18 @@ const ( ) func init() { - lutils.LeaderTrackerMutexes.Store(LTPA_RESOURCE_SHARING_FILE_NAME, &sync.Mutex{}) + leader.LeaderTrackerMutexes.Store(LTPA_RESOURCE_SHARING_FILE_NAME, &sync.Mutex{}) } -func (r *ReconcileWebSphereLiberty) reconcileLTPAMetadata(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, latestOperandVersion string, assetsFolder *string) (lutils.LeaderTrackerMetadataList, error) { - metadataList := &lutils.LTPAMetadataList{} - metadataList.Items = []lutils.LeaderTrackerMetadata{} +func (r *ReconcileWebSphereLiberty) reconcileLTPAMetadata(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, latestOperandVersion string, assetsFolder *string) (leader.LeaderTrackerMetadataList, error) { + metadataList := &leader.LTPAMetadataList{} + metadataList.Items = []leader.LeaderTrackerMetadata{} // During runtime, the WebSphereLibertyApplication instance will decide what LTPA related resources to track by populating arrays of pathOptions and pathChoices pathOptionsList, pathChoicesList := r.getLTPAPathOptionsAndChoices(instance, latestOperandVersion) for i := range pathOptionsList { - metadata := &lutils.LTPAMetadata{} + metadata := &leader.LTPAMetadata{} pathOptions := pathOptionsList[i] pathChoices := pathChoicesList[i] @@ -78,12 +79,12 @@ func (r *ReconcileWebSphereLiberty) reconcileLTPAMetadata(instance *wlv1.WebSphe return metadataList, err } // retrieve the LTPA leader tracker to re-use an existing name or to create a new metadata.Name - leaderTracker, _, err := lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTracker, _, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) if err != nil { return metadataList, err } // if the leaderTracker is on a mismatched version, wait for a subsequent reconcile loop to re-create the leader tracker - if leaderTracker.Labels[lutils.LeaderVersionLabel] != latestOperandVersion { + if leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)] != latestOperandVersion { return metadataList, fmt.Errorf("waiting for the Leader Tracker to be updated") } @@ -113,7 +114,7 @@ func (r *ReconcileWebSphereLiberty) getLTPAPathOptionsAndChoices(instance *wlv1. // 2. Generate a path option/choice for a leader to manage the Liberty config pathOptions = []string{"config"} configChoice := "default" - if r.isUsingPasswordEncryptionKeySharing(instance, &lutils.PasswordEncryptionMetadata{Name: ""}) { + if r.isUsingPasswordEncryptionKeySharing(instance, &leader.PasswordEncryptionMetadata{Name: ""}) { configChoice = "passwordencryption" } pathChoices = []string{configChoice} // fix LTPA to use the default password encryption key (no suffix) @@ -134,15 +135,15 @@ func (r *ReconcileWebSphereLiberty) getLTPAPathOptionsAndChoices(instance *wlv1. func (r *ReconcileWebSphereLiberty) getLTPAMetadataName(instance *wlv1.WebSphereLibertyApplication, leaderTracker *corev1.Secret, validSubPath string, assetsFolder *string, ltpaResourceType LTPAResource) string { // if an existing resource name (suffix) for this key combination already exists, use it - loc := lutils.CommaSeparatedStringContains(string(leaderTracker.Data[lutils.ResourcePathsKey]), validSubPath) + loc := lutils.CommaSeparatedStringContains(string(leaderTracker.Data[leader.ResourcePathsKey]), validSubPath) if loc != -1 { - suffix, _ := lutils.GetCommaSeparatedString(string(leaderTracker.Data[lutils.ResourcesKey]), loc) + suffix, _ := lutils.GetCommaSeparatedString(string(leaderTracker.Data[leader.ResourcesKey]), loc) return suffix } if ltpaResourceType == LTPAKey { // For example, if the env variable LTPA_KEY_RESOURCE_SUFFIXES is set, - // it can provide a comma separated string of length lutils.ResourceSuffixLength suffixes to exhaust + // it can provide a comma separated string of length leader.ResourceSuffixLength suffixes to exhaust // // spec: // env: @@ -151,14 +152,14 @@ func (r *ReconcileWebSphereLiberty) getLTPAMetadataName(instance *wlv1.WebSphere if predeterminedSuffixes, hasEnv := hasLTPAKeyResourceSuffixesEnv(instance); hasEnv { predeterminedSuffixesArray := lutils.GetCommaSeparatedArray(predeterminedSuffixes) for _, suffix := range predeterminedSuffixesArray { - if len(suffix) == lutils.ResourceSuffixLength && lutils.IsLowerAlphanumericSuffix(suffix) && !strings.Contains(string(leaderTracker.Data[lutils.ResourcesKey]), suffix) { + if len(suffix) == leader.ResourceSuffixLength && lutils.IsLowerAlphanumericSuffix(suffix) && !strings.Contains(string(leaderTracker.Data[leader.ResourcesKey]), suffix) { return "-" + suffix } } } } else if ltpaResourceType == LTPAConfig { // For example, if the env variable LTPA_CONFIG_RESOURCE_SUFFIXES is set, - // it can provide a comma separated string of length lutils.ResourceSuffixLength suffixes to exhaust + // it can provide a comma separated string of length leader.ResourceSuffixLength suffixes to exhaust // // spec: // env: @@ -167,20 +168,20 @@ func (r *ReconcileWebSphereLiberty) getLTPAMetadataName(instance *wlv1.WebSphere if predeterminedSuffixes, hasEnv := hasLTPAConfigResourceSuffixesEnv(instance); hasEnv { predeterminedSuffixesArray := lutils.GetCommaSeparatedArray(predeterminedSuffixes) for _, suffix := range predeterminedSuffixesArray { - if len(suffix) == lutils.ResourceSuffixLength && lutils.IsLowerAlphanumericSuffix(suffix) && !strings.Contains(string(leaderTracker.Data[lutils.ResourcesKey]), suffix) { + if len(suffix) == leader.ResourceSuffixLength && lutils.IsLowerAlphanumericSuffix(suffix) && !strings.Contains(string(leaderTracker.Data[leader.ResourcesKey]), suffix) { return "-" + suffix } } } } - // otherwise, generate a random suffix of length lutils.ResourceSuffixLength - randomSuffix := lutils.GetRandomLowerAlphanumericSuffix(lutils.ResourceSuffixLength) + // otherwise, generate a random suffix of length leader.ResourceSuffixLength + randomSuffix := lutils.GetRandomLowerAlphanumericSuffix(leader.ResourceSuffixLength) suffixFoundInCluster := true // MUST check that the operator is not overriding another instance's untracked shared resource - for strings.Contains(string(leaderTracker.Data[lutils.ResourcesKey]), randomSuffix) || suffixFoundInCluster { - randomSuffix = lutils.GetRandomLowerAlphanumericSuffix(lutils.ResourceSuffixLength) + for strings.Contains(string(leaderTracker.Data[leader.ResourcesKey]), randomSuffix) || suffixFoundInCluster { + randomSuffix = lutils.GetRandomLowerAlphanumericSuffix(leader.ResourceSuffixLength) // create the unstructured object; parse and obtain the sharedResourceName via the internal/controller/assets/ltpa-signature.yaml - if sharedResource, sharedResourceName, err := lutils.CreateUnstructuredResourceFromSignature(LTPA_RESOURCE_SHARING_FILE_NAME, assetsFolder, OperatorShortName, randomSuffix); err == nil { + if sharedResource, sharedResourceName, err := leader.CreateUnstructuredResourceFromSignature(LTPA_RESOURCE_SHARING_FILE_NAME, assetsFolder, OperatorShortName, randomSuffix); err == nil { err := r.GetClient().Get(context.TODO(), types.NamespacedName{Namespace: instance.GetNamespace(), Name: sharedResourceName}, sharedResource) if err != nil && kerrors.IsNotFound(err) { suffixFoundInCluster = false @@ -190,27 +191,19 @@ func (r *ReconcileWebSphereLiberty) getLTPAMetadataName(instance *wlv1.WebSphere return randomSuffix } -func hasLTPAKeyResourceSuffixesEnv(instance *wlv1.WebSphereLibertyApplication) (string, bool) { - return hasResourceSuffixesEnv(instance, "LTPA_KEY_RESOURCE_SUFFIXES") -} - -func hasLTPAConfigResourceSuffixesEnv(instance *wlv1.WebSphereLibertyApplication) (string, bool) { - return hasResourceSuffixesEnv(instance, "LTPA_CONFIG_RESOURCE_SUFFIXES") -} - // Create or use an existing LTPA Secret identified by LTPA metadata for the WebSphereLibertyApplication instance -func (r *ReconcileWebSphereLiberty) reconcileLTPAKeys(instance *wlv1.WebSphereLibertyApplication, ltpaKeysMetadata *lutils.LTPAMetadata) (string, string, string, error) { +func (r *ReconcileWebSphereLiberty) reconcileLTPAKeys(rsf tree.ResourceSharingFactory, baseRSF tree.ResourceSharingFactoryBase, instance *wlv1.WebSphereLibertyApplication, ltpaKeysMetadata *leader.LTPAMetadata) (string, string, string, error) { ltpaSecretName := "" ltpaKeysLastRotation := "" if r.isLTPAKeySharingEnabled(instance) { - ltpaSecretNameTemp, ltpaKeysLastRotationTemp, _, err := r.generateLTPAKeys(instance, ltpaKeysMetadata) + ltpaSecretNameTemp, ltpaKeysLastRotationTemp, _, err := r.generateLTPAKeys(rsf, instance, ltpaKeysMetadata) ltpaKeysLastRotation = ltpaKeysLastRotationTemp ltpaSecretName = ltpaSecretNameTemp if err != nil { return "Failed to generate the shared LTPA keys Secret", ltpaSecretName, ltpaKeysLastRotation, err } } else { - err := r.RemoveLeaderTrackerReference(instance, LTPA_RESOURCE_SHARING_FILE_NAME) + err := tree.RemoveLeaderTrackerReference(baseRSF, instance.GetName(), instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME) if err != nil { return "Failed to remove leader tracking reference to the LTPA keys", ltpaSecretName, ltpaKeysLastRotation, err } @@ -219,16 +212,16 @@ func (r *ReconcileWebSphereLiberty) reconcileLTPAKeys(instance *wlv1.WebSphereLi } // Create or use an existing LTPA Secret identified by LTPA metadata for the WebSphereLibertyApplication instance -func (r *ReconcileWebSphereLiberty) reconcileLTPAConfig(instance *wlv1.WebSphereLibertyApplication, ltpaKeysMetadata *lutils.LTPAMetadata, ltpaConfigMetadata *lutils.LTPAMetadata, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata, ltpaKeysLastRotation string, lastKeyRelatedRotation string) (string, string, error) { +func (r *ReconcileWebSphereLiberty) reconcileLTPAConfig(rsf tree.ResourceSharingFactory, baseRSF tree.ResourceSharingFactoryBase, instance *wlv1.WebSphereLibertyApplication, ltpaKeysMetadata *leader.LTPAMetadata, ltpaConfigMetadata *leader.LTPAMetadata, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata, ltpaKeysLastRotation string, lastKeyRelatedRotation string) (string, string, error) { var err error var ltpaXMLSecretName string if r.isLTPAKeySharingEnabled(instance) { - ltpaXMLSecretName, err = r.generateLTPAConfig(instance, ltpaKeysMetadata, ltpaConfigMetadata, passwordEncryptionMetadata, ltpaKeysLastRotation, lastKeyRelatedRotation) + ltpaXMLSecretName, err = r.generateLTPAConfig(rsf, instance, ltpaKeysMetadata, ltpaConfigMetadata, passwordEncryptionMetadata, ltpaKeysLastRotation, lastKeyRelatedRotation) if err != nil { return "Failed to generate the shared LTPA config Secret", ltpaXMLSecretName, err } } else { - err := r.RemoveLeaderTrackerReference(instance, LTPA_RESOURCE_SHARING_FILE_NAME) + err := tree.RemoveLeaderTrackerReference(baseRSF, instance.GetName(), instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME) if err != nil { return "Failed to remove leader tracking reference to the LTPA config", "", err } @@ -237,9 +230,9 @@ func (r *ReconcileWebSphereLiberty) reconcileLTPAConfig(instance *wlv1.WebSphere } // Generates the LTPA keys file and returns the name of the Secret storing its metadata -func (r *ReconcileWebSphereLiberty) generateLTPAKeys(instance *wlv1.WebSphereLibertyApplication, ltpaMetadata *lutils.LTPAMetadata) (string, string, string, error) { +func (r *ReconcileWebSphereLiberty) generateLTPAKeys(rsf tree.ResourceSharingFactory, instance *wlv1.WebSphereLibertyApplication, ltpaMetadata *leader.LTPAMetadata) (string, string, string, error) { // Initialize LTPA resources - passwordEncryptionMetadata := &lutils.PasswordEncryptionMetadata{Name: ""} + passwordEncryptionMetadata := &leader.PasswordEncryptionMetadata{Name: ""} ltpaXMLSecret := &corev1.Secret{} ltpaXMLSecretRootName := OperatorShortName + lutils.LTPAServerXMLSuffix @@ -261,7 +254,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAKeys(instance *wlv1.WebSphereLib // If the LTPA Secret does not exist, run the Kubernetes Job to generate the shared ltpa.keys file and Secret err := r.GetClient().Get(context.TODO(), types.NamespacedName{Name: ltpaSecret.Name, Namespace: ltpaSecret.Namespace}, ltpaSecret) if err != nil && kerrors.IsNotFound(err) { - leaderName, thisInstanceIsLeader, _, err := r.reconcileLeader(instance, ltpaMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, true) + leaderName, thisInstanceIsLeader, _, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), ltpaMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, true) if err != nil { return "", "", leaderName, err } @@ -292,7 +285,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAKeys(instance *wlv1.WebSphereLib return "", "", "", err } - ltpaSecret.Labels[lutils.ResourcePathIndexLabel] = ltpaMetadata.PathIndex + ltpaSecret.Labels[leader.GetResourcePathIndexLabel(lutils.LibertyURI)] = ltpaMetadata.PathIndex ltpaSecret.Data = make(map[string][]byte) if passwordEncryptionKey != "" && encryptionSecretLastRotation != "" { ltpaSecret.Data["encryptionSecretLastRotation"] = []byte(encryptionSecretLastRotation) @@ -311,16 +304,16 @@ func (r *ReconcileWebSphereLiberty) generateLTPAKeys(instance *wlv1.WebSphereLib } else if err != nil { return "", "", "", err } - leaderName, _, _, err := r.reconcileLeader(instance, ltpaMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, true) + leaderName, _, _, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), ltpaMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, true) if err != nil { - return "", "", leaderName, err + return "", "", "", err } lastRotation := string(ltpaSecret.Data["lastRotation"]) return ltpaSecret.Name, lastRotation, leaderName, nil } // Generates the LTPA keys file and returns the name of the Secret storing its metadata -func (r *ReconcileWebSphereLiberty) generateLTPAConfig(instance *wlv1.WebSphereLibertyApplication, ltpaKeysMetadata *lutils.LTPAMetadata, ltpaConfigMetadata *lutils.LTPAMetadata, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata, ltpaKeysLastRotation string, lastKeyRelatedRotation string) (string, error) { +func (r *ReconcileWebSphereLiberty) generateLTPAConfig(rsf tree.ResourceSharingFactory, instance *wlv1.WebSphereLibertyApplication, ltpaKeysMetadata *leader.LTPAMetadata, ltpaConfigMetadata *leader.LTPAMetadata, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata, ltpaKeysLastRotation string, lastKeyRelatedRotation string) (string, error) { ltpaXMLSecret := &corev1.Secret{} ltpaXMLSecretRootName := OperatorShortName + lutils.LTPAServerXMLSuffix ltpaXMLSecret.Name = ltpaXMLSecretRootName + ltpaConfigMetadata.Name @@ -343,7 +336,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAConfig(instance *wlv1.WebSphereL if !kerrors.IsNotFound(err) { return ltpaXMLSecret.Name, err } - leaderName, thisInstanceIsLeader, _, err := r.reconcileLeader(instance, ltpaKeysMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, false) // false, since this function should not elect leader for LTPA keys generation + leaderName, thisInstanceIsLeader, _, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), ltpaConfigMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, false) // false, since this function should not elect leader for LTPA keys generation if err != nil { return ltpaXMLSecret.Name, err } @@ -355,7 +348,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAConfig(instance *wlv1.WebSphereL } // LTPA config leader starts here - leaderName, thisInstanceIsLeader, _, err := r.reconcileLeader(instance, ltpaConfigMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, true) + leaderName, thisInstanceIsLeader, _, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), ltpaConfigMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, true) if err != nil { return ltpaXMLSecret.Name, err } @@ -368,7 +361,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAConfig(instance *wlv1.WebSphereL return ltpaXMLSecret.Name, err } // check that the last rotation label has been set - lastRotationLabel, found := ltpaXMLSecret.Labels[lutils.GetLastRotationLabelKey(LTPA_CONFIG_RESOURCE_SHARING_FILE_NAME)] + lastRotationLabel, found := ltpaXMLSecret.Labels[leader.GetLastRotationLabelKey(LTPA_CONFIG_RESOURCE_SHARING_FILE_NAME, lutils.LibertyURI)] if !found { // the label was not found, but the LTPA config leader is responsible for updating this label return ltpaXMLSecret.Name, fmt.Errorf("Waiting for WebSphereLibertyApplication instance '%s' to update the shared LTPA config for the namespace '%s'.", leaderName, instance.Namespace) @@ -399,7 +392,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAConfig(instance *wlv1.WebSphereL // If the LTPA password Secret does not exist, run the Kubernetes Job to generate the LTPA password Secret err = r.GetClient().Get(context.TODO(), types.NamespacedName{Name: ltpaConfigSecret.Name, Namespace: ltpaConfigSecret.Namespace}, ltpaConfigSecret) if err != nil && kerrors.IsNotFound(err) { - leaderName, thisInstanceIsLeader, _, err := r.reconcileLeader(instance, ltpaConfigMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, true) + leaderName, thisInstanceIsLeader, _, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), ltpaConfigMetadata, LTPA_RESOURCE_SHARING_FILE_NAME, true) if err != nil { return ltpaXMLSecret.Name, err } @@ -426,7 +419,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAConfig(instance *wlv1.WebSphereL ltpaConfigSecret.Data["password"] = []byte(defaultLTPASecretPassword) ltpaConfigSecret.Data["lastRotation"] = []byte(defaultLTPASecretLastRotation) ltpaConfigSecret.Labels = lutils.GetRequiredLabels(ltpaConfigSecretRootName, ltpaConfigSecret.Name) - ltpaConfigSecret.Labels[lutils.ResourcePathIndexLabel] = ltpaConfigMetadata.PathIndex + ltpaConfigSecret.Labels[leader.GetResourcePathIndexLabel(lutils.LibertyURI)] = ltpaConfigMetadata.PathIndex if err := r.CreateOrUpdate(ltpaConfigSecret, nil, func() error { return nil }); err != nil { return ltpaXMLSecret.Name, err } @@ -454,7 +447,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAConfig(instance *wlv1.WebSphereL return "", err } - ltpaConfigSecret.Labels[lutils.ResourcePathIndexLabel] = ltpaConfigMetadata.PathIndex + ltpaConfigSecret.Labels[leader.GetResourcePathIndexLabel(lutils.LibertyURI)] = ltpaConfigMetadata.PathIndex ltpaConfigSecret.Data = make(map[string][]byte) if passwordEncryptionKey != "" && encryptionSecretLastRotation != "" { ltpaConfigSecret.Data["encryptionKeyLastRotation"] = []byte(encryptionSecretLastRotation) @@ -549,7 +542,7 @@ func (r *ReconcileWebSphereLiberty) generateLTPAConfig(instance *wlv1.WebSphereL latestRotationTime = encryptionKeyLastRotationTime } } - ltpaXMLSecret.Labels[lutils.GetLastRotationLabelKey(LTPA_CONFIG_RESOURCE_SHARING_FILE_NAME)] = strconv.Itoa(latestRotationTime) + ltpaXMLSecret.Labels[leader.GetLastRotationLabelKey(LTPA_CONFIG_RESOURCE_SHARING_FILE_NAME, lutils.LibertyURI)] = strconv.Itoa(latestRotationTime) return lutils.CustomizeLTPAServerXML(ltpaXMLSecret, instance, string(ltpaConfigSecret.Data["password"])) }); err != nil { return ltpaXMLSecret.Name, err @@ -566,7 +559,7 @@ func (r *ReconcileWebSphereLiberty) isLTPAKeySharingEnabled(instance *wlv1.WebSp // Search the cluster namespace for existing LTPA keys func (r *ReconcileWebSphereLiberty) GetLTPAKeyResources(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, assetsFolder *string) (*unstructured.UnstructuredList, string, error) { - ltpaResourceList, ltpaResourceRootName, err := lutils.CreateUnstructuredResourceListFromSignature(LTPA_KEY_RESOURCE_SHARING_FILE_NAME, assetsFolder, OperatorShortName) + ltpaResourceList, ltpaResourceRootName, err := leader.CreateUnstructuredResourceListFromSignature(LTPA_KEY_RESOURCE_SHARING_FILE_NAME, assetsFolder, OperatorShortName) if err != nil { return nil, "", err } @@ -577,7 +570,7 @@ func (r *ReconcileWebSphereLiberty) GetLTPAKeyResources(instance *wlv1.WebSphere } // check once for an unlabeled default LTPA key to append if defaultLTPAKeyIndex := defaultLTPAKeyExists(ltpaResourceList, ltpaResourceRootName); defaultLTPAKeyIndex == -1 { - defaultLTPAKeySecret, _, err := lutils.CreateUnstructuredResourceFromSignature(LTPA_RESOURCE_SHARING_FILE_NAME, assetsFolder, OperatorShortName, "") + defaultLTPAKeySecret, _, err := leader.CreateUnstructuredResourceFromSignature(LTPA_RESOURCE_SHARING_FILE_NAME, assetsFolder, OperatorShortName, "") defaultLTPAKeySecret.SetName(ltpaResourceRootName) defaultLTPAKeySecret.SetNamespace(instance.GetNamespace()) if err != nil { @@ -610,7 +603,7 @@ func (r *ReconcileWebSphereLiberty) GetLTPAKeyResources(instance *wlv1.WebSphere if labelsMap == nil { labelsMap = make(map[string]interface{}) } - labelsMap[lutils.ResourcePathIndexLabel] = defaultUpdatedPathIndex + labelsMap[leader.GetResourcePathIndexLabel(lutils.LibertyURI)] = defaultUpdatedPathIndex if err := unstructured.SetNestedMap(ltpaResourceList.Items[defaultLTPAKeyIndex].Object, labelsMap, "metadata", "labels"); err != nil { return err } @@ -631,7 +624,7 @@ func (r *ReconcileWebSphereLiberty) HasDefaultLTPAKeyCollision(ltpaResourceList if err != nil { return false, err } - if pathIndexInterface, found := labelsMap[lutils.ResourcePathIndexLabel]; found { + if pathIndexInterface, found := labelsMap[leader.GetResourcePathIndexLabel(lutils.LibertyURI)]; found { pathIndex := pathIndexInterface.(string) // Skip this resource if path index does not contain a period separating delimeter if !strings.Contains(pathIndex, ".") { @@ -668,7 +661,7 @@ func (r *ReconcileWebSphereLiberty) HasDefaultLTPAKeyCollision(ltpaResourceList // Search the cluster namespace for existing LTPA password Secrets func (r *ReconcileWebSphereLiberty) GetLTPAConfigResources(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, assetsFolder *string, fileName string) (*unstructured.UnstructuredList, string, error) { - ltpaResourceList, ltpaResourceRootName, err := lutils.CreateUnstructuredResourceListFromSignature(fileName, assetsFolder, OperatorShortName) + ltpaResourceList, ltpaResourceRootName, err := leader.CreateUnstructuredResourceListFromSignature(fileName, assetsFolder, OperatorShortName) if err != nil { return nil, "", err } diff --git a/internal/controller/ltpa_keys_sharing_test.go b/internal/controller/ltpa_keys_sharing_test.go index bddf3562..777c94c4 100644 --- a/internal/controller/ltpa_keys_sharing_test.go +++ b/internal/controller/ltpa_keys_sharing_test.go @@ -23,8 +23,9 @@ import ( "strings" "testing" + leader "github.com/OpenLiberty/open-liberty-operator/utils/leader" tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" - wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" + webspherelibertyv1 "github.com/WASdev/websphere-liberty-operator/api/v1" lutils "github.com/WASdev/websphere-liberty-operator/utils" oputils "github.com/application-stacks/runtime-component-operator/utils" corev1 "k8s.io/api/core/v1" @@ -58,7 +59,7 @@ func TestIsLTPAKeySharingEnabled(t *testing.T) { os.Setenv("WATCH_NAMESPACE", namespace) // Test default values no config - spec := wlv1.WebSphereLibertyApplicationSpec{} + spec := webspherelibertyv1.WebSphereLibertyApplicationSpec{} // Create Liberty app instance := createWebSphereLibertyApp(name, namespace, spec) @@ -115,23 +116,23 @@ func TestLTPALeaderTracker(t *testing.T) { os.Setenv("WATCH_NAMESPACE", namespace) // Test default values no config - spec := wlv1.WebSphereLibertyApplicationSpec{} + spec := webspherelibertyv1.WebSphereLibertyApplicationSpec{} // Create Liberty app instance := createWebSphereLibertyApp(name, namespace, spec) r := createReconcilerFromWebSphereLibertyApp(instance) // First, get the LTPA leader tracker which is not initialized - leaderTracker, _, err := lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) - + leaderTracker, _, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTrackerName := OperatorShortName + "-managed-leader-tracking-" + LTPA_RESOURCE_SHARING_FILE_NAME emptyLeaderTracker := &corev1.Secret{ ObjectMeta: metav1.ObjectMeta{ - Name: "wlo-managed-leader-tracking-ltpa", + Name: leaderTrackerName, Namespace: namespace, Labels: map[string]string{ - "app.kubernetes.io/instance": "wlo-managed-leader-tracking-ltpa", - "app.kubernetes.io/managed-by": "websphere-liberty-operator", - "app.kubernetes.io/name": "wlo-managed-leader-tracking-ltpa", + "app.kubernetes.io/instance": leaderTrackerName, + "app.kubernetes.io/managed-by": OperatorName, + "app.kubernetes.io/name": leaderTrackerName, }, }, } @@ -155,7 +156,8 @@ func TestLTPALeaderTracker(t *testing.T) { } assetsFolder := getAssetsFolder() - err = r.reconcileLeaderTracker(instance, treeMap, replaceMap, "v10_4_1", LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder) + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, "v10_4_1", LTPA_RESOURCE_SHARING_FILE_NAME) + err = tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder) tests = []Test{ {"initialize LTPA leader tracker", nil, err}, } @@ -163,17 +165,17 @@ func TestLTPALeaderTracker(t *testing.T) { t.Fatalf("%v", err) } - leaderTracker, _, err = lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTracker, _, err = leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) expectedLeaderTrackerData := map[string][]byte{} - expectedLeaderTrackerData[lutils.ResourcesKey] = []byte("") - expectedLeaderTrackerData[lutils.ResourceOwnersKey] = []byte("") - expectedLeaderTrackerData[lutils.ResourcePathsKey] = []byte("") - expectedLeaderTrackerData[lutils.ResourcePathIndicesKey] = []byte("") + expectedLeaderTrackerData[leader.ResourcesKey] = []byte("") + expectedLeaderTrackerData[leader.ResourceOwnersKey] = []byte("") + expectedLeaderTrackerData[leader.ResourcePathsKey] = []byte("") + expectedLeaderTrackerData[leader.ResourcePathIndicesKey] = []byte("") tests = []Test{ - {"get LTPA leader tracker name", "wlo-managed-leader-tracking-ltpa", leaderTracker.Name}, + {"get LTPA leader tracker name", leaderTrackerName, leaderTracker.Name}, {"get LTPA leader tracker namespace", namespace, leaderTracker.Namespace}, {"get LTPA leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, - {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[lutils.LeaderVersionLabel]}, + {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, {"get LTPA leader tracker error", nil, err}, } if err := verifyTests(tests); err != nil { @@ -186,7 +188,7 @@ func TestLTPALeaderTracker(t *testing.T) { Name: "wlo-managed-ltpa-ab215", Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: latestOperandVersion + ".2", // choosing path index 2 under tree v10_4_1 (i.e. v10_4_1.a.b.e.true) + leader.GetResourcePathIndexLabel(lutils.LibertyURI): latestOperandVersion + ".2", // choosing path index 2 under tree v10_4_1 (i.e. v10_4_1.a.b.e.true) }, }, Data: map[string][]byte{}, // create empty data @@ -201,7 +203,7 @@ func TestLTPALeaderTracker(t *testing.T) { } // Mock the process where the operator saves the LTPA Secret, storing it into the leader tracker - leaderName, isLeader, pathIndex, err := r.reconcileLeader(instance, &lutils.LTPAMetadata{ + leaderName, isLeader, pathIndex, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), &leader.LTPAMetadata{ Path: latestOperandVersion + ".a.b.e.true", PathIndex: latestOperandVersion + ".2", Name: "-ab215", @@ -217,18 +219,18 @@ func TestLTPALeaderTracker(t *testing.T) { } // Fourth, check that the leader tracker received the new LTPA state - leaderTracker, leaderTrackers, err := lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) expectedLeaderTrackerData = map[string][]byte{ - lutils.ResourcesKey: []byte("-ab215"), - lutils.ResourceOwnersKey: []byte(name), - lutils.ResourcePathsKey: []byte(latestOperandVersion + ".a.b.e.true"), - lutils.ResourcePathIndicesKey: []byte(latestOperandVersion + ".2"), + leader.ResourcesKey: []byte("-ab215"), + leader.ResourceOwnersKey: []byte(name), + leader.ResourcePathsKey: []byte(latestOperandVersion + ".a.b.e.true"), + leader.ResourcePathIndicesKey: []byte(latestOperandVersion + ".2"), } tests = []Test{ - {"get LTPA leader tracker name", "wlo-managed-leader-tracking-ltpa", leaderTracker.Name}, + {"get LTPA leader tracker name", leaderTrackerName, leaderTracker.Name}, {"get LTPA leader tracker namespace", namespace, leaderTracker.Namespace}, {"get LTPA leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, - {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[lutils.LeaderVersionLabel]}, + {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, {"get LTPA leader tracker error", nil, err}, } if err := verifyTests(tests); err != nil { @@ -241,7 +243,7 @@ func TestLTPALeaderTracker(t *testing.T) { Name: "wlo-managed-ltpa-cd123", Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: latestOperandVersion + ".1", + leader.GetResourcePathIndexLabel(lutils.LibertyURI): latestOperandVersion + ".1", }, }, Data: map[string][]byte{}, // create empty data @@ -256,25 +258,25 @@ func TestLTPALeaderTracker(t *testing.T) { } // Mock the process where the operator saves the LTPA Secret, storing it into the leader tracker - r.reconcileLeader(instance, &lutils.LTPAMetadata{ + tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), &leader.LTPAMetadata{ Path: latestOperandVersion + ".a.b.d.true", PathIndex: latestOperandVersion + ".1", Name: "-cd123", }, LTPA_RESOURCE_SHARING_FILE_NAME, true) // Sixth, check that the LTPA leader tracker was updated - leaderTracker, _, err = lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTracker, _, err = leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) expectedLeaderTrackerData = map[string][]byte{ - lutils.ResourcesKey: []byte("-ab215,-cd123"), - lutils.ResourceOwnersKey: []byte(fmt.Sprintf("%s,%s", instance.Name, instance.Name)), - lutils.ResourcePathsKey: []byte(fmt.Sprintf("%s.a.b.e.true,%s.a.b.d.true", latestOperandVersion, latestOperandVersion)), - lutils.ResourcePathIndicesKey: []byte(fmt.Sprintf("%s.2,%s.1", latestOperandVersion, latestOperandVersion)), + leader.ResourcesKey: []byte("-ab215,-cd123"), + leader.ResourceOwnersKey: []byte(fmt.Sprintf("%s,%s", instance.Name, instance.Name)), + leader.ResourcePathsKey: []byte(fmt.Sprintf("%s.a.b.e.true,%s.a.b.d.true", latestOperandVersion, latestOperandVersion)), + leader.ResourcePathIndicesKey: []byte(fmt.Sprintf("%s.2,%s.1", latestOperandVersion, latestOperandVersion)), } tests = []Test{ - {"get LTPA leader tracker name", "wlo-managed-leader-tracking-ltpa", leaderTracker.Name}, + {"get LTPA leader tracker name", leaderTrackerName, leaderTracker.Name}, {"get LTPA leader tracker namespace", namespace, leaderTracker.Namespace}, {"get LTPA leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, - {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[lutils.LeaderVersionLabel]}, + {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, {"get LTPA leader tracker error", nil, err}, } if err := verifyTests(tests); err != nil { @@ -282,10 +284,10 @@ func TestLTPALeaderTracker(t *testing.T) { } // Lastly, remove the LTPA leader - err1 = r.RemoveLeaderTrackerReference(instance, LTPA_RESOURCE_SHARING_FILE_NAME) - err2 = r.RemoveLeader(instance, leaderTracker, leaderTrackers, LTPA_RESOURCE_SHARING_FILE_NAME) - _, leaderTrackers, leaderTrackerErr := lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) - var nilLeaderTrackers *[]lutils.LeaderTracker + err1 = tree.RemoveLeaderTrackerReference(rsf, instance.GetName(), instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME) + err2 = tree.RemoveLeader(instance.GetName(), rsf, leaderTracker, leaderTrackers) + _, leaderTrackers, leaderTrackerErr := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + var nilLeaderTrackers *[]leader.LeaderTracker tests = []Test{ {"remove LTPA - deleteLTPAKeysResource errors", nil, err1}, {"remove LTPA - RemoveLeader errors", nil, err2}, @@ -304,7 +306,7 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExist(t *testing.T) { os.Setenv("WATCH_NAMESPACE", namespace) // Test default values no config - spec := wlv1.WebSphereLibertyApplicationSpec{} + spec := webspherelibertyv1.WebSphereLibertyApplicationSpec{} // Create Liberty app instance := createWebSphereLibertyApp(name, namespace, spec) @@ -328,8 +330,8 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExist(t *testing.T) { Name: ltpaRootName + "-b12g1", // random lower alphanumeric suffix of length 5 Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: latestOperandVersion + ".2", // choosing path index 2 under tree v10_4_1 (i.e. v10_4_1.a.b.e.true) - "app.kubernetes.io/name": ltpaRootName, + leader.GetResourcePathIndexLabel(lutils.LibertyURI): latestOperandVersion + ".2", // choosing path index 2 under tree v10_4_1 (i.e. v10_4_1.a.b.e.true) + "app.kubernetes.io/name": ltpaRootName, }, }, Data: map[string][]byte{}, // create empty data @@ -339,8 +341,8 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExist(t *testing.T) { Name: ltpaRootName + "-bazc1", // random lower alphanumeric suffix of length 5 Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: latestOperandVersion + ".3", // choosing path index 3 under tree v10_4_1 (i.e. v10_4_1.a.b.e.false) - "app.kubernetes.io/name": ltpaRootName, + leader.GetResourcePathIndexLabel(lutils.LibertyURI): latestOperandVersion + ".3", // choosing path index 3 under tree v10_4_1 (i.e. v10_4_1.a.b.e.false) + "app.kubernetes.io/name": ltpaRootName, }, }, Data: map[string][]byte{}, // create empty data @@ -355,27 +357,28 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExist(t *testing.T) { // Second, initialize the LTPA leader tracker assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME) tests = []Test{ - {"initialize LTPA leader tracker error", nil, r.reconcileLeaderTracker(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + {"initialize LTPA leader tracker error", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, } if err := verifyTests(tests); err != nil { t.Fatalf("%v", err) } // Lastly, check that the LTPA leader tracker processes the two LTPA Secrets created - leaderTracker, _, err := lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTracker, _, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) expectedLeaderTrackerData := map[string][]byte{ - lutils.ResourcesKey: []byte("-b12g1,-bazc1"), - lutils.ResourceOwnersKey: []byte(","), // no owners associated with the LTPA Secrets because this decision tree (only for test) is not registered to use with the operator - lutils.ResourcePathsKey: []byte("v10_4_1.a.b.e.true,v10_4_1.a.b.e.false"), - lutils.ResourcePathIndicesKey: []byte("v10_4_1.2,v10_4_1.3"), + leader.ResourcesKey: []byte("-b12g1,-bazc1"), + leader.ResourceOwnersKey: []byte(","), // no owners associated with the LTPA Secrets because this decision tree (only for test) is not registered to use with the operator + leader.ResourcePathsKey: []byte("v10_4_1.a.b.e.true,v10_4_1.a.b.e.false"), + leader.ResourcePathIndicesKey: []byte("v10_4_1.2,v10_4_1.3"), } tests = []Test{ {"get LTPA leader tracker error", nil, err}, {"get LTPA leader tracker name", "wlo-managed-leader-tracking-ltpa", leaderTracker.Name}, {"get LTPA leader tracker namespace", namespace, leaderTracker.Namespace}, {"get LTPA leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, - {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[lutils.LeaderVersionLabel]}, + {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, } if err := verifyTests(tests); err != nil { t.Fatalf("%v", err) @@ -384,7 +387,7 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExist(t *testing.T) { // This tests that the LTPA leader tracker can have cluster awareness of LTPA Secrets before operator reconciliation and upgrade the LTPA Secrets to the latest decision tree version func TestReconcileLeaderTrackerWhenLTPASecretsExistWithUpgrade(t *testing.T) { - spec := wlv1.WebSphereLibertyApplicationSpec{} + spec := webspherelibertyv1.WebSphereLibertyApplicationSpec{} instance := createWebSphereLibertyApp(name, namespace, spec) r := createReconcilerFromWebSphereLibertyApp(instance) @@ -405,8 +408,8 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithUpgrade(t *testing.T) { Name: ltpaRootName + "-b12g1", // random lower alphanumeric suffix of length 5 Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: latestOperandVersion + ".2", // choosing path index 2 under tree v10_4_1 (i.e. v10_4_1.a.b.e.true) - "app.kubernetes.io/name": ltpaRootName, + leader.GetResourcePathIndexLabel(lutils.LibertyURI): latestOperandVersion + ".2", // choosing path index 2 under tree v10_4_1 (i.e. v10_4_1.a.b.e.true) + "app.kubernetes.io/name": ltpaRootName, }, }, Data: map[string][]byte{}, // create empty data @@ -416,8 +419,8 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithUpgrade(t *testing.T) { Name: ltpaRootName + "-bazc1", // random lower alphanumeric suffix of length 5 Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: latestOperandVersion + ".3", // choosing path index 3 under tree v10_4_1 (i.e. v10_4_1.a.b.e.false) - "app.kubernetes.io/name": ltpaRootName, + leader.GetResourcePathIndexLabel(lutils.LibertyURI): latestOperandVersion + ".3", // choosing path index 3 under tree v10_4_1 (i.e. v10_4_1.a.b.e.false) + "app.kubernetes.io/name": ltpaRootName, }, }, Data: map[string][]byte{}, // create empty data @@ -433,26 +436,27 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithUpgrade(t *testing.T) { // Second, initialize the leader tracker but on a higher version of the LTPA decision tree latestOperandVersion = "v10_4_20" // upgrade the version assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME) tests = []Test{ - {"reconcileLeaderTracker at version v10_4_20", nil, r.reconcileLeaderTracker(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + {"reconcileLeaderTracker at version v10_4_20", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, } if err := verifyTests(tests); err != nil { t.Fatalf("%v", err) } // Lastly, check that the LTPA leader tracker upgraded the two LTPA Secrets created - leaderTracker, _, err := lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTracker, _, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) expectedLeaderTrackerData := map[string][]byte{ - lutils.ResourcesKey: []byte("-b12g1,-bazc1"), - lutils.ResourceOwnersKey: []byte(","), // no owners associated with the LTPA Secrets because this decision tree (only for test) is not registered to use with the operator - lutils.ResourcePathsKey: []byte("v10_4_20.a.b.e.foo,v10_4_20.a.f.g.i.bar"), // These paths have been upgraded to v10_4_20 based on replaceMap - lutils.ResourcePathIndicesKey: []byte("v10_4_20.2,v10_4_20.3"), // These path indices have been upgraded to v10_4_20 based on replaceMap + leader.ResourcesKey: []byte("-b12g1,-bazc1"), + leader.ResourceOwnersKey: []byte(","), // no owners associated with the LTPA Secrets because this decision tree (only for test) is not registered to use with the operator + leader.ResourcePathsKey: []byte("v10_4_20.a.b.e.foo,v10_4_20.a.f.g.i.bar"), // These paths have been upgraded to v10_4_20 based on replaceMap + leader.ResourcePathIndicesKey: []byte("v10_4_20.2,v10_4_20.3"), // These path indices have been upgraded to v10_4_20 based on replaceMap } tests = []Test{ {"get LTPA leader tracker name", "wlo-managed-leader-tracking-ltpa", leaderTracker.Name}, {"get LTPA leader tracker namespace", namespace, leaderTracker.Namespace}, {"get LTPA leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, - {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[lutils.LeaderVersionLabel]}, + {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, {"get LTPA leader tracker error", nil, err}, } if err := verifyTests(tests); err != nil { @@ -462,7 +466,7 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithUpgrade(t *testing.T) { // This tests that the LTPA leader tracker can have cluster awareness of LTPA Secrets before operator reconciliation and upgrade the LTPA Secrets to the latest decision tree version func TestReconcileLeaderTrackerWhenLTPASecretsExistWithMultipleUpgradesAndDowngrades(t *testing.T) { - spec := wlv1.WebSphereLibertyApplicationSpec{} + spec := webspherelibertyv1.WebSphereLibertyApplicationSpec{} instance := createWebSphereLibertyApp(name, namespace, spec) r := createReconcilerFromWebSphereLibertyApp(instance) @@ -483,8 +487,8 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithMultipleUpgradesAndDowngr Name: ltpaRootName + "-b12g1", // random lower alphanumeric suffix of length 5 Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: "v10_4_1.2", // choosing path index 2 under tree v10_4_1 (i.e. v10_4_1.a.b.e.true) - "app.kubernetes.io/name": ltpaRootName, + leader.GetResourcePathIndexLabel(lutils.LibertyURI): "v10_4_1.2", // choosing path index 2 under tree v10_4_1 (i.e. v10_4_1.a.b.e.true) + "app.kubernetes.io/name": ltpaRootName, }, }, Data: map[string][]byte{}, // create empty data @@ -494,8 +498,8 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithMultipleUpgradesAndDowngr Name: ltpaRootName + "-bazc1", // random lower alphanumeric suffix of length 5 Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: "v10_4_1.3", // choosing path index 3 under tree v10_4_1 (i.e. v10_4_1.a.b.e.false) - "app.kubernetes.io/name": ltpaRootName, + leader.GetResourcePathIndexLabel(lutils.LibertyURI): "v10_4_1.3", // choosing path index 3 under tree v10_4_1 (i.e. v10_4_1.a.b.e.false) + "app.kubernetes.io/name": ltpaRootName, }, }, Data: map[string][]byte{}, // create empty data @@ -505,8 +509,8 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithMultipleUpgradesAndDowngr Name: ltpaRootName + "-ccccc", // random lower alphanumeric suffix of length 5 Namespace: namespace, Labels: map[string]string{ - lutils.ResourcePathIndexLabel: "v10_4_1.4", // choosing path index 4 under tree v10_4_1 (i.e. v10_4_1.j.fizz) - "app.kubernetes.io/name": ltpaRootName, + leader.GetResourcePathIndexLabel(lutils.LibertyURI): "v10_4_1.4", // choosing path index 4 under tree v10_4_1 (i.e. v10_4_1.j.fizz) + "app.kubernetes.io/name": ltpaRootName, }, }, Data: map[string][]byte{}, // create empty data @@ -523,26 +527,27 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithMultipleUpgradesAndDowngr // Second, initialize the leader tracker but on a higher version of the LTPA decision tree latestOperandVersion = "v10_4_500" // upgrade the version assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME) tests = []Test{ - {"reconcileLeaderTracker at version v10_4_500", nil, r.reconcileLeaderTracker(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + {"reconcileLeaderTracker at version v10_4_500", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, } if err := verifyTests(tests); err != nil { t.Fatalf("%v", err) } // Thirdly, check that the LTPA leader tracker upgraded the two LTPA Secrets created - leaderTracker, _, err := lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTracker, _, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) expectedLeaderTrackerData := map[string][]byte{ - lutils.ResourcesKey: []byte("-b12g1,-bazc1,-ccccc"), - lutils.ResourceOwnersKey: []byte(",,"), // no owners associated with the LTPA Secrets because this decision tree (only for test) is not registered to use with the operator - lutils.ResourcePathsKey: []byte("v10_4_500.a.b.b.true,v10_4_500.a.f.g.i.bar,v10_4_1.j.fizz"), // These paths have been upgraded to v10_4_500 based on replaceMap - lutils.ResourcePathIndicesKey: []byte("v10_4_500.0,v10_4_500.4,v10_4_1.4"), // These path indices have been upgraded to v10_4_500 based on replaceMap + leader.ResourcesKey: []byte("-b12g1,-bazc1,-ccccc"), + leader.ResourceOwnersKey: []byte(",,"), // no owners associated with the LTPA Secrets because this decision tree (only for test) is not registered to use with the operator + leader.ResourcePathsKey: []byte("v10_4_500.a.b.b.true,v10_4_500.a.f.g.i.bar,v10_4_1.j.fizz"), // These paths have been upgraded to v10_4_500 based on replaceMap + leader.ResourcePathIndicesKey: []byte("v10_4_500.0,v10_4_500.4,v10_4_1.4"), // These path indices have been upgraded to v10_4_500 based on replaceMap } tests = []Test{ {"get LTPA leader tracker name", "wlo-managed-leader-tracking-ltpa", leaderTracker.Name}, {"get LTPA leader tracker namespace", namespace, leaderTracker.Namespace}, {"get LTPA leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, - {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[lutils.LeaderVersionLabel]}, + {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, {"get LTPA leader tracker error", nil, err}, } if err := verifyTests(tests); err != nil { @@ -551,37 +556,39 @@ func TestReconcileLeaderTrackerWhenLTPASecretsExistWithMultipleUpgradesAndDowngr // Fourthly, downgrade the decision tree version and initialize the leader tracker (run initialize once to delete the old configMap) latestOperandVersion = "v10_3_3" + rsf = r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME) tests = []Test{ - {"Downgrade LTPA Leader Tracker from v10_4_500 to v10_3_3", nil, r.reconcileLeaderTracker(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + {"Downgrade LTPA Leader Tracker from v10_4_500 to v10_3_3", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, } if err := verifyTests(tests); err != nil { t.Fatalf("%v", err) } - r.reconcileLeaderTracker(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder) + rsf = r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME) + tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, LTPA_RESOURCE_SHARING_FILE_NAME, &assetsFolder) - leaderTracker, _, err = lutils.GetLeaderTracker(instance, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leaderTracker, _, err = leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME, r.GetClient()) expectedLeaderTrackerData = map[string][]byte{ - lutils.ResourcesKey: []byte("-b12g1,-bazc1,-ccccc"), - lutils.ResourceOwnersKey: []byte(",,"), // no owners associated with the LTPA Secrets because this decision tree (only for test) is not registered to use with the operator - lutils.ResourcePathsKey: []byte("v10_3_3.a.b,v10_4_1.a.b.e.false,v10_4_1.j.fizz"), // v10_4_1 has no path to v10_3_3 so it is kept to be reference for a future upgrade - lutils.ResourcePathIndicesKey: []byte("v10_3_3.0,v10_4_1.3,v10_4_1.4"), // These path indices have been upgraded to v10_4_500 based on replaceMap + leader.ResourcesKey: []byte("-b12g1,-bazc1,-ccccc"), + leader.ResourceOwnersKey: []byte(",,"), // no owners associated with the LTPA Secrets because this decision tree (only for test) is not registered to use with the operator + leader.ResourcePathsKey: []byte("v10_3_3.a.b,v10_4_1.a.b.e.false,v10_4_1.j.fizz"), // v10_4_1 has no path to v10_3_3 so it is kept to be reference for a future upgrade + leader.ResourcePathIndicesKey: []byte("v10_3_3.0,v10_4_1.3,v10_4_1.4"), // These path indices have been upgraded to v10_4_500 based on replaceMap } tests = []Test{ {"get LTPA leader tracker error", nil, err}, {"get LTPA leader tracker name", "wlo-managed-leader-tracking-ltpa", leaderTracker.Name}, {"get LTPA leader tracker namespace", namespace, leaderTracker.Namespace}, {"get LTPA leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, - {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[lutils.LeaderVersionLabel]}, + {"get LTPA leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, } if err := verifyTests(tests); err != nil { t.Fatalf("%v", err) } } -func createReconcilerFromWebSphereLibertyApp(wlapp *wlv1.WebSphereLibertyApplication) *ReconcileWebSphereLiberty { - objs, s := []runtime.Object{wlapp}, scheme.Scheme - s.AddKnownTypes(wlv1.GroupVersion, wlapp) +func createReconcilerFromWebSphereLibertyApp(olapp *webspherelibertyv1.WebSphereLibertyApplication) *ReconcileWebSphereLiberty { + objs, s := []runtime.Object{olapp}, scheme.Scheme + s.AddKnownTypes(webspherelibertyv1.GroupVersion, olapp) cl := fakeclient.NewFakeClient(objs...) rcl := fakeclient.NewFakeClient(objs...) rb := oputils.NewReconcilerBase(rcl, cl, s, &rest.Config{}, record.NewFakeRecorder(10)) @@ -591,8 +598,8 @@ func createReconcilerFromWebSphereLibertyApp(wlapp *wlv1.WebSphereLibertyApplica return rol } -func createWebSphereLibertyApp(n, ns string, spec wlv1.WebSphereLibertyApplicationSpec) *wlv1.WebSphereLibertyApplication { - app := &wlv1.WebSphereLibertyApplication{ +func createWebSphereLibertyApp(n, ns string, spec webspherelibertyv1.WebSphereLibertyApplicationSpec) *webspherelibertyv1.WebSphereLibertyApplication { + app := &webspherelibertyv1.WebSphereLibertyApplication{ ObjectMeta: metav1.ObjectMeta{Name: n, Namespace: ns}, Spec: spec, } diff --git a/internal/controller/password_encryption_key_sharing.go b/internal/controller/password_encryption_key_sharing.go index 20c0ae66..af557f9d 100644 --- a/internal/controller/password_encryption_key_sharing.go +++ b/internal/controller/password_encryption_key_sharing.go @@ -23,6 +23,7 @@ import ( "sync" "time" + "github.com/OpenLiberty/open-liberty-operator/utils/leader" tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" lutils "github.com/WASdev/websphere-liberty-operator/utils" @@ -36,12 +37,12 @@ import ( const PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME = "password-encryption" func init() { - lutils.LeaderTrackerMutexes.Store(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, &sync.Mutex{}) + leader.LeaderTrackerMutexes.Store(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, &sync.Mutex{}) } -func (r *ReconcileWebSphereLiberty) reconcilePasswordEncryptionKey(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata) (string, string, string, error) { +func (r *ReconcileWebSphereLiberty) reconcilePasswordEncryptionKey(rsf tree.ResourceSharingFactory, baseRSF tree.ResourceSharingFactoryBase, instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) (string, string, string, error) { if r.isPasswordEncryptionKeySharingEnabled(instance) { - leaderName, thisInstanceIsLeader, _, err := r.reconcileLeader(instance, passwordEncryptionMetadata, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, true) + leaderName, thisInstanceIsLeader, _, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), passwordEncryptionMetadata, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, true) if err != nil && !kerrors.IsNotFound(err) { return "", "", "", err } @@ -76,7 +77,7 @@ func (r *ReconcileWebSphereLiberty) reconcilePasswordEncryptionKey(instance *wlv return "Failed to get the password encryption key Secret", "", "", err } } else { - err := r.RemoveLeaderTrackerReference(instance, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME) + err := tree.RemoveLeaderTrackerReference(baseRSF, instance.GetName(), instance.GetNamespace(), OperatorName, OperatorShortName, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME) if err != nil { return "Failed to remove leader tracking reference to the password encryption key", "", "", err } @@ -84,13 +85,13 @@ func (r *ReconcileWebSphereLiberty) reconcilePasswordEncryptionKey(instance *wlv return "", "", "", nil } -func (r *ReconcileWebSphereLiberty) reconcilePasswordEncryptionMetadata(treeMap map[string]interface{}, latestOperandVersion string) (lutils.LeaderTrackerMetadataList, error) { - metadataList := &lutils.PasswordEncryptionMetadataList{} - metadataList.Items = []lutils.LeaderTrackerMetadata{} +func (r *ReconcileWebSphereLiberty) reconcilePasswordEncryptionMetadata(treeMap map[string]interface{}, latestOperandVersion string) (leader.LeaderTrackerMetadataList, error) { + metadataList := &leader.PasswordEncryptionMetadataList{} + metadataList.Items = []leader.LeaderTrackerMetadata{} pathOptionsList, pathChoicesList := r.getPasswordEncryptionPathOptionsAndChoices(latestOperandVersion) for i := range pathOptionsList { - metadata := &lutils.PasswordEncryptionMetadata{} + metadata := &leader.PasswordEncryptionMetadata{} pathOptions := pathOptionsList[i] pathChoices := pathChoicesList[i] @@ -112,12 +113,12 @@ func (r *ReconcileWebSphereLiberty) reconcilePasswordEncryptionMetadata(treeMap // Uncomment code below to extend to multiple password encryption keys per namespace. See ltpa_keys_sharing.go for an example. // // retrieve the password encryption leader tracker to re-use an existing name or to create a new metadata.Name - // leaderTracker, _, err := lutils.GetLeaderTracker(instance, OperatorShortName, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + // leaderTracker, _, err := lutils.GetLeaderTracker(instance.GetNamespace(), OperatorShortName, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, r.GetClient()) // if err != nil { // return metadataList, err // } // // if the leaderTracker is on a mismatched version, wait for a subsequent reconcile loop to re-create the leader tracker - // if leaderTracker.Labels[lutils.LeaderVersionLabel] != latestOperandVersion { + // if leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)] != latestOperandVersion { // return metadataList, fmt.Errorf("waiting for the Leader Tracker to be updated") // } @@ -160,7 +161,7 @@ func (r *ReconcileWebSphereLiberty) isPasswordEncryptionKeySharingEnabled(instan return instance.GetManagePasswordEncryption() != nil && *instance.GetManagePasswordEncryption() } -func (r *ReconcileWebSphereLiberty) isUsingPasswordEncryptionKeySharing(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata) bool { +func (r *ReconcileWebSphereLiberty) isUsingPasswordEncryptionKeySharing(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) bool { if r.isPasswordEncryptionKeySharingEnabled(instance) { _, err := r.hasUserEncryptionKeySecret(instance, passwordEncryptionMetadata) return err == nil @@ -168,7 +169,7 @@ func (r *ReconcileWebSphereLiberty) isUsingPasswordEncryptionKeySharing(instance return false } -func (r *ReconcileWebSphereLiberty) getInternalPasswordEncryptionKeyState(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata) (string, string, bool, error) { +func (r *ReconcileWebSphereLiberty) getInternalPasswordEncryptionKeyState(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) (string, string, bool, error) { if !r.isPasswordEncryptionKeySharingEnabled(instance) { return "", "", false, nil } @@ -189,16 +190,16 @@ func (r *ReconcileWebSphereLiberty) getInternalPasswordEncryptionKeyState(instan } // Returns the Secret that contains the password encryption key used internally by the operator -func (r *ReconcileWebSphereLiberty) hasInternalEncryptionKeySecret(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata) (*corev1.Secret, error) { +func (r *ReconcileWebSphereLiberty) hasInternalEncryptionKeySecret(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) (*corev1.Secret, error) { return r.getSecret(instance, lutils.LocalPasswordEncryptionKeyRootName+passwordEncryptionMetadata.Name+"-internal") } // Returns the Secret that contains the password encryption key provided by the user -func (r *ReconcileWebSphereLiberty) hasUserEncryptionKeySecret(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata) (*corev1.Secret, error) { +func (r *ReconcileWebSphereLiberty) hasUserEncryptionKeySecret(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) (*corev1.Secret, error) { return r.getSecret(instance, lutils.PasswordEncryptionKeyRootName+passwordEncryptionMetadata.Name) } -func (r *ReconcileWebSphereLiberty) encryptionKeySecretMirrored(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata) bool { +func (r *ReconcileWebSphereLiberty) encryptionKeySecretMirrored(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) bool { userEncryptionSecret, err := r.hasUserEncryptionKeySecret(instance, passwordEncryptionMetadata) if err != nil { return false @@ -212,7 +213,7 @@ func (r *ReconcileWebSphereLiberty) encryptionKeySecretMirrored(instance *wlv1.W return userPasswordEncryptionKey != "" && internalPasswordEncryptionKey == userPasswordEncryptionKey } -func (r *ReconcileWebSphereLiberty) mirrorEncryptionKeySecretState(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata) error { +func (r *ReconcileWebSphereLiberty) mirrorEncryptionKeySecretState(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) error { userEncryptionSecret, userEncryptionSecretErr := r.hasUserEncryptionKeySecret(instance, passwordEncryptionMetadata) // Error if there was an issue getting the userEncryptionSecret if userEncryptionSecretErr != nil && !kerrors.IsNotFound(userEncryptionSecretErr) { @@ -255,7 +256,7 @@ func (r *ReconcileWebSphereLiberty) mirrorEncryptionKeySecretState(instance *wlv } // Deletes the mirrored encryption key secret if the initial encryption key secret no longer exists -func (r *ReconcileWebSphereLiberty) deleteMirroredEncryptionKeySecret(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata) error { +func (r *ReconcileWebSphereLiberty) deleteMirroredEncryptionKeySecret(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) error { _, userEncryptionSecretErr := r.hasUserEncryptionKeySecret(instance, passwordEncryptionMetadata) // Error if there was an issue getting the userEncryptionSecret if userEncryptionSecretErr != nil && !kerrors.IsNotFound(userEncryptionSecretErr) { @@ -285,7 +286,7 @@ func (r *ReconcileWebSphereLiberty) getSecret(instance *wlv1.WebSphereLibertyApp } // Creates the Liberty XML to mount the password encryption keys Secret into the application pods -func (r *ReconcileWebSphereLiberty) createPasswordEncryptionKeyLibertyConfig(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *lutils.PasswordEncryptionMetadata, encryptionKey string) error { +func (r *ReconcileWebSphereLiberty) createPasswordEncryptionKeyLibertyConfig(instance *wlv1.WebSphereLibertyApplication, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata, encryptionKey string) error { if len(encryptionKey) == 0 { return fmt.Errorf("a password encryption key was not specified") } @@ -328,11 +329,11 @@ func (r *ReconcileWebSphereLiberty) createPasswordEncryptionKeyLibertyConfig(ins // Tracks existing password encryption resources by populating a LeaderTracker array used to initialize the LeaderTracker func (r *ReconcileWebSphereLiberty) GetPasswordEncryptionResources(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, assetsFolder *string) (*unstructured.UnstructuredList, string, error) { - passwordEncryptionResources, _, err := lutils.CreateUnstructuredResourceListFromSignature(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, assetsFolder, "") // TODO: replace prefix "" to specify operator precedence such as with prefix "wlo-" + passwordEncryptionResources, _, err := leader.CreateUnstructuredResourceListFromSignature(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, assetsFolder, "") // TODO: replace prefix "" to specify operator precedence such as with prefix "wlo-" if err != nil { return nil, "", err } - passwordEncryptionResource, passwordEncryptionResourceName, err := lutils.CreateUnstructuredResourceFromSignature(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, assetsFolder, "", "") // TODO: replace prefix "" to specify operator precedence such as with prefix "wlo-" + passwordEncryptionResource, passwordEncryptionResourceName, err := leader.CreateUnstructuredResourceFromSignature(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, assetsFolder, "", "") // TODO: replace prefix "" to specify operator precedence such as with prefix "wlo-" if err != nil { return nil, "", err } diff --git a/internal/controller/resource_sharing.go b/internal/controller/resource_sharing.go deleted file mode 100644 index 314ee407..00000000 --- a/internal/controller/resource_sharing.go +++ /dev/null @@ -1,359 +0,0 @@ -/* - Copyright contributors to the WASdev project. - - 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. -*/ - -package controller - -import ( - "fmt" - "strconv" - "strings" - "sync" - - tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" - wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" - lutils "github.com/WASdev/websphere-liberty-operator/utils" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" -) - -// Validates the resource decision tree YAML and generates the leader tracking state (Secret) for maintaining multiple shared resources -func (r *ReconcileWebSphereLiberty) reconcileResourceTrackingState(instance *wlv1.WebSphereLibertyApplication, leaderTrackerType string) (lutils.LeaderTrackerMetadataList, error) { - treeMap, replaceMap, err := tree.ParseDecisionTree(leaderTrackerType, nil) - if err != nil { - return nil, err - } - - latestOperandVersion, err := tree.GetLatestOperandVersion(treeMap, "") - if err != nil { - return nil, err - } - - // persist or create a Secret to store the shared resources' state - err = r.reconcileLeaderTracker(instance, treeMap, replaceMap, latestOperandVersion, leaderTrackerType, nil) - if err != nil { - return nil, err - } - - // return the metadata specific to the operator version, instance configuration, and shared resource being reconciled - if leaderTrackerType == LTPA_RESOURCE_SHARING_FILE_NAME { - ltpaMetadataList, err := r.reconcileLTPAMetadata(instance, treeMap, latestOperandVersion, nil) - if err != nil { - return nil, err - } - return ltpaMetadataList, nil - } - if leaderTrackerType == PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME { - passwordEncryptionMetadataList, err := r.reconcilePasswordEncryptionMetadata(treeMap, latestOperandVersion) - if err != nil { - return nil, err - } - return passwordEncryptionMetadataList, nil - } - return nil, fmt.Errorf("a leaderTrackerType was not provided when running reconcileResourceTrackingState") -} - -// If shouldElectNewLeader is set to true, the WebSphereLibertyApplication instance will be set and returned as the resource leader -// Otherwise, returns the current shared resource leader -func (r *ReconcileWebSphereLiberty) reconcileLeader(instance *wlv1.WebSphereLibertyApplication, leaderMetadata lutils.LeaderTrackerMetadata, leaderTrackerType string, shouldElectNewLeader bool) (string, bool, string, error) { - leaderTracker, leaderTrackers, err := lutils.GetLeaderTracker(instance, OperatorShortName, leaderTrackerType, r.GetClient()) - if err != nil { - return "", false, "", err - } - return r.reconcileLeaderWithState(instance, leaderTracker, leaderTrackers, leaderMetadata, shouldElectNewLeader, leaderTrackerType) -} - -func (r *ReconcileWebSphereLiberty) reconcileLeaderWithState(instance *wlv1.WebSphereLibertyApplication, leaderTracker *corev1.Secret, leaderTrackers *[]lutils.LeaderTracker, leaderMetadata lutils.LeaderTrackerMetadata, shouldElectNewLeader bool, leaderTrackerType string) (string, bool, string, error) { - initialLeaderIndex := -1 - for i, tracker := range *leaderTrackers { - if tracker.Name == leaderMetadata.GetName() { - initialLeaderIndex = i - } - } - // if the tracked resource does not exist in resources labels, this instance is leader - if initialLeaderIndex == -1 { - if !shouldElectNewLeader { - return "", false, "", nil - } - // clear instance.Name from ownership of any prior resources - for i := range *leaderTrackers { - (*leaderTrackers)[i].ClearOwnerIfMatchingAndSharesLastPathParent(instance.Name, leaderMetadata.GetPath()) - } - // make instance.Name the new leader - newLeader := lutils.LeaderTracker{ - Name: leaderMetadata.GetName(), - Owner: instance.Name, - PathIndex: leaderMetadata.GetPathIndex(), - Path: leaderMetadata.GetPath(), - // Sublease: fmt.Sprint(time.Now().Unix()), - } - // append it to the list of leaders - *leaderTrackers = append(*leaderTrackers, newLeader) - // save the tracker state - if err := r.SaveLeaderTracker(leaderTracker, leaderTrackers, leaderTrackerType); err != nil { - return "", false, "", err - } - return instance.Name, true, leaderMetadata.GetPathIndex(), nil - } - // otherwise, the resource is being tracked - // if the leader of the tracked resource is non empty decide whether or not to return the resource owner - candidateLeader := (*leaderTrackers)[initialLeaderIndex].Owner - if len(candidateLeader) > 0 { - // Return this other instance as the leader (the "other" instance could also be this instance) - // Before returning, if the candidate instance is not this instance, this instance must clean up its old owner references to avoid an resource owner cycle. - // A resource owner cycle can occur when instance A points to resource A and instance B points to resource B but then both instance A and B swap pointing to each other's resource. - if candidateLeader != instance.Name { - // clear instance.Name from ownership of any prior resources and evict the owner if the sublease has expired - for i := range *leaderTrackers { - (*leaderTrackers)[i].ClearOwnerIfMatchingAndSharesLastPathParent(instance.Name, leaderMetadata.GetPath()) - // (*leaderTrackers)[i].EvictOwnerIfSubleaseHasExpired() - } - } - // else { - // candidate is this instance, so renew the sublease - // (*leaderTrackers)[initialLeaderIndex].RenewSublease() - // } - - // If the current owner has been evicted, use this instance as the new owner - currentOwner := (*leaderTrackers)[initialLeaderIndex].Owner - if currentOwner == "" { - currentOwner = instance.Name - (*leaderTrackers)[initialLeaderIndex].SetOwner(currentOwner) - } - // save this new owner list - if err := r.SaveLeaderTracker(leaderTracker, leaderTrackers, leaderTrackerType); err != nil { - return "", false, "", err - } - return currentOwner, currentOwner == instance.Name, (*leaderTrackers)[initialLeaderIndex].PathIndex, nil - } - if !shouldElectNewLeader { - return "", false, "", nil - } - // there is either no leader (empty string) or the leader was deleted so now this instance is leader - pathIndex := "" - for i := range *leaderTrackers { - if i == initialLeaderIndex { - pathIndex = (*leaderTrackers)[i].PathIndex - (*leaderTrackers)[i].SetOwner(instance.Name) - } else { - (*leaderTrackers)[i].ClearOwnerIfMatchingAndSharesLastPathParent(instance.Name, leaderMetadata.GetPath()) - } - } - // save this new owner list - if err := r.SaveLeaderTracker(leaderTracker, leaderTrackers, leaderTrackerType); err != nil { - return "", false, "", err - } - return instance.Name, true, pathIndex, nil -} - -func (r *ReconcileWebSphereLiberty) createNewLeaderTrackerList(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, leaderTrackerType string, assetsFolder *string) (*[]lutils.LeaderTracker, error) { - var resourcesMatrix []*unstructured.UnstructuredList - var resourcesRootNameList []string - - if leaderTrackerType == LTPA_RESOURCE_SHARING_FILE_NAME { - // 1. Add LTPA key Secret - resourcesList, resourceRootName, keyErr := r.GetLTPAKeyResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder) - if keyErr != nil { - return nil, keyErr - } - resourcesMatrix = append(resourcesMatrix, resourcesList) - resourcesRootNameList = append(resourcesRootNameList, resourceRootName) - // 2. Add LTPA password Secret (config 1) - resourcesList, resourceRootName, keyErr = r.GetLTPAConfigResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder, LTPA_CONFIG_1_RESOURCE_SHARING_FILE_NAME) - if keyErr != nil { - return nil, keyErr - } - resourcesMatrix = append(resourcesMatrix, resourcesList) - resourcesRootNameList = append(resourcesRootNameList, resourceRootName) - // 3. Add LTPA password Secret (config 2) - resourcesList, resourceRootName, keyErr = r.GetLTPAConfigResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder, LTPA_CONFIG_2_RESOURCE_SHARING_FILE_NAME) - if keyErr != nil { - return nil, keyErr - } - resourcesMatrix = append(resourcesMatrix, resourcesList) - resourcesRootNameList = append(resourcesRootNameList, resourceRootName) - } else if leaderTrackerType == PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME { - resourcesList, resourceRootName, passwordErr := r.GetPasswordEncryptionResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder) - if passwordErr != nil { - return nil, passwordErr - } - resourcesMatrix = append(resourcesMatrix, resourcesList) - resourcesRootNameList = append(resourcesRootNameList, resourceRootName) - } else { - return nil, fmt.Errorf("a valid leaderTrackerType was not specified for createNewLeaderTrackerList") - } - - leaderTracker := make([]lutils.LeaderTracker, 0) - for i, resourcesList := range resourcesMatrix { - r.UpdateLeaderTrackersFromUnstructuredList(&leaderTracker, resourcesList, treeMap, replaceMap, latestOperandVersion, resourcesRootNameList[i]) - } - return &leaderTracker, nil -} - -// Reconciles the latest LeaderTracker state to be used by the operator -func (r *ReconcileWebSphereLiberty) reconcileLeaderTracker(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, leaderTrackerType string, assetsFolder *string) error { - leaderTracker, _, err := lutils.GetLeaderTracker(instance, OperatorShortName, leaderTrackerType, r.GetClient()) - // If the Leader Tracker is missing, create from scratch - if err != nil && kerrors.IsNotFound(err) { - leaderTracker.Labels[lutils.LeaderVersionLabel] = latestOperandVersion - leaderTracker.ResourceVersion = "" - leaderTrackers, err := r.createNewLeaderTrackerList(instance, treeMap, replaceMap, latestOperandVersion, leaderTrackerType, assetsFolder) - if err != nil { - return err - } - return r.SaveLeaderTracker(leaderTracker, leaderTrackers, leaderTrackerType) - } else if err != nil { - return err - } - // If the Leader Tracker is outdated, delete it so that it gets recreated in another reconcile - if leaderTracker.Labels[lutils.LeaderVersionLabel] != latestOperandVersion { - r.DeleteLeaderTracker(leaderTracker, leaderTrackerType) - } - return nil -} - -func (r *ReconcileWebSphereLiberty) SaveLeaderTracker(leaderTracker *corev1.Secret, trackerList *[]lutils.LeaderTracker, leaderTrackerType string) error { - leaderMutex, mutexFound := lutils.LeaderTrackerMutexes.Load(leaderTrackerType) - if !mutexFound { - return fmt.Errorf("Could not get %s leader tracker's mutex when attempting to save. Exiting.", leaderTrackerType) - } - leaderMutex.(*sync.Mutex).Lock() - defer leaderMutex.(*sync.Mutex).Unlock() - - return r.CreateOrUpdate(leaderTracker, nil, func() error { - lutils.CustomizeLeaderTracker(leaderTracker, trackerList) - return nil - }) -} - -func (r *ReconcileWebSphereLiberty) DeleteLeaderTracker(leaderTracker *corev1.Secret, leaderTrackerType string) error { - leaderMutex, mutexFound := lutils.LeaderTrackerMutexes.Load(leaderTrackerType) - if !mutexFound { - return fmt.Errorf("Could not get %s leader tracker's mutex when attempting to delete. Exiting.", leaderTrackerType) - } - leaderMutex.(*sync.Mutex).Lock() - defer leaderMutex.(*sync.Mutex).Unlock() - return r.DeleteResource(leaderTracker) -} - -// Removes the instance as leader if instance is the leader and if no leaders are being tracked then delete the leader tracking Secret -func (r *ReconcileWebSphereLiberty) RemoveLeader(instance *wlv1.WebSphereLibertyApplication, leaderTracker *corev1.Secret, leaderTrackers *[]lutils.LeaderTracker, leaderTrackerType string) error { - changeDetected := false - noOwners := true - // If the instance is being tracked, remove it - for i := range *leaderTrackers { - if (*leaderTrackers)[i].ClearOwnerIfMatching(instance.Name) { - changeDetected = true - } - if (*leaderTrackers)[i].Owner != "" { - noOwners = false - } - } - if noOwners { - if err := r.DeleteLeaderTracker(leaderTracker, leaderTrackerType); err != nil { - return err - } - } else if changeDetected { - if err := r.SaveLeaderTracker(leaderTracker, leaderTrackers, leaderTrackerType); err != nil { - return err - } - } - return nil -} - -func (r *ReconcileWebSphereLiberty) UpdateLeaderTrackersFromUnstructuredList(leaderTrackers *[]lutils.LeaderTracker, resourceList *unstructured.UnstructuredList, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, resourceRootName string) error { - for i, resource := range resourceList.Items { - labelsMap, _, err := unstructured.NestedMap(resource.Object, "metadata", "labels") - if err != nil { - return err - } - if pathIndexInterface, found := labelsMap[lutils.ResourcePathIndexLabel]; found { - pathIndex := pathIndexInterface.(string) - // Skip this resource if path index does not contain a period separating delimeter - if !strings.Contains(pathIndex, ".") { - continue - } - labelVersionArray := strings.Split(pathIndex, ".") - // Skip this resource if the path index is not a tuple representing the version and index - if len(labelVersionArray) != 2 { - continue - } - leader := lutils.LeaderTracker{ - PathIndex: pathIndex, - } - indexIntVal, _ := strconv.ParseInt(labelVersionArray[1], 10, 64) - path, pathErr := tree.GetPathFromLeafIndex(treeMap, labelVersionArray[0], int(indexIntVal)) - // If path comes from a different operand version, the path needs to be upgraded/downgraded to the latestOperandVersion - if pathErr == nil && labelVersionArray[0] != latestOperandVersion { - // If user error has occurred, based on whether or not a dev deleted the decision tree structure of an older version - // allow this condition to error (when err != nil) so that a deleted (older) revision of the decision tree that may be missing - // won't halt the operator when the ReplacePath validation is performed - if path, err := tree.ReplacePath(path, latestOperandVersion, treeMap, replaceMap); err == nil { - newPathIndex := strings.Split(path, ".")[0] + "." + strconv.FormatInt(int64(tree.GetLeafIndex(treeMap, path)), 10) - leader.PathIndex = newPathIndex - leader.Path = path - // the path may have changed so the path index reference needs to be updated directly in the resource - if err := r.CreateOrUpdate(&resourceList.Items[i], nil, func() error { - labelsMap, _, err := unstructured.NestedMap(resourceList.Items[i].Object, "metadata", "labels") - if err != nil { - return err - } - labelsMap[lutils.ResourcePathIndexLabel] = newPathIndex - if err := unstructured.SetNestedMap(resourceList.Items[i].Object, labelsMap, "metadata", "labels"); err != nil { - return err - } - return nil - }); err != nil { - return err - } - } - } else if pathErr == nil { // only update the path metadata if this operator's decision tree structure recognizes the resource - leader.Path = path - } else { - // A valid decision tree path could not be found, so it will not be used by the operator and this resource will not be tracked - continue - } - nameString, _, err := unstructured.NestedString(resource.Object, "metadata", "name") - if err != nil { - return err - } - leader.Name = nameString[len(resourceRootName):] - leader.EvictOwner() - lutils.InsertIntoSortedLeaderTrackers(leaderTrackers, &leader) - } - } - return nil -} - -func (r *ReconcileWebSphereLiberty) RemoveLeaderTrackerReference(instance *wlv1.WebSphereLibertyApplication, leaderTrackerType string) error { - leaderTracker, leaderTrackers, err := lutils.GetLeaderTracker(instance, OperatorShortName, leaderTrackerType, r.GetClient()) - if err != nil { - if kerrors.IsNotFound(err) { - return nil - } - return err - } - return r.RemoveLeader(instance, leaderTracker, leaderTrackers, leaderTrackerType) -} - -func hasResourceSuffixesEnv(instance *wlv1.WebSphereLibertyApplication, envName string) (string, bool) { - for _, env := range instance.GetEnv() { - if env.Name == envName { - return env.Value, true - } - } - return "", false -} diff --git a/internal/controller/tests/trace-decision-tree-2-generations.yaml b/internal/controller/tests/trace-decision-tree-2-generations.yaml new file mode 100644 index 00000000..e6e2ab8d --- /dev/null +++ b/internal/controller/tests/trace-decision-tree-2-generations.yaml @@ -0,0 +1,10 @@ +tree: + v1_4_2: + name: "*" + v10_4_3: + type: + aname: other + name: "*" +replace: + v10_4_3: + "v1_4_2.name.*": "v10_4_3.type.name.*" \ No newline at end of file diff --git a/internal/controller/tests/trace-decision-tree-3-generations.yaml b/internal/controller/tests/trace-decision-tree-3-generations.yaml new file mode 100644 index 00000000..53f79002 --- /dev/null +++ b/internal/controller/tests/trace-decision-tree-3-generations.yaml @@ -0,0 +1,21 @@ +tree: + v1_4_2: + name: "*" + v10_4_3: + type: + aname: other # 0 + name: "*" # 1 + v10_4_500: + type: + aes: + list: + - true # 0 + - false # 1 + - test # 2 + xor: type # 4 + name: "*" # 3 +replace: + v10_4_3: + "v1_4_2.name.*": "v10_4_3.type.name.*" + v10_4_500: + "v10_4_3.type.name.*": "v10_4_500.type.name.*" \ No newline at end of file diff --git a/internal/controller/tests/trace-decision-tree-complex.yaml b/internal/controller/tests/trace-decision-tree-complex.yaml new file mode 100644 index 00000000..cf325056 --- /dev/null +++ b/internal/controller/tests/trace-decision-tree-complex.yaml @@ -0,0 +1,51 @@ +tree: + v1_4_2: + name: "*" + v10_3_3: + a: "*" # 0 + v10_4_1: + a: + b: + c: true # 0 + e: + - "*" # 2 + - false # 3 + d: true # 1 + j: fizz # 4 (this element appears only in v10_4_1, but is deprecated in all other versions because no replace command exists) + v10_4_20: + a: + b: + e: "*" # 2 + c: true # 0 + d: false # 1 + f: + h: element # 4 + g: + i: bar # 3 + v10_4_500: + a: + b: + d: false # 2 + b: "*" # 0 + e: foo # 3 + c: true # 1 + f: + h: element # 5 + g: + i: bar # 4 +replace: + v10_3_3: + "v1_4_2.name.*": "v10_3_3.a.*" + v10_4_1: + "v10_3_3.a.*": "v10_4_1.a.b.e.*" + v10_4_20: + "v10_4_1.a.b.c.true": "v10_4_20.a.b.c.true" + "v10_4_1.a.b.d.true": "v10_4_20.a.b.d.false" + "v10_4_1.a.b.e.*": "v10_4_20.a.b.e.*" + "v10_4_1.a.b.e.false": "v10_4_20.a.f.g.i.bar" + v10_4_500: + "v10_4_20.a.b.d.false": "v10_4_500.a.b.d.false" + "v10_4_20.a.b.c.true": "v10_4_500.a.b.c.true" + "v10_4_20.a.b.e.*": "v10_4_500.a.b.b.*" + "v10_4_20.a.f.g.i.bar": "v10_4_500.a.f.g.i.bar" + "v10_4_20.a.f.h.element": "v10_4_500.a.f.h.element" diff --git a/internal/controller/trace_sharing.go b/internal/controller/trace_sharing.go new file mode 100644 index 00000000..b8b7de81 --- /dev/null +++ b/internal/controller/trace_sharing.go @@ -0,0 +1,91 @@ +package controller + +import ( + "fmt" + "sync" + + "github.com/OpenLiberty/open-liberty-operator/utils/leader" + tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" + wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" + lutils "github.com/WASdev/websphere-liberty-operator/utils" +) + +const TRACE_RESOURCE_SHARING_FILE_NAME = "trace" + +func init() { + leader.LeaderTrackerMutexes.Store(TRACE_RESOURCE_SHARING_FILE_NAME, &sync.Mutex{}) +} + +func (r *ReconcileWebSphereLibertyTrace) reconcileTraceMetadata(instance *wlv1.WebSphereLibertyTrace, treeMap map[string]interface{}, latestOperandVersion string, assetsFolder *string) (leader.LeaderTrackerMetadataList, error) { + metadataList := &leader.TraceMetadataList{} + metadataList.Items = []leader.LeaderTrackerMetadata{} + + // During runtime, the WebSphereLibertyApplication instance will decide what Trace related resources to track by populating arrays of pathOptions and pathChoices + pathOptionsList, pathChoicesList := r.getTracePathOptionsAndChoices(instance, latestOperandVersion) + + for i := range pathOptionsList { + metadata := &leader.TraceMetadata{} + pathOptions := pathOptionsList[i] + pathChoices := pathChoicesList[i] + + // convert the path options and choices into a labelString, for a path of length n, the labelString is + // constructed as a weaved array in format "......." + labelString, err := tree.GetLabelFromDecisionPath(latestOperandVersion, pathOptions, pathChoices) + if err != nil { + return metadataList, err + } + + // validate that the decision path such as "v1_4_0.managePasswordEncryption." is a valid subpath in treeMap + // an error here indicates a build time error created by the operator developer or pollution of the ltpa-decision-tree.yaml + // Note: validSubPath is a substring of labelString and a valid path within treeMap; it will always hold that len(validSubPath) <= len(labelString) + // Also, validSubPath will return wildcard characters as '*' and will not output the full entry provided from labelString so that reverse lookup with GetLeafIndex is possible + validSubPath, err := tree.CanTraverseTree(treeMap, labelString, true) + if err != nil { + return metadataList, err + } + + // retrieve the Trace leader tracker to re-use an existing name or to create a new metadata.Name + leaderTracker, _, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + if err != nil { + return metadataList, err + } + + // if the leaderTracker is on a mismatched version, wait for a subsequent reconcile loop to re-create the leader tracker + if leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)] != latestOperandVersion { + return metadataList, fmt.Errorf("waiting for the Leader Tracker to be updated") + } + + // to avoid limitation with Kubernetes label values having a max length of 63, translate validSubPath into a path index + pathIndex := tree.GetLeafIndex(treeMap, validSubPath) + versionedPathIndex := fmt.Sprintf("%s.%d", latestOperandVersion, pathIndex) + if n := len(pathChoices); n > 0 { + metadata.Path = validSubPath + metadata.PathIndex = versionedPathIndex + metadata.Name = pathChoices[n-1] // for v1_4_2, pathChoices[n-1] will be the wildcard entry * representing the name + metadataList.Items = append(metadataList.Items, metadata) + } + } + return metadataList, nil +} + +func (r *ReconcileWebSphereLibertyTrace) getTracePathOptionsAndChoices(instance *wlv1.WebSphereLibertyTrace, latestOperandVersion string) ([][]string, [][]string) { + var pathOptionsList, pathChoicesList [][]string + if latestOperandVersion == "v1_4_2" { + // Generate a path option/choice for a leader to manage the Trace CR instance's pods + pathOptions := []string{"name"} // ordering matters, it must follow the nodes of the Trace decision tree in trace-decision-tree.yaml + pathChoices := []string{instance.Spec.PodName} // wildcard entry can be provided as any string + pathOptionsList = append(pathOptionsList, pathOptions) + pathChoicesList = append(pathChoicesList, pathChoices) + + if instance.GetStatus() != nil && instance.GetStatus().GetOperatedResource() != nil { + prevPodName := instance.GetStatus().GetOperatedResource().GetOperatedResourceName() + if instance.Spec.PodName != prevPodName && prevPodName != "" { + pathOptions := []string{"name"} + pathChoices := []string{prevPodName} + pathOptionsList = append(pathOptionsList, pathOptions) + pathChoicesList = append(pathChoicesList, pathChoices) + } + } + } + return pathOptionsList, pathChoicesList +} diff --git a/internal/controller/trace_sharing_test.go b/internal/controller/trace_sharing_test.go new file mode 100644 index 00000000..d8ab3670 --- /dev/null +++ b/internal/controller/trace_sharing_test.go @@ -0,0 +1,921 @@ +package controller + +import ( + "fmt" + "testing" + + "github.com/OpenLiberty/open-liberty-operator/utils/leader" + tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" + webspherelibertyv1 "github.com/WASdev/websphere-liberty-operator/api/v1" + lutils "github.com/WASdev/websphere-liberty-operator/utils" + corev1 "k8s.io/api/core/v1" + kerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/record" + fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +var ( + traceSpecification = "*=info:com.ibm.ws.webcontainer*=all" + traceLeaderTrackerVersion_v1_4_2 = "v1_4_2" + // traceLeaderTrackerVersion_v*_*_* = "v*_*_*" // create this flag when testing a new upgrade path +) + +func TestTraceGetLeaderTrackerIsEmpty(t *testing.T) { + _, r := mockWebSphereLibertyTrace() + + // Check that the Trace leader tracker is not initialized + leaderTracker, _, err := leader.GetLeaderTracker(namespace, OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + emptyLeaderTracker := createEmptyLeaderTrackerSecret(TRACE_RESOURCE_SHARING_FILE_NAME) + tests := []Test{ + {"get Trace leader tracker is nil", emptyLeaderTracker, leaderTracker}, + {"get Trace leader tracker is not found", true, kerrors.IsNotFound(err)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +func TestTraceDecisionTreeIsValid(t *testing.T) { + // check the trace decision tree + fileName := getControllerFolder() + "/assets/trace-decision-tree.yaml" + _, _, err := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + tests := []Test{ + {"parse trace decision tree", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // check the test trace 2 gen decision tree + fileName = getControllerFolder() + "/tests/trace-decision-tree-2-generations.yaml" + _, _, err = tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + tests = []Test{ + {"parse test trace decision tree 2 generations", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // check the test decision tree + fileName = getControllerFolder() + "/tests/trace-decision-tree-complex.yaml" + _, _, err = tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + tests = []Test{ + {"parse test decision tree complex", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +func TestTraceLeaderTrackerComplex(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + // load decision tree + latestOperandVersion := "v10_4_1" + fileName := getControllerFolder() + "/tests/trace-decision-tree-complex.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + // load leader tracker + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + err := tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder) + tests := []Test{ + {"initialize Trace leader tracker", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + leaderTracker, _, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + expectedLeaderTrackerData := map[string][]byte{} + expectedLeaderTrackerData[leader.ResourcesKey] = []byte("") + expectedLeaderTrackerData[leader.ResourceOwnersKey] = []byte("") + expectedLeaderTrackerData[leader.ResourcePathsKey] = []byte("") + expectedLeaderTrackerData[leader.ResourcePathIndicesKey] = []byte("") + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // create a trace instance + complexTrace := createTraceCR("complex-trace-one", "test-pod", latestOperandVersion+".2", nil) + err1 := r.CreateOrUpdate(complexTrace, nil, func() error { return nil }) + tests = []Test{ + {"create Trace CR from based on path index 2 of complex decision tree", nil, err1}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // Mock the process where the reconciler checks the WebSphereLibertyTrace (itself), storing itself into the leader tracker + leaderName, isLeader, pathIndex, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, complexTrace.GetName(), complexTrace.GetNamespace(), &leader.TraceMetadata{ + Path: latestOperandVersion + ".a.b.e.true", + PathIndex: latestOperandVersion + ".2", + Name: "test-pod", + }, TRACE_RESOURCE_SHARING_FILE_NAME, true) + tests = []Test{ + {"update leader tracker based on path index 2 of complex decision tree - error", nil, err}, + {"update leader tracker based on path index 2 of complex decision tree - path index", pathIndex, latestOperandVersion + ".2"}, + {"update leader tracker based on path index 2 of complex decision tree - isLeader", true, isLeader}, + {"update leader tracker based on path index 2 of complex decision tree - leader name", complexTrace.GetName(), leaderName}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // check that the leader tracker received the new Trace CR state + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + expectedLeaderTrackerData = map[string][]byte{ + leader.ResourcesKey: []byte("test-pod"), + leader.ResourceOwnersKey: []byte(complexTrace.GetName()), + leader.ResourcePathsKey: []byte(latestOperandVersion + ".a.b.e.true"), + leader.ResourcePathIndicesKey: []byte(latestOperandVersion + ".2"), + } + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // create another trace instance + complexTraceTwo := createTraceCR("complex-trace-two", "test-pod-2", latestOperandVersion+".1", nil) + err = r.CreateOrUpdate(complexTraceTwo, nil, func() error { return nil }) + tests = []Test{ + {"create Trace Secret from based on path index 1 of complex decision tree", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // Mock the process where the operator saves the Trace CR, storing it into the leader tracker + tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, complexTraceTwo.GetName(), complexTraceTwo.GetNamespace(), &leader.TraceMetadata{ + Path: latestOperandVersion + ".a.b.d.true", + PathIndex: latestOperandVersion + ".1", + Name: "test-pod-2", + }, TRACE_RESOURCE_SHARING_FILE_NAME, true) + + // Check the Trace leader tracker was updated + leaderTracker, _, err = leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + expectedLeaderTrackerData = map[string][]byte{ + leader.ResourcesKey: []byte("test-pod,test-pod-2"), + leader.ResourceOwnersKey: []byte(fmt.Sprintf("%s,%s", complexTrace.GetName(), complexTraceTwo.GetName())), + leader.ResourcePathsKey: []byte(fmt.Sprintf("%s.a.b.e.true,%s.a.b.d.true", latestOperandVersion, latestOperandVersion)), + leader.ResourcePathIndicesKey: []byte(fmt.Sprintf("%s.2,%s.1", latestOperandVersion, latestOperandVersion)), + } + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-trace", leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader tracker data", expectedLeaderTrackerData, leaderTracker.Data}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // remove the Trace leader + removeErr1 := tree.RemoveLeaderTrackerReference(rsf, complexTrace.GetName(), complexTrace.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME) + removeErr2 := tree.RemoveLeaderTrackerReference(rsf, complexTraceTwo.GetName(), complexTraceTwo.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME) + removeLeaderErr1 := tree.RemoveLeader(complexTrace.GetName(), rsf, leaderTracker, leaderTrackers) + removeLeaderErr2 := tree.RemoveLeader(complexTraceTwo.GetName(), rsf, leaderTracker, leaderTrackers) + _, leaderTrackers, leaderTrackerErr := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + var nilLeaderTrackers *[]leader.LeaderTracker + tests = []Test{ + {"remove Trace - deleteTraceKeysResource errors 1", nil, removeErr1}, + {"remove Trace - deleteTraceKeysResource errors 2", nil, removeErr2}, + {"remove Trace - RemoveLeader errors 1", nil, removeLeaderErr1}, + {"remove Trace - RemoveLeader errors 2", nil, removeLeaderErr2}, + {"remove Trace - GetLeaderTracker is not found", true, kerrors.IsNotFound(leaderTrackerErr)}, + {"remove Trace - leader trackers list is nil", nilLeaderTrackers, leaderTrackers}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +// Check that Trace CRs can manage a pod +func TestTraceLeaderTrackerManagesOnePod(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + traceName := "example-trace" + + // create decision tree + latestOperandVersion := "v10_4_2" + fileName := getControllerFolder() + "/assets/trace-decision-tree.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + // create a trace instance + trace1Name := traceName + "-one" + trace1PodName := traceName + "-pod-one" + trace1Instance := createTraceCR(trace1Name, trace1PodName, traceLeaderTrackerVersion_v1_4_2+".0", nil) + tests := []Test{ + {"create Trace CR from based on path index 0 of trace decision tree", nil, r.CreateOrUpdate(trace1Instance, nil, func() error { return nil })}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // init the leader tracker + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tests = []Test{ + {"initialize Trace leader tracker error", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 := leader.LeaderTracker{ + Name: trace1PodName, + Owner: "", // no owners associated with the Trace CRs because the mock decision tree is not registered to use with the operator + Path: traceLeaderTrackerVersion_v1_4_2 + ".name.*", + PathIndex: traceLeaderTrackerVersion_v1_4_2 + ".0", + } + + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers is not nil", true, leaderTrackers != nil}, + {"get Trace leader trackers matches length", 1, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +// Check that Trace CRs could manage more than one pod tied to wildcard path v1_4_2.name +func TestTraceLeaderTrackerManagesTwoPods(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + traceName := "example-trace" + + // load decision tree + latestOperandVersion := "v10_4_1" // When implementing v1_4_3 or higher, set latestOperandVersion to v1_4_(n-1) + fileName := getControllerFolder() + "/assets/trace-decision-tree.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + // create first instance + trace1Name := traceName + "-one" + trace1PodName := traceName + "-pod-one" + trace1Instance := createTraceCR(trace1Name, trace1PodName, traceLeaderTrackerVersion_v1_4_2+".0", nil) + + // create second instance + trace2Name := traceName + "-two" + trace2PodName := traceName + "-pod-two" + trace2Instance := createTraceCR(trace2Name, trace2PodName, traceLeaderTrackerVersion_v1_4_2+".0", nil) + + // check instances can be created + tests := []Test{ + {"create Trace CR 1 based on path index 0 of trace decision tree", nil, r.CreateOrUpdate(trace1Instance, nil, func() error { return nil })}, + {"create Trace CR 2 based on path index 0 of trace decision tree", nil, r.CreateOrUpdate(trace2Instance, nil, func() error { return nil })}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // init the leader tracker + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tests = []Test{ + {"initialize Trace leader tracker error", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 := leader.LeaderTracker{ + Name: trace1PodName, + Owner: "", // no owners associated with the Trace CRs because the mock decision tree is not registered to use with the operator + Path: traceLeaderTrackerVersion_v1_4_2 + ".name.*", + PathIndex: traceLeaderTrackerVersion_v1_4_2 + ".0", + } + leader2 := leader.LeaderTracker{ + Name: trace2PodName, + Owner: "", // no owners associated with the Trace CRs because the mock decision tree is not registered to use with the operator + Path: traceLeaderTrackerVersion_v1_4_2 + ".name.*", + PathIndex: traceLeaderTrackerVersion_v1_4_2 + ".0", + } + + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers is not nil", leaderTrackers != nil, true}, + {"get Trace leader trackers matches length", 2, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader2)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +// Check that Trace CRs could update from being managed by v1_4_2.name to another wildcard path v100_4_2.othername +func TestTraceLeaderTrackerManagesOnePodWithUpgrade(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + traceName := "example-trace" + + // load decision tree + latestOperandVersion := "v10_4_2" + fileName := getControllerFolder() + "/assets/trace-decision-tree.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + // create instance + trace1Name := traceName + "-one" + trace1PodName := traceName + "-pod-one" + trace1Instance := createTraceCR(trace1Name, trace1PodName, traceLeaderTrackerVersion_v1_4_2+".0", nil) + tests := []Test{ + {"create Trace CR from based on path index 0 of trace decision tree", nil, r.CreateOrUpdate(trace1Instance, nil, func() error { return nil })}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // init the leader tracker + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tests = []Test{ + {"initialize Trace leader tracker error", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 := leader.LeaderTracker{ + Name: trace1PodName, + Owner: "", // no owners associated with the Trace CRs because the mock decision tree is not registered to use with the operator + Path: traceLeaderTrackerVersion_v1_4_2 + ".name.*", + PathIndex: traceLeaderTrackerVersion_v1_4_2 + ".0", + } + + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers is not nil", true, leaderTrackers != nil}, + {"get Trace leader trackers matches length", 1, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +// This tests that the Trace leader tracker can have cluster awareness of Trace CRs before operator reconciliation +func TestReconcileLeaderTrackerComplexWhenTraceExists(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + traceName := "example-trace" + + // load decision tree + latestOperandVersion := "v10_4_500" + fileName := getControllerFolder() + "/tests/trace-decision-tree-complex.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + // create trace instance + complexTraceName := traceName + "-one" + complexTracePodName := traceName + "-pod-one" + complexTrace := createTraceCR(complexTraceName, complexTracePodName, "v1_4_2.0", nil) + + // create another trace instance + complexTrace2Name := traceName + "-two" + complexTrace2PodName := traceName + "-pod-two" + complexTrace2 := createTraceCR(complexTrace2Name, complexTrace2PodName, "v10_4_1.2", nil) + + tests := []Test{ + {"create Trace CR from based on path index 2 of complex decision tree", nil, r.CreateOrUpdate(complexTrace, nil, func() error { return nil })}, + {"create Trace CR from based on path index 3 of complex decision tree", nil, r.CreateOrUpdate(complexTrace2, nil, func() error { return nil })}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // init the leader tracker + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tests = []Test{ + {"initialize Trace leader tracker error", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // check the leader tracker + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 := leader.LeaderTracker{ + Name: complexTracePodName, + Owner: "", // no owners associated with the Trace CRs because the mock decision tree is not registered to use with the operator + Path: "v10_4_500.a.b.b.*", + PathIndex: "v10_4_500.0", + } + leader2 := leader.LeaderTracker{ + Name: complexTrace2PodName, + Owner: "", // no owners associated with the Trace CRs because the mock decision tree is not registered to use with the operator + Path: "v10_4_500.a.b.b.*", + PathIndex: "v10_4_500.0", + } + + tests = []Test{ + {"get Trace leader tracker error", nil, err}, + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers matches length", 2, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader trackers contains leader2", true, leader.LeaderTrackersContains(leaderTrackers, leader2)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +// This tests that the Trace leader tracker can have cluster awareness of Trace CRs before operator reconciliation and upgrade the Trace CRs to the latest decision tree version +func TestReconcileLeaderTrackerWhenTraceExistWithUpgrade(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + fileName := getControllerFolder() + "/tests/trace-decision-tree-complex.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + // create trace instance + traceName := "example-trace" + complexTraceName := traceName + "-one" + complexTracePodName := traceName + "-pod-one" + complexTrace := createTraceCR(complexTraceName, complexTracePodName, "v1_4_2.0", nil) + r.CreateOrUpdate(complexTrace, nil, func() error { return nil }) // err checked in TestReconcileLeaderTrackerComplexWhenTraceExists + + // create another trace instance + complexTrace2Name := traceName + "-two" + complexTrace2PodName := traceName + "-pod-two" + complexTrace2 := createTraceCR(complexTrace2Name, complexTrace2PodName, "v10_4_20.2", nil) + r.CreateOrUpdate(complexTrace2, nil, func() error { return nil }) // err checked in TestReconcileLeaderTrackerComplexWhenTraceExists + + // init the leader tracker but on a higher version of the decision tree + latestOperandVersion := "v10_4_20" // upgrade the version + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tests := []Test{ + {"reconcileLeaderTracker at version v10_4_20", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // validate the leader tracker upgraded the two Trace CRs created + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 := leader.LeaderTracker{ + Name: complexTracePodName, + Owner: "", // no owners associated with the Trace CRs because the mock decision tree is not registered to use with the operator + Path: "v10_4_20.a.b.e.*", + PathIndex: "v10_4_20.2", + } + leader2 := leader.LeaderTracker{ + Name: complexTrace2PodName, + Owner: "", // no owners associated with the Trace CRs because the mock decision tree is not registered to use with the operator + Path: "v10_4_20.a.b.e.*", + PathIndex: "v10_4_20.2", + } + + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers matches length", 2, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader trackers contains leader2", true, leader.LeaderTrackersContains(leaderTrackers, leader2)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +// This tests that the Trace leader tracker can have cluster awareness of Trace CRs before operator reconciliation and upgrade the Trace CRs to the latest decision tree version +func TestReconcileLeaderTrackerWhenTraceExistsWithMultipleUpgradesAndDowngrades(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + fileName := getControllerFolder() + "/tests/trace-decision-tree-complex.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + // create 3 trace instances + latestOperandVersion := "v10_4_1" + traceName := "example-trace" + complexTraceName := traceName + "-one" + complexTrace2Name := traceName + "-two" + complexTrace3Name := traceName + "-three" + complexTracePodName := traceName + "-pod-one" + complexTrace2PodName := traceName + "-pod-two" + complexTrace3PodName := traceName + "-pod-three" + complexTrace := createTraceCR(complexTraceName, complexTracePodName, "v10_4_1.2", nil) + complexTrace2 := createTraceCR(complexTrace2Name, complexTrace2PodName, "v10_4_1.3", nil) + complexTrace3 := createTraceCR(complexTrace3Name, complexTrace3PodName, "v10_4_1.4", nil) + tests := []Test{ + {"create Trace CR based on path index 2 of complex decision tree", nil, r.CreateOrUpdate(complexTrace, nil, func() error { return nil })}, + {"create Trace CR based on path index 3 of complex decision tree", nil, r.CreateOrUpdate(complexTrace2, nil, func() error { return nil })}, + {"create Trace CR based on path index 4 of complex decision tree", nil, r.CreateOrUpdate(complexTrace3, nil, func() error { return nil })}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // init the leader tracker but on a higher version of the decision tree + latestOperandVersion = "v10_4_500" + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tests = []Test{ + {"reconcileLeaderTracker at version v10_4_500", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // check the Trace leader tracker upgraded the 3 trace instances created + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 := leader.LeaderTracker{ + Name: complexTracePodName, + Owner: "", // no owners associated with the Trace CRs because this decision tree (only for test) is not registered to use with the operator + Path: "v10_4_500.a.b.b.*", // These paths have been upgraded to v10_4_500 based on replaceMap + PathIndex: "v10_4_500.0", // These path indices have been upgraded to v10_4_500 based on replaceMap + } + leader2 := leader.LeaderTracker{ + Name: complexTrace2PodName, + Owner: "", + Path: "v10_4_500.a.f.g.i.bar", + PathIndex: "v10_4_500.4", + } + leader3 := leader.LeaderTracker{ + Name: complexTrace3PodName, + Owner: "", + Path: "v10_4_1.j.fizz", + PathIndex: "v10_4_1.4", + } + + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers is not nil", leaderTrackers != nil, true}, + {"get Trace leader trackers matches length", 3, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader trackers contains leader2", true, leader.LeaderTrackersContains(leaderTrackers, leader2)}, + {"get Trace leader trackers contains leader3", true, leader.LeaderTrackersContains(leaderTrackers, leader3)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // downgrade the decision tree version and initialize the leader tracker + latestOperandVersion = "v10_3_3" + rsf = r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + tests = []Test{ + {"downgrade Trace Leader Tracker from v10_4_500 to v10_3_3", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + rsf = r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder) + + leaderTracker, leaderTrackers, err = leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 = leader.LeaderTracker{ + Name: complexTracePodName, + Owner: "", // no owners associated with the Trace CRs because this decision tree (only for test) is not registered to use with the operator + Path: "v10_3_3.a.*", // This path has been upgraded to v10_3_3 based on replaceMap + PathIndex: "v10_3_3.0", // This path index has been upgraded to v10_3_3.0 based on replaceMap + } + leader2 = leader.LeaderTracker{ + Name: complexTrace2PodName, + Owner: "", + Path: "v10_4_1.a.b.e.false", + PathIndex: "v10_4_1.3", + } + leader3 = leader.LeaderTracker{ + Name: complexTrace3PodName, + Owner: "", + Path: "v10_4_1.j.fizz", + PathIndex: "v10_4_1.4", + } + + tests = []Test{ + {"get Trace leader tracker error", nil, err}, + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers matches length", 3, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader trackers contains leader2", true, leader.LeaderTrackersContains(leaderTrackers, leader2)}, + {"get Trace leader trackers contains leader3", true, leader.LeaderTrackersContains(leaderTrackers, leader3)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +// This tests that the Trace leader tracker can upgrade using the wildcard entry +func TestReconcileLeaderTrackerWhenTraceExistsWithWildcardUpgrade(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + fileName := getControllerFolder() + "/tests/trace-decision-tree-2-generations.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + latestOperandVersion := "v1_4_2" + + // create trace instance + traceName := "example-trace" + complexTraceName := traceName + "-one" + complexTracePodName := traceName + "-pod-one" + complexTrace := createTraceCR(complexTraceName, complexTracePodName, latestOperandVersion+".0", nil) + + // create another trace instance + complexTrace2Name := traceName + "-two" + complexTrace2PodName := traceName + "-pod-two" + complexTrace2 := createTraceCR(complexTrace2Name, complexTrace2PodName, latestOperandVersion+".0", nil) + + tests := []Test{ + {"create Trace CR based on path index 0 of 2nd gen decision tree", nil, r.CreateOrUpdate(complexTrace, nil, func() error { return nil })}, + {"create Trace CR based on path index 0 of 2nd gen decision tree", nil, r.CreateOrUpdate(complexTrace2, nil, func() error { return nil })}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // init the leader tracker but on a higher version of the decision tree + latestOperandVersion = "v10_4_3" // upgrade the version + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tests = []Test{ + {"reconcileLeaderTracker at version v10_4_3", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // validate the leader tracker upgraded the two Trace CRs created + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 := leader.LeaderTracker{ + Name: complexTracePodName, + Owner: "", // no owners associated with the Trace CRs because this decision tree (only for test) is not registered to use with the operator + Path: "v10_4_3.type.name.*", // This path has been upgraded to v10_3_3 based on replaceMap + PathIndex: "v10_4_3.1", // This path index has been upgraded to v10_3_3.0 based on replaceMap + } + leader2 := leader.LeaderTracker{ + Name: complexTrace2PodName, + Owner: "", + Path: "v10_4_3.type.name.*", + PathIndex: "v10_4_3.1", + } + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader tracker name", 2, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader trackers contains leader2", true, leader.LeaderTrackersContains(leaderTrackers, leader2)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +// This tests that the Trace leader tracker can upgrade and downgrade using the wildcard entry +func TestReconcileLeaderTrackerWhenTraceExistsWithMultipleWildcardUpgradesAndDowngrades(t *testing.T) { + instance, r := mockWebSphereLibertyTrace() + + fileName := getControllerFolder() + "/tests/trace-decision-tree-3-generations.yaml" + treeMap, replaceMap, _ := tree.ParseDecisionTree(TRACE_RESOURCE_SHARING_FILE_NAME, &fileName) + + // create 3 trace instances + latestOperandVersion := "v1_4_2" + traceName := "example-trace" + complexTraceName := traceName + "-one" + complexTrace2Name := traceName + "-two" + complexTrace3Name := traceName + "-three" + complexTracePodName := traceName + "-pod-one" + complexTrace2PodName := traceName + "-pod-two" + complexTrace3PodName := traceName + "-pod-three" + complexTrace := createTraceCR(complexTraceName, complexTracePodName, "v1_4_2.0", nil) + complexTrace2 := createTraceCR(complexTrace2Name, complexTrace2PodName, "v1_4_2.0", nil) + complexTrace3 := createTraceCR(complexTrace3Name, complexTrace3PodName, "v1_4_2.0", nil) + tests := []Test{ + {"create Trace CR based on path index 0 of 3rd gen decision tree", nil, r.CreateOrUpdate(complexTrace, nil, func() error { return nil })}, + {"create Trace CR based on path index 0 of 3rd gen decision tree", nil, r.CreateOrUpdate(complexTrace2, nil, func() error { return nil })}, + {"create Trace CR based on path index 0 of 3rd gen decision tree", nil, r.CreateOrUpdate(complexTrace3, nil, func() error { return nil })}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // init the leader tracker but on a higher version of the decision tree + latestOperandVersion = "v10_4_500" + assetsFolder := getAssetsFolder() + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tests = []Test{ + {"reconcileLeaderTracker at version v10_4_500", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // check the Trace leader tracker upgraded the 3 trace instances created + leaderTracker, leaderTrackers, err := leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 := leader.LeaderTracker{ + Name: complexTracePodName, + Owner: "", // no owners associated with the Trace CRs because this decision tree (only for test) is not registered to use with the operator + Path: "v10_4_500.type.name.*", // These paths have been upgraded to v10_4_500 based on replaceMap + PathIndex: "v10_4_500.3", // These path indices have been upgraded to v10_4_500 based on tree + } + leader2 := leader.LeaderTracker{ + Name: complexTrace2PodName, + Owner: "", + Path: "v10_4_500.type.name.*", + PathIndex: "v10_4_500.3", + } + leader3 := leader.LeaderTracker{ + Name: complexTrace3PodName, + Owner: "", + Path: "v10_4_500.type.name.*", + PathIndex: "v10_4_500.3", + } + + tests = []Test{ + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers is not nil", leaderTrackers != nil, true}, + {"get Trace leader trackers matches length", 3, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader trackers contains leader2", true, leader.LeaderTrackersContains(leaderTrackers, leader2)}, + {"get Trace leader trackers contains leader3", true, leader.LeaderTrackersContains(leaderTrackers, leader3)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + {"get Trace leader tracker error", nil, err}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + // downgrade the decision tree version and initialize the leader tracker + latestOperandVersion = "v10_4_3" + rsf = r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + tests = []Test{ + {"downgrade Trace Leader Tracker from v10_4_500 to v10_4_3", nil, tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder)}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } + + rsf = r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME) + rsf.SetCleanupUnusedResources(func() bool { return false }) // prevent removal if owner is empty because this test won't elect leaders + tree.ReconcileLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, rsf, treeMap, replaceMap, latestOperandVersion, TRACE_RESOURCE_SHARING_FILE_NAME, &assetsFolder) + + leaderTracker, leaderTrackers, err = leader.GetLeaderTracker(instance.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME, r.GetClient()) + leader1 = leader.LeaderTracker{ + Name: complexTracePodName, + Owner: "", // no owners associated with the Trace CRs because this decision tree (only for test) is not registered to use with the operator + Path: "v10_4_3.type.name.*", // This path has been downgraded to v10_4_3 based on replaceMap + PathIndex: "v10_4_3.1", // This path index has been downgraded to v10_4_3 based on tree + } + leader2 = leader.LeaderTracker{ + Name: complexTrace2PodName, + Owner: "", + Path: "v10_4_3.type.name.*", + PathIndex: "v10_4_3.1", + } + leader3 = leader.LeaderTracker{ + Name: complexTrace3PodName, + Owner: "", + Path: "v10_4_3.type.name.*", + PathIndex: "v10_4_3.1", + } + + tests = []Test{ + {"get Trace leader tracker error", nil, err}, + {"get Trace leader tracker name", "wlo-managed-leader-tracking-" + TRACE_RESOURCE_SHARING_FILE_NAME, leaderTracker.Name}, + {"get Trace leader tracker namespace", namespace, leaderTracker.Namespace}, + {"get Trace leader trackers matches length", 3, len(*leaderTrackers)}, + {"get Trace leader trackers contains leader1", true, leader.LeaderTrackersContains(leaderTrackers, leader1)}, + {"get Trace leader trackers contains leader2", true, leader.LeaderTrackersContains(leaderTrackers, leader2)}, + {"get Trace leader trackers contains leader3", true, leader.LeaderTrackersContains(leaderTrackers, leader3)}, + {"get Trace leader tracker label", latestOperandVersion, leaderTracker.Labels[leader.GetLeaderVersionLabel(lutils.LibertyURI)]}, + } + if err := verifyTests(tests); err != nil { + t.Fatalf("%v", err) + } +} + +func createEmptyLeaderTrackerSecret(resourceSharingFileName string) *corev1.Secret { + leaderTrackerName := "wlo-managed-leader-tracking-" + resourceSharingFileName + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: leaderTrackerName, + Namespace: namespace, + Labels: map[string]string{ + "app.kubernetes.io/instance": leaderTrackerName, + "app.kubernetes.io/managed-by": OperatorName, + "app.kubernetes.io/name": leaderTrackerName, + }, + }, + } +} + +func createTraceCR(traceName, tracePodName, resourcePathIndex string, prevPod *string) *webspherelibertyv1.WebSphereLibertyTrace { + if prevPod != nil && len(*prevPod) > 0 { + return &webspherelibertyv1.WebSphereLibertyTrace{ + ObjectMeta: metav1.ObjectMeta{ + Name: traceName, + Namespace: namespace, + Labels: map[string]string{ + leader.GetResourcePathIndexLabel(lutils.LibertyURI): resourcePathIndex, + "app.kubernetes.io/name": traceName, + }, + }, + Spec: webspherelibertyv1.WebSphereLibertyTraceSpec{ + PodName: tracePodName, + TraceSpecification: traceSpecification, + }, + Status: webspherelibertyv1.WebSphereLibertyTraceStatus{ + OperatedResource: webspherelibertyv1.OperatedResource{ + ResourceType: "pod", + ResourceName: *prevPod, + }, + }, + } + } + return &webspherelibertyv1.WebSphereLibertyTrace{ + ObjectMeta: metav1.ObjectMeta{ + Name: traceName, + Namespace: namespace, + Labels: map[string]string{ + leader.GetResourcePathIndexLabel(lutils.LibertyURI): resourcePathIndex, + "app.kubernetes.io/name": traceName, + }, + }, + Spec: webspherelibertyv1.WebSphereLibertyTraceSpec{ + PodName: tracePodName, + TraceSpecification: traceSpecification, + }, + } +} + +func mockWebSphereLibertyTrace() (*webspherelibertyv1.WebSphereLibertyTrace, *ReconcileWebSphereLibertyTrace) { + instance := createWebSphereLibertyTrace(name, namespace+"-other", webspherelibertyv1.WebSphereLibertyTraceSpec{}) // create instance outside of namespace + r := createReconcilerFromWebSphereLibertyTrace(instance) + instance.Namespace = namespace + return instance, r +} + +func createReconcilerFromWebSphereLibertyTrace(olt *webspherelibertyv1.WebSphereLibertyTrace) *ReconcileWebSphereLibertyTrace { + objs, s := []runtime.Object{olt}, scheme.Scheme + oltl := &webspherelibertyv1.WebSphereLibertyTraceList{} + s.AddKnownTypes(webspherelibertyv1.GroupVersion, olt) + s.AddKnownTypes(webspherelibertyv1.GroupVersion, oltl) + cl := fakeclient.NewFakeClient(objs...) + rol := &ReconcileWebSphereLibertyTrace{ + Client: cl, + Scheme: s, + Recorder: record.NewFakeRecorder(10), + RestConfig: &rest.Config{}, + } + return rol +} + +func createWebSphereLibertyTrace(n, ns string, spec webspherelibertyv1.WebSphereLibertyTraceSpec) *webspherelibertyv1.WebSphereLibertyTrace { + app := &webspherelibertyv1.WebSphereLibertyTrace{ + ObjectMeta: metav1.ObjectMeta{Name: n, Namespace: ns}, + Spec: spec, + } + return app +} diff --git a/internal/controller/webspherelibertyapplication_controller.go b/internal/controller/webspherelibertyapplication_controller.go index b1c4a1d1..81f201a2 100644 --- a/internal/controller/webspherelibertyapplication_controller.go +++ b/internal/controller/webspherelibertyapplication_controller.go @@ -27,6 +27,8 @@ import ( "github.com/application-stacks/runtime-component-operator/common" "github.com/go-logr/logr" + "github.com/OpenLiberty/open-liberty-operator/utils/leader" + tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" lutils "github.com/WASdev/websphere-liberty-operator/utils" oputils "github.com/application-stacks/runtime-component-operator/utils" @@ -174,6 +176,8 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. return r.ManageError(err, common.StatusConditionTypeReconciled, instance) } + baseRSF := r.createResourceSharingFactoryBase() + // Check if the WebSphereLibertyApplication instance is marked to be deleted, which is // indicated by the deletion timestamp being set. isInstanceMarkedToBeDeleted := instance.GetDeletionTimestamp() != nil @@ -181,7 +185,7 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. if lutils.Contains(instance.GetFinalizers(), applicationFinalizer) { // Run finalization logic for applicationFinalizer. If the finalization logic fails, don't remove the // finalizer so that we can retry during the next reconciliation. - if err := r.finalizeWebSphereLibertyApplication(reqLogger, instance, instance.Name+"-serviceability", instance.Namespace); err != nil { + if err := r.finalizeWebSphereLibertyApplication(reqLogger, instance, baseRSF, instance.Name+"-serviceability", instance.Namespace); err != nil { return reconcile.Result{}, err } @@ -268,30 +272,35 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. } // Reconciles the shared LTPA state for the instance namespace - var ltpaMetadataList *lutils.LTPAMetadataList - var ltpaKeysMetadata, ltpaConfigMetadata *lutils.LTPAMetadata + var ltpaMetadataList *leader.LTPAMetadataList + var ltpaKeysMetadata, ltpaConfigMetadata *leader.LTPAMetadata + var ltpaRSF tree.ResourceSharingFactory if r.isLTPAKeySharingEnabled(instance) { - leaderMetadataList, err := r.reconcileResourceTrackingState(instance, LTPA_RESOURCE_SHARING_FILE_NAME) + rsf, leaderMetadataList, err := r.reconcileResourceTrackingState(instance, LTPA_RESOURCE_SHARING_FILE_NAME) if err != nil { return r.ManageError(err, common.StatusConditionTypeReconciled, instance) } - ltpaMetadataList = leaderMetadataList.(*lutils.LTPAMetadataList) + ltpaRSF = rsf + ltpaMetadataList = leaderMetadataList.(*leader.LTPAMetadataList) if ltpaMetadataList != nil && len(ltpaMetadataList.Items) == 2 { - ltpaKeysMetadata = ltpaMetadataList.Items[0].(*lutils.LTPAMetadata) - ltpaConfigMetadata = ltpaMetadataList.Items[1].(*lutils.LTPAMetadata) + ltpaKeysMetadata = ltpaMetadataList.Items[0].(*leader.LTPAMetadata) + ltpaConfigMetadata = ltpaMetadataList.Items[1].(*leader.LTPAMetadata) } } + // Reconciles the shared password encryption key state for the instance namespace only if the shared key already exists - var passwordEncryptionMetadataList *lutils.PasswordEncryptionMetadataList - passwordEncryptionMetadata := &lutils.PasswordEncryptionMetadata{} + var passwordEncryptionMetadataList *leader.PasswordEncryptionMetadataList + passwordEncryptionMetadata := &leader.PasswordEncryptionMetadata{} + var passwordEncryptionRSF tree.ResourceSharingFactory if r.isUsingPasswordEncryptionKeySharing(instance, passwordEncryptionMetadata) { - leaderMetadataList, err := r.reconcileResourceTrackingState(instance, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME) + rsf, leaderMetadataList, err := r.reconcileResourceTrackingState(instance, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME) if err != nil { return r.ManageError(err, common.StatusConditionTypeReconciled, instance) } - passwordEncryptionMetadataList = leaderMetadataList.(*lutils.PasswordEncryptionMetadataList) + passwordEncryptionRSF = rsf + passwordEncryptionMetadataList = leaderMetadataList.(*leader.PasswordEncryptionMetadataList) if passwordEncryptionMetadataList != nil && len(passwordEncryptionMetadataList.Items) == 1 { - passwordEncryptionMetadata = passwordEncryptionMetadataList.Items[0].(*lutils.PasswordEncryptionMetadata) + passwordEncryptionMetadata = passwordEncryptionMetadataList.Items[0].(*leader.PasswordEncryptionMetadata) } } else if r.isPasswordEncryptionKeySharingEnabled(instance) { // error if the password encryption key sharing is enabled but the Secret is not found @@ -497,14 +506,14 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. } // Manage the shared password encryption key Secret if it exists - message, encryptionSecretName, passwordEncryptionKeyLastRotation, err := r.reconcilePasswordEncryptionKey(instance, passwordEncryptionMetadata) + message, encryptionSecretName, passwordEncryptionKeyLastRotation, err := r.reconcilePasswordEncryptionKey(passwordEncryptionRSF, baseRSF, instance, passwordEncryptionMetadata) if err != nil { reqLogger.Error(err, message) return r.ManageError(err, common.StatusConditionTypeReconciled, instance) } // Create and manage the shared LTPA keys Secret if the feature is enabled - message, ltpaSecretName, ltpaKeysLastRotation, err := r.reconcileLTPAKeys(instance, ltpaKeysMetadata) + message, ltpaSecretName, ltpaKeysLastRotation, err := r.reconcileLTPAKeys(ltpaRSF, baseRSF, instance, ltpaKeysMetadata) if err != nil { reqLogger.Error(err, message) return r.ManageError(err, common.StatusConditionTypeReconciled, instance) @@ -518,7 +527,7 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. } // Using the LTPA keys and config metadata, create and manage the shared LTPA Liberty server XML if the feature is enabled - message, ltpaXMLSecretName, err := r.reconcileLTPAConfig(instance, ltpaKeysMetadata, ltpaConfigMetadata, passwordEncryptionMetadata, ltpaKeysLastRotation, lastKeyRelatedRotation) + message, ltpaXMLSecretName, err := r.reconcileLTPAConfig(ltpaRSF, baseRSF, instance, ltpaKeysMetadata, ltpaConfigMetadata, passwordEncryptionMetadata, ltpaKeysLastRotation, lastKeyRelatedRotation) if err != nil { reqLogger.Error(err, message) return r.ManageError(err, common.StatusConditionTypeReconciled, instance) @@ -590,12 +599,12 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. return err } lutils.AddPodTemplateSpecAnnotation(&statefulSet.Spec.Template, lastRotationAnnotation) - if instance.Status.GetReferences()[lutils.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)] != encryptionSecretName { - instance.Status.SetReference(lutils.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME), encryptionSecretName) + if instance.Status.GetReferences()[leader.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)] != encryptionSecretName { + instance.Status.SetReference(leader.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME), encryptionSecretName) } } else { - lutils.RemovePodTemplateSpecAnnotationByKey(&statefulSet.Spec.Template, lutils.GetLastRotationLabelKey(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)) - lutils.RemoveMapElementByKey(instance.Status.GetReferences(), lutils.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)) + lutils.RemovePodTemplateSpecAnnotationByKey(&statefulSet.Spec.Template, leader.GetLastRotationLabelKey(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, lutils.LibertyURI)) + lutils.RemoveMapElementByKey(instance.Status.GetReferences(), leader.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)) } if r.isLTPAKeySharingEnabled(instance) && len(ltpaSecretName) > 0 { @@ -612,12 +621,12 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. return err } lutils.AddPodTemplateSpecAnnotation(&statefulSet.Spec.Template, configLastRotationAnnotation) - if instance.Status.GetReferences()[lutils.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME)] != ltpaSecretName { - instance.Status.SetReference(lutils.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME), ltpaSecretName) + if instance.Status.GetReferences()[leader.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME)] != ltpaSecretName { + instance.Status.SetReference(leader.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME), ltpaSecretName) } } else { - lutils.RemovePodTemplateSpecAnnotationByKey(&statefulSet.Spec.Template, lutils.GetLastRotationLabelKey(LTPA_RESOURCE_SHARING_FILE_NAME)) - lutils.RemoveMapElementByKey(instance.Status.GetReferences(), lutils.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME)) + lutils.RemovePodTemplateSpecAnnotationByKey(&statefulSet.Spec.Template, leader.GetLastRotationLabelKey(LTPA_RESOURCE_SHARING_FILE_NAME, lutils.LibertyURI)) + lutils.RemoveMapElementByKey(instance.Status.GetReferences(), leader.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME)) } return nil }) @@ -687,12 +696,12 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. return err } lutils.AddPodTemplateSpecAnnotation(&deploy.Spec.Template, lastRotationAnnotation) - if instance.Status.GetReferences()[lutils.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)] != encryptionSecretName { - instance.Status.SetReference(lutils.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME), encryptionSecretName) + if instance.Status.GetReferences()[leader.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)] != encryptionSecretName { + instance.Status.SetReference(leader.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME), encryptionSecretName) } } else { - lutils.RemovePodTemplateSpecAnnotationByKey(&deploy.Spec.Template, lutils.GetLastRotationLabelKey(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)) - lutils.RemoveMapElementByKey(instance.Status.GetReferences(), lutils.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)) + lutils.RemovePodTemplateSpecAnnotationByKey(&deploy.Spec.Template, leader.GetLastRotationLabelKey(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME, lutils.LibertyURI)) + lutils.RemoveMapElementByKey(instance.Status.GetReferences(), leader.GetTrackedResourceName(PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME)) } if r.isLTPAKeySharingEnabled(instance) && len(ltpaSecretName) > 0 { @@ -709,12 +718,12 @@ func (r *ReconcileWebSphereLiberty) Reconcile(ctx context.Context, request ctrl. return err } lutils.AddPodTemplateSpecAnnotation(&deploy.Spec.Template, configLastRotationAnnotation) - if instance.Status.GetReferences()[lutils.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME)] != ltpaSecretName { - instance.Status.SetReference(lutils.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME), ltpaSecretName) + if instance.Status.GetReferences()[leader.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME)] != ltpaSecretName { + instance.Status.SetReference(leader.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME), ltpaSecretName) } } else { - lutils.RemovePodTemplateSpecAnnotationByKey(&deploy.Spec.Template, lutils.GetLastRotationLabelKey(LTPA_RESOURCE_SHARING_FILE_NAME)) - lutils.RemoveMapElementByKey(instance.Status.GetReferences(), lutils.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME)) + lutils.RemovePodTemplateSpecAnnotationByKey(&deploy.Spec.Template, leader.GetLastRotationLabelKey(LTPA_RESOURCE_SHARING_FILE_NAME, lutils.LibertyURI)) + lutils.RemoveMapElementByKey(instance.Status.GetReferences(), leader.GetTrackedResourceName(LTPA_RESOURCE_SHARING_FILE_NAME)) } return nil }) @@ -988,9 +997,9 @@ func getMonitoringEnabledLabelName(ba common.BaseComponent) string { return "monitor." + ba.GetGroupName() + "/enabled" } -func (r *ReconcileWebSphereLiberty) finalizeWebSphereLibertyApplication(reqLogger logr.Logger, wlapp *webspherelibertyv1.WebSphereLibertyApplication, pvcName string, pvcNamespace string) error { - r.RemoveLeaderTrackerReference(wlapp, LTPA_RESOURCE_SHARING_FILE_NAME) - r.RemoveLeaderTrackerReference(wlapp, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME) +func (r *ReconcileWebSphereLiberty) finalizeWebSphereLibertyApplication(reqLogger logr.Logger, wlapp *webspherelibertyv1.WebSphereLibertyApplication, baseRSF tree.ResourceSharingFactoryBase, pvcName string, pvcNamespace string) error { + tree.RemoveLeaderTrackerReference(baseRSF, wlapp.GetName(), wlapp.GetNamespace(), OperatorName, OperatorShortName, LTPA_RESOURCE_SHARING_FILE_NAME) + tree.RemoveLeaderTrackerReference(baseRSF, wlapp.GetName(), wlapp.GetNamespace(), OperatorName, OperatorShortName, PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME) r.deletePVC(reqLogger, pvcName, pvcNamespace) return nil } diff --git a/internal/controller/webspherelibertyapplication_resource_sharing.go b/internal/controller/webspherelibertyapplication_resource_sharing.go new file mode 100644 index 00000000..abb32beb --- /dev/null +++ b/internal/controller/webspherelibertyapplication_resource_sharing.go @@ -0,0 +1,212 @@ +package controller + +import ( + "fmt" + + "github.com/OpenLiberty/open-liberty-operator/utils/leader" + tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" + wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" + lutils "github.com/WASdev/websphere-liberty-operator/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type WebSphereLibertyApplicationResourceSharingFactory struct { + resourcesFunc func() (leader.LeaderTrackerMetadataList, error) + leaderTrackersFunc func(assetsFolder *string) ([]*unstructured.UnstructuredList, []string, error) + createOrUpdateFunc func(obj client.Object, owner metav1.Object, cb func() error) error + deleteResourcesFunc func(obj client.Object) error + leaderTrackerNameFunc func(map[string]interface{}) (string, error) + cleanupUnusedResourcesFunc func() bool + clientFunc func() client.Client + libertyURI string +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) Resources() func() (leader.LeaderTrackerMetadataList, error) { + return rsf.resourcesFunc +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) SetResources(fn func() (leader.LeaderTrackerMetadataList, error)) { + rsf.resourcesFunc = fn +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) LeaderTrackers() func(*string) ([]*unstructured.UnstructuredList, []string, error) { + return rsf.leaderTrackersFunc +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) SetLeaderTrackers(fn func(*string) ([]*unstructured.UnstructuredList, []string, error)) { + rsf.leaderTrackersFunc = fn +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) CreateOrUpdate() func(obj client.Object, owner metav1.Object, cb func() error) error { + return rsf.createOrUpdateFunc +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) SetCreateOrUpdate(fn func(obj client.Object, owner metav1.Object, cb func() error) error) { + rsf.createOrUpdateFunc = fn +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) DeleteResources() func(obj client.Object) error { + return rsf.deleteResourcesFunc +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) SetDeleteResources(fn func(obj client.Object) error) { + rsf.deleteResourcesFunc = fn +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) LeaderTrackerName() func(map[string]interface{}) (string, error) { + return rsf.leaderTrackerNameFunc +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) SetLeaderTrackerName(fn func(map[string]interface{}) (string, error)) { + rsf.leaderTrackerNameFunc = fn +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) CleanupUnusedResources() func() bool { + return rsf.cleanupUnusedResourcesFunc +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) SetCleanupUnusedResources(fn func() bool) { + rsf.cleanupUnusedResourcesFunc = fn +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) Client() func() client.Client { + return rsf.clientFunc +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) SetClient(fn func() client.Client) { + rsf.clientFunc = fn +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) LibertyURI() string { + return rsf.libertyURI +} + +func (rsf *WebSphereLibertyApplicationResourceSharingFactory) SetLibertyURI(uri string) { + rsf.libertyURI = uri +} + +func (r *ReconcileWebSphereLiberty) createResourceSharingFactoryBase() tree.ResourceSharingFactoryBase { + rsf := &WebSphereLibertyApplicationResourceSharingFactory{} + rsf.SetCreateOrUpdate(func(obj client.Object, owner metav1.Object, cb func() error) error { + return r.CreateOrUpdate(obj, owner, cb) + }) + rsf.SetDeleteResources(func(obj client.Object) error { + return r.DeleteResource(obj) + }) + rsf.SetCleanupUnusedResources(func() bool { + return false + }) + rsf.SetClient(func() client.Client { + return r.GetClient() + }) + rsf.SetLibertyURI(lutils.LibertyURI) + return rsf +} + +func (r *ReconcileWebSphereLiberty) createResourceSharingFactory(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, leaderTrackerType string) tree.ResourceSharingFactory { + var rsf *WebSphereLibertyApplicationResourceSharingFactory + rsfb := r.createResourceSharingFactoryBase() + rsf = rsfb.(*WebSphereLibertyApplicationResourceSharingFactory) + rsf.SetLeaderTrackers(func(assetsFolder *string) ([]*unstructured.UnstructuredList, []string, error) { + return r.WebSphereLibertyApplicationLeaderTrackerGenerator(instance, treeMap, replaceMap, latestOperandVersion, leaderTrackerType, assetsFolder) + }) + rsf.SetLeaderTrackerName(func(obj map[string]interface{}) (string, error) { + nameString, _, err := unstructured.NestedString(obj, "metadata", "name") // the LTPA and Password Encryption Secret will both use their .metadata.name as the leaderTracker key identifier + return nameString, err + }) + rsf.SetResources(func() (leader.LeaderTrackerMetadataList, error) { + return r.WebSphereLibertyApplicationSharedResourceGenerator(instance, treeMap, latestOperandVersion, leaderTrackerType) + }) + return rsf + +} + +func (r *ReconcileWebSphereLiberty) reconcileResourceTrackingState(instance *wlv1.WebSphereLibertyApplication, leaderTrackerType string) (tree.ResourceSharingFactory, leader.LeaderTrackerMetadataList, error) { + treeMap, replaceMap, err := tree.ParseDecisionTree(leaderTrackerType, nil) + if err != nil { + return nil, nil, err + } + latestOperandVersion, err := tree.GetLatestOperandVersion(treeMap, "") + if err != nil { + return nil, nil, err + } + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, leaderTrackerType) + trackerMetadataList, err := tree.ReconcileResourceTrackingState(instance.GetNamespace(), OperatorName, OperatorShortName, leaderTrackerType, rsf, treeMap, replaceMap, latestOperandVersion) + return rsf, trackerMetadataList, err +} + +func (r *ReconcileWebSphereLiberty) WebSphereLibertyApplicationSharedResourceGenerator(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, latestOperandVersion, leaderTrackerType string) (leader.LeaderTrackerMetadataList, error) { + // return the metadata specific to the operator version, instance configuration, and shared resource being reconciled + if leaderTrackerType == LTPA_RESOURCE_SHARING_FILE_NAME { + ltpaMetadataList, err := r.reconcileLTPAMetadata(instance, treeMap, latestOperandVersion, nil) + if err != nil { + return nil, err + } + return ltpaMetadataList, nil + } + if leaderTrackerType == PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME { + passwordEncryptionMetadataList, err := r.reconcilePasswordEncryptionMetadata(treeMap, latestOperandVersion) + if err != nil { + return nil, err + } + return passwordEncryptionMetadataList, nil + } + return nil, fmt.Errorf("a leaderTrackerType was not provided when running reconcileResourceTrackingState") +} + +func (r *ReconcileWebSphereLiberty) WebSphereLibertyApplicationLeaderTrackerGenerator(instance *wlv1.WebSphereLibertyApplication, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, leaderTrackerType string, assetsFolder *string) ([]*unstructured.UnstructuredList, []string, error) { + var resourcesMatrix []*unstructured.UnstructuredList + var resourcesRootNameList []string + if leaderTrackerType == LTPA_RESOURCE_SHARING_FILE_NAME { + // 1. Add LTPA key Secret + resourcesList, resourceRootName, keyErr := r.GetLTPAKeyResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder) + if keyErr != nil { + return nil, nil, keyErr + } + resourcesMatrix = append(resourcesMatrix, resourcesList) + resourcesRootNameList = append(resourcesRootNameList, resourceRootName) + // 2. Add LTPA password Secret (config 1) + resourcesList, resourceRootName, keyErr = r.GetLTPAConfigResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder, LTPA_CONFIG_1_RESOURCE_SHARING_FILE_NAME) + if keyErr != nil { + return nil, nil, keyErr + } + resourcesMatrix = append(resourcesMatrix, resourcesList) + resourcesRootNameList = append(resourcesRootNameList, resourceRootName) + // 3. Add LTPA password Secret (config 2) + resourcesList, resourceRootName, keyErr = r.GetLTPAConfigResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder, LTPA_CONFIG_2_RESOURCE_SHARING_FILE_NAME) + if keyErr != nil { + return nil, nil, keyErr + } + resourcesMatrix = append(resourcesMatrix, resourcesList) + resourcesRootNameList = append(resourcesRootNameList, resourceRootName) + } else if leaderTrackerType == PASSWORD_ENCRYPTION_RESOURCE_SHARING_FILE_NAME { + resourcesList, resourceRootName, passwordErr := r.GetPasswordEncryptionResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder) + if passwordErr != nil { + return nil, nil, passwordErr + } + resourcesMatrix = append(resourcesMatrix, resourcesList) + resourcesRootNameList = append(resourcesRootNameList, resourceRootName) + } else { + return nil, nil, fmt.Errorf("a valid leaderTrackerType was not specified for createNewLeaderTrackerList") + } + return resourcesMatrix, resourcesRootNameList, nil +} + +func hasLTPAKeyResourceSuffixesEnv(instance *wlv1.WebSphereLibertyApplication) (string, bool) { + return hasResourceSuffixesEnv(instance, "LTPA_KEY_RESOURCE_SUFFIXES") +} + +func hasLTPAConfigResourceSuffixesEnv(instance *wlv1.WebSphereLibertyApplication) (string, bool) { + return hasResourceSuffixesEnv(instance, "LTPA_CONFIG_RESOURCE_SUFFIXES") +} + +func hasResourceSuffixesEnv(instance *wlv1.WebSphereLibertyApplication, envName string) (string, bool) { + for _, env := range instance.GetEnv() { + if env.Name == envName { + return env.Value, true + } + } + return "", false +} diff --git a/internal/controller/webspherelibertytrace_controller.go b/internal/controller/webspherelibertytrace_controller.go index 7809b93e..c7246a26 100644 --- a/internal/controller/webspherelibertytrace_controller.go +++ b/internal/controller/webspherelibertytrace_controller.go @@ -18,26 +18,33 @@ package controller import ( "context" + "fmt" "os" + "reflect" "strconv" "time" + "github.com/OpenLiberty/open-liberty-operator/utils/leader" + tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" lutils "github.com/WASdev/websphere-liberty-operator/utils" oputils "github.com/application-stacks/runtime-component-operator/utils" "github.com/go-logr/logr" webspherelibertyv1 "github.com/WASdev/websphere-liberty-operator/api/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" + kerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/rest" "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/apiutil" "sigs.k8s.io/controller-runtime/pkg/controller" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" "sigs.k8s.io/controller-runtime/pkg/event" "sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/reconcile" @@ -60,6 +67,7 @@ const serviceabilityDir = "/serviceability" // +kubebuilder:rbac:groups=liberty.websphere.ibm.com,resources=webspherelibertytraces;webspherelibertytraces/status;webspherelibertytraces/finalizers,verbs=get;list;watch;create;update;patch;delete,namespace=websphere-liberty-operator // +kubebuilder:rbac:groups=core,resources=pods;pods/exec,verbs=get;list;watch;create;update;patch;delete,namespace=websphere-liberty-operator +// +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch;create;update;patch;delete,namespace=websphere-liberty-operator // Reconcile reads that state of the cluster for a WebSphereLibertyTrace object and makes changes based on the state read // and what is in the WebSphereLibertyTrace.Spec @@ -75,7 +83,7 @@ func (r *ReconcileWebSphereLibertyTrace) Reconcile(ctx context.Context, request instance := &webspherelibertyv1.WebSphereLibertyTrace{} err := r.Client.Get(context.TODO(), request.NamespacedName, instance) if err != nil { - if errors.IsNotFound(err) { + if kerrors.IsNotFound(err) { // Request object not found, could have been deleted after reconcile request. // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. // Return and don't requeue @@ -89,6 +97,28 @@ func (r *ReconcileWebSphereLibertyTrace) Reconcile(ctx context.Context, request instance.Initialize() + // Reconciles the shared Trace state for the instance namespace + var traceMetadataList *leader.TraceMetadataList + var traceMetadata, prevPodTraceMetadata *leader.TraceMetadata + rsf, leaderMetadataList, err := r.reconcileResourceTrackingState(instance, TRACE_RESOURCE_SHARING_FILE_NAME) + + if err != nil { + return reconcile.Result{}, err + } + traceMetadataList = leaderMetadataList.(*leader.TraceMetadataList) + if traceMetadataList != nil { + numTraceItems := len(traceMetadataList.Items) + if numTraceItems >= 1 { + traceMetadata = traceMetadataList.Items[0].(*leader.TraceMetadata) + } + if numTraceItems >= 2 { + prevPodTraceMetadata = traceMetadataList.Items[1].(*leader.TraceMetadata) + } + } + if traceMetadata == nil { + return reconcile.Result{Requeue: true, RequeueAfter: time.Second}, fmt.Errorf("the WebSphereLibertyTrace controller could not be initialized correctly") + } + //Pod is expected to be from the same namespace as the CR instance podNamespace := instance.Namespace podName := instance.Spec.PodName @@ -97,6 +127,19 @@ func (r *ReconcileWebSphereLibertyTrace) Reconcile(ctx context.Context, request prevTraceEnabled := instance.GetStatus().GetCondition(webspherelibertyv1.OperationStatusConditionTypeEnabled).Status podChanged := prevPodName != podName + isPrevPodTraceDisabled := false + // if pod changed then disable prevPod (if possible) + if podChanged && prevTraceEnabled == corev1.ConditionTrue && traceMetadata != nil && prevPodTraceMetadata != nil && traceMetadata.Name != prevPodTraceMetadata.Name { + _, thisInstanceIsLeader, _, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), prevPodTraceMetadata, TRACE_RESOURCE_SHARING_FILE_NAME, true) + if err != nil && !kerrors.IsNotFound(err) { + return reconcile.Result{Requeue: true, RequeueAfter: time.Second}, err + } + if thisInstanceIsLeader { + r.disableTraceOnPrevPod(reqLogger, prevPodName, podNamespace) + isPrevPodTraceDisabled = true + } + } + // Check if the WebSphereLibertyTrace instance is marked to be deleted, which is // indicated by the deletion timestamp being set. isInstanceMarkedToBeDeleted := instance.GetDeletionTimestamp() != nil @@ -104,13 +147,13 @@ func (r *ReconcileWebSphereLibertyTrace) Reconcile(ctx context.Context, request if lutils.Contains(instance.GetFinalizers(), traceFinalizer) { // Run finalization logic for traceFinalizer. If the finalization logic fails, don't remove the // finalizer so that we can retry during the next reconciliation. - if err := r.finalizeWebSphereLibertyTrace(reqLogger, instance, prevTraceEnabled, prevPodName, podNamespace); err != nil { + if err := r.finalizeWebSphereLibertyTrace(reqLogger, instance, rsf, prevTraceEnabled != corev1.ConditionTrue || isPrevPodTraceDisabled, prevPodName, podNamespace); err != nil { return reconcile.Result{}, err } // Remove traceFinalizer. Once all finalizers have been removed, the object will be deleted. instance.SetFinalizers(lutils.Remove(instance.GetFinalizers(), traceFinalizer)) - err := r.Client.Update(context.TODO(), instance) + err = r.Client.Update(context.TODO(), instance) if err != nil { return reconcile.Result{}, err } @@ -125,15 +168,25 @@ func (r *ReconcileWebSphereLibertyTrace) Reconcile(ctx context.Context, request } } - //If pod name changed, then stop tracing on previous pod (if trace was enabled on it) - if podChanged && (prevTraceEnabled == corev1.ConditionTrue) { - r.disableTraceOnPrevPod(reqLogger, prevPodName, podNamespace) + // exit if this instance is not the leader of podName + leaderName, thisInstanceIsLeader, _, err := tree.ReconcileLeader(rsf, OperatorName, OperatorShortName, instance.GetName(), instance.GetNamespace(), traceMetadata, TRACE_RESOURCE_SHARING_FILE_NAME, true) + if err != nil && !kerrors.IsNotFound(err) { + return reconcile.Result{Requeue: true, RequeueAfter: time.Second}, err + } + if !thisInstanceIsLeader { + err := fmt.Errorf("Trace could not be applied. Pod '%s' is already configured by WebSphereLibertyTrace instance '%s'.", podName, leaderName) + reqLogger.Error(err, "Trace was denied for instance '%s'; Trace instance '%s' is already managing pod '%s' in namespace '%s'", instance.GetName(), leaderName, podName, podNamespace) + // Possible race condition where two WebSphereLibertyTraces can swap pointing to each other's Pod and one of them doesn't get the leader tracker update in time. + // Requeue to dynamically resolve the leader tracker references over time. + r.UpdateStatus(err, webspherelibertyv1.OperationStatusConditionTypeEnabled, *instance, corev1.ConditionFalse, podName, podChanged) + return reconcile.Result{Requeue: true, RequeueAfter: time.Second}, fmt.Errorf("The trace leader is out of sync. Requeuing to recalibrate leader tracker references.") } + // run day-2 operation err = r.Client.Get(context.TODO(), types.NamespacedName{Name: podName, Namespace: podNamespace}, &corev1.Pod{}) - if err != nil && errors.IsNotFound(err) { + if err != nil && kerrors.IsNotFound(err) { //Pod is not found. Return and don't requeue - reqLogger.Error(err, "Pod "+podName+" was not found in namespace "+podNamespace) + reqLogger.Error(err, "Pod '%s' was not found in namespace '%s'", podName, podNamespace) return r.UpdateStatus(err, webspherelibertyv1.OperationStatusConditionTypeEnabled, *instance, corev1.ConditionFalse, podName, podChanged) } @@ -142,10 +195,10 @@ func (r *ReconcileWebSphereLibertyTrace) Reconcile(ctx context.Context, request if !podChanged && prevTraceEnabled == corev1.ConditionTrue { _, err = lutils.ExecuteCommandInContainer(r.RestConfig, podName, podNamespace, "app", []string{"/bin/sh", "-c", "rm -f " + traceConfigFile}) if err != nil { - reqLogger.Error(err, "Encountered error while disabling trace for pod "+podName+" in namespace "+podNamespace) + reqLogger.Error(err, "Encountered error while disabling trace for pod '%s' in namespace '%s'", podName, podNamespace) return r.UpdateStatus(err, webspherelibertyv1.OperationStatusConditionTypeEnabled, *instance, corev1.ConditionTrue, podName, podChanged) } - reqLogger.Info("Disabled trace for pod " + podName + " in namespace " + podNamespace) + reqLogger.Info("Disabled trace for pod '%s' in namespace '%s'", podName, podNamespace) } r.UpdateStatus(nil, webspherelibertyv1.OperationStatusConditionTypeEnabled, *instance, corev1.ConditionFalse, podName, podChanged) } else { @@ -225,7 +278,7 @@ func (r *ReconcileWebSphereLibertyTrace) UpdateStatus(issue error, conditionType func (r *ReconcileWebSphereLibertyTrace) disableTraceOnPrevPod(reqLogger logr.Logger, prevPodName string, podNamespace string) { err := r.Client.Get(context.TODO(), types.NamespacedName{Name: prevPodName, Namespace: podNamespace}, &corev1.Pod{}) - if err != nil && errors.IsNotFound(err) { + if err != nil && kerrors.IsNotFound(err) { //Previous Pod is not found. No-op reqLogger.Info("Previous pod " + prevPodName + " was not found in namespace " + podNamespace) } else { @@ -239,19 +292,71 @@ func (r *ReconcileWebSphereLibertyTrace) disableTraceOnPrevPod(reqLogger logr.Lo } } -func (r *ReconcileWebSphereLibertyTrace) finalizeWebSphereLibertyTrace(reqLogger logr.Logger, olt *webspherelibertyv1.WebSphereLibertyTrace, prevTraceEnabled corev1.ConditionStatus, prevPodName string, podNamespace string) error { - if prevTraceEnabled == corev1.ConditionTrue { +// CreateOrUpdate ... +func (r *ReconcileWebSphereLibertyTrace) CreateOrUpdate(obj client.Object, owner metav1.Object, reconcile func() error) error { + + if owner != nil { + controllerutil.SetControllerReference(owner, obj, r.Scheme) + } + + result, err := controllerutil.CreateOrUpdate(context.TODO(), r.GetClient(), obj, reconcile) + if err != nil { + return err + } + + var gvk schema.GroupVersionKind + gvk, err = apiutil.GVKForObject(obj, r.Scheme) + if err == nil { + r.Log.Info("Reconciled", "Kind", gvk.Kind, "Namespace", obj.GetNamespace(), "Name", obj.GetName(), "Status", result) + } + + return err +} + +// DeleteResource deletes kubernetes resource +func (r *ReconcileWebSphereLibertyTrace) DeleteResource(obj client.Object) error { + err := r.GetClient().Delete(context.TODO(), obj) + if err != nil { + if !kerrors.IsNotFound(err) { + //log.Error(err, "Unable to delete object ", "object", obj) + return err + } + return nil + } + + _, ok := obj.(metav1.Object) + if !ok { + err := fmt.Errorf("%T is not a metav1.Object", obj) + //log.Error(err, "Failed to convert into metav1.Object") + return err + } + + _, err = apiutil.GVKForObject(obj, r.Scheme) + if err == nil { + //log.Info("Reconciled", "Kind", gvk.Kind, "Name", owner.GetName(), "Status", "deleted") + } + return nil +} + +// GetClient returns client +func (r *ReconcileWebSphereLibertyTrace) GetClient() client.Client { + return r.Client +} + +func (r *ReconcileWebSphereLibertyTrace) finalizeWebSphereLibertyTrace(reqLogger logr.Logger, wlt *webspherelibertyv1.WebSphereLibertyTrace, rsf tree.ResourceSharingFactory, isPrevPodTraceDisabled bool, prevPodName, podNamespace string) error { + if !isPrevPodTraceDisabled { r.disableTraceOnPrevPod(reqLogger, prevPodName, podNamespace) } + tree.RemoveLeaderTrackerReference(rsf, wlt.GetName(), wlt.GetNamespace(), OperatorName, OperatorShortName, TRACE_RESOURCE_SHARING_FILE_NAME) return nil } -func (r *ReconcileWebSphereLibertyTrace) addFinalizer(reqLogger logr.Logger, olt *webspherelibertyv1.WebSphereLibertyTrace) error { +func (r *ReconcileWebSphereLibertyTrace) addFinalizer(reqLogger logr.Logger, wlt *webspherelibertyv1.WebSphereLibertyTrace) error { reqLogger.Info("Adding Finalizer for WebSphereLibertyTrace") - olt.SetFinalizers(append(olt.GetFinalizers(), traceFinalizer)) + wlt.SetFinalizers(append(wlt.GetFinalizers(), traceFinalizer)) // Update CR - err := r.Client.Update(context.TODO(), olt) + err := r.Client.Update(context.TODO(), wlt) if err != nil { reqLogger.Error(err, "Failed to update WebSphereLibertyTrace with finalizer") return err @@ -278,8 +383,8 @@ func (r *ReconcileWebSphereLibertyTrace) SetupWithManager(mgr ctrl.Manager) erro pred := predicate.Funcs{ UpdateFunc: func(e event.UpdateEvent) bool { - // Ignore updates to CR status in which case metadata.Generation does not change - return e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() && (isClusterWide || watchNamespacesMap[e.ObjectOld.GetNamespace()]) + // Ignore updates to CR status in which case metadata.Generation does not change, unless labels were changed + return (e.ObjectOld.GetGeneration() != e.ObjectNew.GetGeneration() || !reflect.DeepEqual(e.ObjectOld.GetLabels(), e.ObjectNew.GetLabels())) && (isClusterWide || watchNamespacesMap[e.ObjectOld.GetNamespace()]) }, CreateFunc: func(e event.CreateEvent) bool { return isClusterWide || watchNamespacesMap[e.Object.GetNamespace()] diff --git a/internal/controller/webspherelibertytrace_resource_sharing.go b/internal/controller/webspherelibertytrace_resource_sharing.go new file mode 100644 index 00000000..142e8f24 --- /dev/null +++ b/internal/controller/webspherelibertytrace_resource_sharing.go @@ -0,0 +1,215 @@ +package controller + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/OpenLiberty/open-liberty-operator/utils/leader" + tree "github.com/OpenLiberty/open-liberty-operator/utils/tree" + wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" + lutils "github.com/WASdev/websphere-liberty-operator/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +type WebSphereLibertyTraceResourceSharingFactory struct { + resourcesFunc func() (leader.LeaderTrackerMetadataList, error) + leaderTrackersFunc func(assetsFolder *string) ([]*unstructured.UnstructuredList, []string, error) + createOrUpdateFunc func(obj client.Object, owner metav1.Object, cb func() error) error + deleteResourcesFunc func(obj client.Object) error + leaderTrackerNameFunc func(map[string]interface{}) (string, error) + cleanupUnusedResourcesFunc func() bool + clientFunc func() client.Client + libertyURI string +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) Resources() func() (leader.LeaderTrackerMetadataList, error) { + return rsf.resourcesFunc +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) SetResources(fn func() (leader.LeaderTrackerMetadataList, error)) { + rsf.resourcesFunc = fn +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) LeaderTrackers() func(*string) ([]*unstructured.UnstructuredList, []string, error) { + return rsf.leaderTrackersFunc +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) SetLeaderTrackers(fn func(*string) ([]*unstructured.UnstructuredList, []string, error)) { + rsf.leaderTrackersFunc = fn +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) CreateOrUpdate() func(obj client.Object, owner metav1.Object, cb func() error) error { + return rsf.createOrUpdateFunc +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) SetCreateOrUpdate(fn func(obj client.Object, owner metav1.Object, cb func() error) error) { + rsf.createOrUpdateFunc = fn +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) DeleteResources() func(obj client.Object) error { + return rsf.deleteResourcesFunc +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) SetDeleteResources(fn func(obj client.Object) error) { + rsf.deleteResourcesFunc = fn +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) LeaderTrackerName() func(map[string]interface{}) (string, error) { + return rsf.leaderTrackerNameFunc +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) SetLeaderTrackerName(fn func(map[string]interface{}) (string, error)) { + rsf.leaderTrackerNameFunc = fn +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) CleanupUnusedResources() func() bool { + return rsf.cleanupUnusedResourcesFunc +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) SetCleanupUnusedResources(fn func() bool) { + rsf.cleanupUnusedResourcesFunc = fn +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) Client() func() client.Client { + return rsf.clientFunc +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) SetClient(fn func() client.Client) { + rsf.clientFunc = fn +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) LibertyURI() string { + return rsf.libertyURI +} + +func (rsf *WebSphereLibertyTraceResourceSharingFactory) SetLibertyURI(uri string) { + rsf.libertyURI = uri +} + +func (r *ReconcileWebSphereLibertyTrace) createResourceSharingFactoryBase() tree.ResourceSharingFactoryBase { + rsf := &WebSphereLibertyTraceResourceSharingFactory{} + rsf.SetCreateOrUpdate(func(obj client.Object, owner metav1.Object, cb func() error) error { + return r.CreateOrUpdate(obj, owner, cb) + }) + rsf.SetDeleteResources(func(obj client.Object) error { + return r.DeleteResource(obj) + }) + rsf.SetCleanupUnusedResources(func() bool { + return true + }) + rsf.SetClient(func() client.Client { + return r.GetClient() + }) + rsf.SetLibertyURI(lutils.LibertyURI) + return rsf +} + +func (r *ReconcileWebSphereLibertyTrace) createResourceSharingFactory(instance *wlv1.WebSphereLibertyTrace, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, leaderTrackerType string) tree.ResourceSharingFactory { + var rsf *WebSphereLibertyTraceResourceSharingFactory + rsfb := r.createResourceSharingFactoryBase() + rsf = rsfb.(*WebSphereLibertyTraceResourceSharingFactory) + rsf.SetLeaderTrackers(func(assetsFolder *string) ([]*unstructured.UnstructuredList, []string, error) { + return r.WebSphereLibertyTraceLeaderTrackerGenerator(instance, treeMap, replaceMap, latestOperandVersion, leaderTrackerType, assetsFolder) + }) + rsf.SetLeaderTrackerName(func(obj map[string]interface{}) (string, error) { + nameString, _, err := unstructured.NestedString(obj, "spec", "podName") // the Trace CR will use .spec.podName as the leaderTracker key identifier + return nameString, err + }) + rsf.SetResources(func() (leader.LeaderTrackerMetadataList, error) { + return r.WebSphereLibertyTraceSharedResourceGenerator(instance, treeMap, latestOperandVersion, leaderTrackerType) + }) + return rsf +} + +func (r *ReconcileWebSphereLibertyTrace) reconcileResourceTrackingState(instance *wlv1.WebSphereLibertyTrace, leaderTrackerType string) (tree.ResourceSharingFactory, leader.LeaderTrackerMetadataList, error) { + treeMap, replaceMap, err := tree.ParseDecisionTree(leaderTrackerType, nil) + if err != nil { + return nil, nil, err + } + + latestOperandVersion, err := tree.GetLatestOperandVersion(treeMap, "") + if err != nil { + return nil, nil, err + } + + rsf := r.createResourceSharingFactory(instance, treeMap, replaceMap, latestOperandVersion, leaderTrackerType) + trackerMetadataList, err := tree.ReconcileResourceTrackingState(instance.GetNamespace(), OperatorName, OperatorShortName, leaderTrackerType, rsf, treeMap, replaceMap, latestOperandVersion) + return rsf, trackerMetadataList, err +} + +func (r *ReconcileWebSphereLibertyTrace) WebSphereLibertyTraceSharedResourceGenerator(instance *wlv1.WebSphereLibertyTrace, treeMap map[string]interface{}, latestOperandVersion, leaderTrackerType string) (leader.LeaderTrackerMetadataList, error) { + // return the metadata specific to the operator version, instance configuration, and shared resource being reconciled + if leaderTrackerType == TRACE_RESOURCE_SHARING_FILE_NAME { + traceMetadataList, err := r.reconcileTraceMetadata(instance, treeMap, latestOperandVersion, nil) + if err != nil { + return nil, err + } + return traceMetadataList, nil + } + return nil, fmt.Errorf("a leaderTrackerType was not provided when running reconcileResourceTrackingState") +} + +func (r *ReconcileWebSphereLibertyTrace) WebSphereLibertyTraceLeaderTrackerGenerator(instance *wlv1.WebSphereLibertyTrace, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, leaderTrackerType string, assetsFolder *string) ([]*unstructured.UnstructuredList, []string, error) { + var resourcesMatrix []*unstructured.UnstructuredList + var resourcesRootNameList []string + + if leaderTrackerType == TRACE_RESOURCE_SHARING_FILE_NAME { + resourcesList, resourceRootName, traceErr := r.GetTraceResources(instance, treeMap, replaceMap, latestOperandVersion, assetsFolder, TRACE_RESOURCE_SHARING_FILE_NAME) + if traceErr != nil { + return nil, nil, traceErr + } + resourcesMatrix = append(resourcesMatrix, resourcesList) + resourcesRootNameList = append(resourcesRootNameList, resourceRootName) + } else { + return nil, nil, fmt.Errorf("a valid leaderTrackerType was not specified for createNewLeaderTrackerList") + } + return resourcesMatrix, resourcesRootNameList, nil +} + +// Search the instance's namespace for existing Trace CRs +func (r *ReconcileWebSphereLibertyTrace) GetTraceResources(instance *wlv1.WebSphereLibertyTrace, treeMap map[string]interface{}, replaceMap map[string]map[string]string, latestOperandVersion string, assetsFolder *string, fileName string) (*unstructured.UnstructuredList, string, error) { + traceResourceList, _, err := leader.CreateUnstructuredResourceListFromSignature(fileName, assetsFolder) + if err != nil { + return nil, "", err + } + if err := r.GetClient().List(context.TODO(), traceResourceList, client.InNamespace(instance.GetNamespace())); err != nil { + return nil, "", err + } + + // If the Trace CR is not annotated with a resource tracking label, patch the CR instance with a leader tracking label to work on the current resource tracking impl. + for i := range len(traceResourceList.Items) { + labelsMap, _, _ := unstructured.NestedMap(traceResourceList.Items[i].Object, "metadata", "labels") + if labelsMap != nil { + if _, found := labelsMap[leader.GetResourcePathIndexLabel(lutils.LibertyURI)]; found { + continue // skip if resource tracking label exists + } + } + // otherwise, create the resource tracking label + defaultUpdatedPathIndex := "" + // path is hardcoded to start replaceMap translation at "v1_4_2.name.*" + if path, err := tree.ReplacePath("v1_4_2.name.*", latestOperandVersion, treeMap, replaceMap); err == nil { + defaultUpdatedPathIndex = strings.Split(path, ".")[0] + "." + strconv.FormatInt(int64(tree.GetLeafIndex(treeMap, path)), 10) + } + if defaultUpdatedPathIndex != "" { + if err := r.CreateOrUpdate(&traceResourceList.Items[i], nil, func() error { + // add the ResourcePathIndexLabel + labelsMap, _, _ := unstructured.NestedMap(traceResourceList.Items[i].Object, "metadata", "labels") + if labelsMap == nil { + labelsMap = make(map[string]interface{}) + } + labelsMap[leader.GetResourcePathIndexLabel(lutils.LibertyURI)] = defaultUpdatedPathIndex + if err := unstructured.SetNestedMap(traceResourceList.Items[i].Object, labelsMap, "metadata", "labels"); err != nil { + return err + } + return nil + }); err != nil { + return traceResourceList, "", err + } + } + } + return traceResourceList, "", nil +} diff --git a/internal/deploy/kubectl/websphereliberty-app-operator.yaml b/internal/deploy/kubectl/websphereliberty-app-operator.yaml index 32d3b3d4..c243b64d 100644 --- a/internal/deploy/kubectl/websphereliberty-app-operator.yaml +++ b/internal/deploy/kubectl/websphereliberty-app-operator.yaml @@ -125,6 +125,18 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - image.openshift.io resources: diff --git a/internal/deploy/kubectl/websphereliberty-app-rbac-watch-all.yaml b/internal/deploy/kubectl/websphereliberty-app-rbac-watch-all.yaml index c450836c..2ccd9d22 100644 --- a/internal/deploy/kubectl/websphereliberty-app-rbac-watch-all.yaml +++ b/internal/deploy/kubectl/websphereliberty-app-rbac-watch-all.yaml @@ -117,6 +117,18 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - image.openshift.io resources: diff --git a/internal/deploy/kubectl/websphereliberty-app-rbac-watch-another.yaml b/internal/deploy/kubectl/websphereliberty-app-rbac-watch-another.yaml index c76a7917..c99694cd 100644 --- a/internal/deploy/kubectl/websphereliberty-app-rbac-watch-another.yaml +++ b/internal/deploy/kubectl/websphereliberty-app-rbac-watch-another.yaml @@ -119,6 +119,18 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - image.openshift.io resources: diff --git a/internal/deploy/kustomize/daily/base/websphere-liberty-roles.yaml b/internal/deploy/kustomize/daily/base/websphere-liberty-roles.yaml index e2569f79..07de4397 100644 --- a/internal/deploy/kustomize/daily/base/websphere-liberty-roles.yaml +++ b/internal/deploy/kustomize/daily/base/websphere-liberty-roles.yaml @@ -128,6 +128,18 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - image.openshift.io resources: diff --git a/internal/deploy/kustomize/daily/overlays/watch-all-namespaces/cluster-roles.yaml b/internal/deploy/kustomize/daily/overlays/watch-all-namespaces/cluster-roles.yaml index 980cd5a4..c2d48e65 100644 --- a/internal/deploy/kustomize/daily/overlays/watch-all-namespaces/cluster-roles.yaml +++ b/internal/deploy/kustomize/daily/overlays/watch-all-namespaces/cluster-roles.yaml @@ -117,6 +117,18 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - image.openshift.io resources: diff --git a/internal/deploy/kustomize/daily/overlays/watch-another-namespace/wlo-watched-ns/watched-roles.yaml b/internal/deploy/kustomize/daily/overlays/watch-another-namespace/wlo-watched-ns/watched-roles.yaml index 9a76fe25..308e2a1a 100644 --- a/internal/deploy/kustomize/daily/overlays/watch-another-namespace/wlo-watched-ns/watched-roles.yaml +++ b/internal/deploy/kustomize/daily/overlays/watch-another-namespace/wlo-watched-ns/watched-roles.yaml @@ -119,6 +119,18 @@ rules: - patch - update - watch +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - list + - patch + - update + - watch - apiGroups: - image.openshift.io resources: diff --git a/utils/leader_tracker.go b/utils/leader_tracker.go deleted file mode 100644 index e7278629..00000000 --- a/utils/leader_tracker.go +++ /dev/null @@ -1,358 +0,0 @@ -/* - Copyright contributors to the WASdev project. - - 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. -*/ - -package utils - -import ( - "context" - "fmt" - "os" - "strconv" - "strings" - "sync" - "time" - - wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" - "gopkg.in/yaml.v2" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -var LeaderTrackerMutexes *sync.Map - -func init() { - LeaderTrackerMutexes = &sync.Map{} -} - -// Leader tracking constants -const ResourcesKey = "names" -const ResourceOwnersKey = "owners" -const ResourcePathsKey = "paths" -const ResourcePathIndicesKey = "pathIndices" - -// const ResourceSubleasesKey = "subleases" - -const LibertyURI = "webspherelibertyapplications.liberty.websphere.ibm.com" -const LeaderVersionLabel = LibertyURI + "/leader-version" -const ResourcePathIndexLabel = LibertyURI + "/resource-path-index" - -const ResourceSuffixLength = 5 - -func GetLastRotationLabelKey(sharedResourceName string) string { - return LibertyURI + "/" + sharedResourceName + "-last-rotation" -} - -func GetTrackedResourceName(sharedResourceName string) string { - return kebabToCamelCase(sharedResourceName) + "TrackedResourceName" -} - -type LeaderTracker struct { - Name string - Owner string - PathIndex string - Path string - Sublease string -} - -type LeaderTrackerMetadata interface { - GetKind() string - GetAPIVersion() string - GetName() string - GetPath() string - GetPathIndex() string -} - -type LeaderTrackerMetadataList interface { - GetItems() []LeaderTrackerMetadata -} - -func RemoveLeaderTracker(leaderTracker *[]LeaderTracker, i int) bool { - if leaderTracker == nil { - return false - } - if i >= len(*leaderTracker) { - return false - } - *leaderTracker = append((*leaderTracker)[:i], (*leaderTracker)[i+1:]...) - return true -} - -func (tracker *LeaderTracker) RenewSublease() bool { - if tracker == nil { - return false - } - tracker.Sublease = fmt.Sprint(time.Now().Unix()) - return true -} - -func (tracker *LeaderTracker) SetOwner(instance string) bool { - if tracker == nil { - return false - } - tracker.Owner = instance - return true - // return tracker.RenewSublease() -} - -// Clears the LeaderTracker owner if it matches instance, returning true if the LeaderTracker has changed and false otherwise -func (tracker *LeaderTracker) ClearOwnerIfMatching(instance string) bool { - if tracker == nil { - return false - } - if tracker.Owner == instance { - tracker.Owner = "" - return true - } - return false -} - -func (tracker *LeaderTracker) ClearOwnerIfMatchingAndSharesLastPathParent(instance string, path string) bool { - if tracker == nil || !strings.Contains(path, ".") || !strings.Contains(tracker.Path, ".") { - return false - } - pathArr := strings.Split(path, ".") - trackerPathArr := strings.Split(tracker.Path, ".") - - if tracker.Owner == instance && strings.Join(pathArr[:len(pathArr)-1], ".") == strings.Join(trackerPathArr[:len(trackerPathArr)-1], ".") { - tracker.Owner = "" - return true - } - return false -} - -// Removes the Owner and Sublease attribute from LeaderTracker to indicate the resource is no longer being tracked -func (tracker *LeaderTracker) EvictOwner() bool { - if tracker == nil { - return false - } - tracker.Owner = "" - // tracker.Sublease = "" - return true -} - -func (tracker *LeaderTracker) EvictOwnerIfSubleaseHasExpired() bool { - if tracker == nil { - return false - } - // Evict if the sublease could not be parsed - then, err := strconv.ParseInt(tracker.Sublease, 10, 64) - if err != nil { - return tracker.EvictOwner() - } - // Evict if the sublease has surpassed the renew time - now := time.Now().Unix() - if now-then > 20 { - return tracker.EvictOwner() - } - return false -} - -func InsertIntoSortedLeaderTrackers(leaderTrackers *[]LeaderTracker, newLeader *LeaderTracker) { - insertIndex := -1 - for i, leader := range *leaderTrackers { - if strings.Compare(newLeader.Name, leader.Name) < 0 { - insertIndex = i - } - } - if insertIndex == -1 { - *leaderTrackers = append(*leaderTrackers, *newLeader) - } else { - *leaderTrackers = append(*leaderTrackers, LeaderTracker{}) - copy((*leaderTrackers)[insertIndex+1:], (*leaderTrackers)[insertIndex:]) - (*leaderTrackers)[insertIndex] = *newLeader - } -} - -func CustomizeLeaderTracker(leaderTracker *corev1.Secret, trackerList *[]LeaderTracker) { - if trackerList == nil { - leaderTracker.Data = make(map[string][]byte) - leaderTracker.Data[ResourceOwnersKey] = []byte("") - leaderTracker.Data[ResourcesKey] = []byte("") - leaderTracker.Data[ResourcePathIndicesKey] = []byte("") - leaderTracker.Data[ResourcePathsKey] = []byte("") - // leaderTracker.Data[ResourceSubleasesKey] = []byte("") - return - } - leaderTracker.Data = make(map[string][]byte) - // owners, names, pathIndices, paths, subleases := "", "", "", "", "" - owners, names, pathIndices, paths := "", "", "", "" - n := len(*trackerList) - for i, tracker := range *trackerList { - owners += tracker.Owner - names += tracker.Name - pathIndices += tracker.PathIndex - paths += tracker.Path - // subleases += tracker.Sublease - if i < n-1 { - owners += "," - names += "," - pathIndices += "," - paths += "," - // subleases += "," - } - } - leaderTracker.Data[ResourceOwnersKey] = []byte(owners) - leaderTracker.Data[ResourcesKey] = []byte(names) - leaderTracker.Data[ResourcePathIndicesKey] = []byte(pathIndices) - leaderTracker.Data[ResourcePathsKey] = []byte(paths) - // leaderTracker.Data[ResourceSubleasesKey] = []byte(subleases) -} - -func GetLeaderTracker(instance *wlv1.WebSphereLibertyApplication, operatorShortName string, leaderTrackerType string, client client.Client) (*corev1.Secret, *[]LeaderTracker, error) { - leaderMutex, mutexFound := LeaderTrackerMutexes.Load(leaderTrackerType) - if !mutexFound { - return nil, nil, fmt.Errorf("Could not retrieve %s leader tracker's mutex when attempting to get. Exiting.", leaderTrackerType) - } - leaderMutex.(*sync.Mutex).Lock() - defer leaderMutex.(*sync.Mutex).Unlock() - - leaderTracker := &corev1.Secret{} - leaderTracker.Name = operatorShortName + "-managed-leader-tracking-" + leaderTrackerType - leaderTracker.Namespace = instance.GetNamespace() - leaderTracker.Labels = GetRequiredLabels(leaderTracker.Name, "") - if err := client.Get(context.TODO(), types.NamespacedName{Name: leaderTracker.Name, Namespace: leaderTracker.Namespace}, leaderTracker); err != nil { - // return a default leaderTracker - return leaderTracker, nil, err - } - // Create the LeaderTracker array - leaderTrackers := make([]LeaderTracker, 0) - owners, ownersFound := leaderTracker.Data[ResourceOwnersKey] - names, namesFound := leaderTracker.Data[ResourcesKey] - pathIndices, pathIndicesFound := leaderTracker.Data[ResourcePathIndicesKey] - paths, pathsFound := leaderTracker.Data[ResourcePathsKey] - // subleases, subleasesFound := leaderTracker.Data[ResourceSubleasesKey] - // If flags are out of sync, delete the leader tracker - if ownersFound != namesFound || pathIndicesFound != pathsFound || namesFound != pathIndicesFound { // || pathIndicesFound != subleasesFound { - if err := client.Delete(context.TODO(), leaderTracker); err != nil { - return nil, nil, err - } - return nil, nil, fmt.Errorf("the resource tracker is out of sync and has been deleted") - } - if len(owners) == 0 && len(names) == 0 && len(pathIndices) == 0 && len(paths) == 0 { // && len(subleases) == 0 { - return leaderTracker, &leaderTrackers, nil - } - ownersList := GetCommaSeparatedArray(string(owners)) - namesList := GetCommaSeparatedArray(string(names)) - pathIndicesList := GetCommaSeparatedArray(string(pathIndices)) - pathsList := GetCommaSeparatedArray(string(paths)) - // subleasesList := GetCommaSeparatedArray(string(subleases)) - numOwners := len(ownersList) - numNames := len(namesList) - numPathIndices := len(pathIndicesList) - numPaths := len(pathsList) - // numSubleases := len(subleasesList) - // check for array length equivalence - if numOwners != numNames || numNames != numPathIndices || numPathIndices != numPaths { // || numPaths != numSubleases { - if err := client.Delete(context.TODO(), leaderTracker); err != nil { - return nil, nil, err - } - return nil, nil, fmt.Errorf("the resource tracker does not have array length equivalence and has been deleted") - } - // populate the leader trackers array - for i := range ownersList { - leaderTrackers = append(leaderTrackers, LeaderTracker{ - Owner: string(ownersList[i]), - Name: string(namesList[i]), - PathIndex: string(pathIndicesList[i]), - Path: string(pathsList[i]), - // Sublease: string(subleasesList[i]), - }) - } - return leaderTracker, &leaderTrackers, nil -} - -func getUnstructuredResourceSignature(leaderTrackerType string, assetsPath *string) (map[string]interface{}, error) { - var folderPath string - if assetsPath != nil { - folderPath = *assetsPath - } else { - folderPath = "internal/controller/assets" - } - signature, err := os.ReadFile(folderPath + "/" + leaderTrackerType + "-signature.yaml") - if err != nil { - return nil, err - } - resourceSignatureYAML := make(map[string]interface{}) - err = yaml.Unmarshal(signature, resourceSignatureYAML) - if err != nil { - return nil, err - } - return resourceSignatureYAML, nil -} - -func CreateUnstructuredResourceFromSignature(leaderTrackerType string, assetsFolder *string, args ...string) (*unstructured.Unstructured, string, error) { - resourceSignatureYAML, err := getUnstructuredResourceSignature(leaderTrackerType, assetsFolder) - if err != nil { - return nil, "", err - } - apiVersion, apiVersionFound := resourceSignatureYAML["apiVersion"] - kind, kindFound := resourceSignatureYAML["kind"] - name, nameFound := resourceSignatureYAML["name"] - // rootName, rootNameFound := resourceSignatureYAML["rootName"] - if !apiVersionFound || !kindFound || !nameFound { - return nil, "", fmt.Errorf("the operator bundled the shared resource '%s' with an invalid signature", leaderTrackerType) - } - sharedResource := &unstructured.Unstructured{} - sharedResource.SetKind(kind.(string)) - sharedResource.SetAPIVersion(apiVersion.(string)) - sharedResourceName, err := parseUnstructuredResourceName(leaderTrackerType, name.(string), args...) - if err != nil { - return nil, "", err - } - return sharedResource, sharedResourceName, nil -} - -func CreateUnstructuredResourceListFromSignature(leaderTrackerType string, assetsFolder *string, args ...string) (*unstructured.UnstructuredList, string, error) { - resourceSignatureYAML, err := getUnstructuredResourceSignature(leaderTrackerType, assetsFolder) - if err != nil { - return nil, "", err - } - apiVersion, apiVersionFound := resourceSignatureYAML["apiVersion"] - kind, kindFound := resourceSignatureYAML["kind"] - rootName := resourceSignatureYAML["rootName"] - if !apiVersionFound || !kindFound { - return nil, "", fmt.Errorf("the operator bundled the shared resource '%s' with an invalid signature", leaderTrackerType) - } - sharedResourceList := &unstructured.UnstructuredList{} - sharedResourceList.SetKind(kind.(string)) - sharedResourceList.SetAPIVersion(apiVersion.(string)) - - rootName, rootNameFound := resourceSignatureYAML["rootName"] - sharedResourceRootName := "" - if rootNameFound { - unstructuredResourceRootName, err := parseUnstructuredResourceName(leaderTrackerType, rootName.(string), args[0]) - if err != nil { - return nil, "", err - } - sharedResourceRootName = unstructuredResourceRootName - } - return sharedResourceList, sharedResourceRootName, nil -} - -// Returns the name of the unstructured resource from the leaderTrackerType signature by parsing and replacing string arguments in the args list -func parseUnstructuredResourceName(leaderTrackerType string, nameStr string, args ...string) (string, error) { - for i, replacementString := range args { - replaceToken := fmt.Sprintf("{%d}", i) - if strings.Contains(nameStr, replaceToken) { - nameStr = strings.ReplaceAll(nameStr, replaceToken, replacementString) - } else { - return "", fmt.Errorf("the operator bundled the shared resource '%s' with an invalid signature; parseUnstructuredResourceName len(args) does not match the number of replacement tokens in the provided signature", leaderTrackerType) - } - } - return nameStr, nil -} diff --git a/utils/leader_tracker_test.go b/utils/leader_tracker_test.go deleted file mode 100644 index 0fac1bf1..00000000 --- a/utils/leader_tracker_test.go +++ /dev/null @@ -1,725 +0,0 @@ -/* - Copyright contributors to the WASdev project. - - 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. -*/ - -package utils - -import ( - "context" - "fmt" - "os" - "strings" - "sync" - "testing" - - wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" - corev1 "k8s.io/api/core/v1" - kerrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/kubernetes/scheme" - fakeclient "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -func TestCustomizeLeaderTrackerNil(t *testing.T) { - leaderTracker := &corev1.Secret{} - leaderTracker.Name = "leader-tracker-test" - - CustomizeLeaderTracker(leaderTracker, nil) - - expectedLeaderTrackerData := make(map[string][]byte) - expectedLeaderTrackerData[ResourceOwnersKey] = []byte("") - expectedLeaderTrackerData[ResourcesKey] = []byte("") - expectedLeaderTrackerData[ResourcePathIndicesKey] = []byte("") - expectedLeaderTrackerData[ResourcePathsKey] = []byte("") - - tests := []Test{ - {"nil leader tracker list", expectedLeaderTrackerData, leaderTracker.Data}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCustomizeLeaderTrackerEmpty(t *testing.T) { - leaderTracker := &corev1.Secret{} - leaderTracker.Name = "leader-tracker-test" - - trackerList := make([]LeaderTracker, 0) - CustomizeLeaderTracker(leaderTracker, &trackerList) - - expectedLeaderTrackerData := make(map[string][]byte) - expectedLeaderTrackerData[ResourcesKey] = []byte("") - expectedLeaderTrackerData[ResourceOwnersKey] = []byte("") - expectedLeaderTrackerData[ResourcePathIndicesKey] = []byte("") - expectedLeaderTrackerData[ResourcePathsKey] = []byte("") - - tests := []Test{ - {"empty leader tracker list", expectedLeaderTrackerData, leaderTracker.Data}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCustomizeLeaderTrackerSingle(t *testing.T) { - leaderTracker := &corev1.Secret{} - leaderTracker.Name = "leader-tracker-test" - - ref1LeaderTracker := createMock1LeaderTracker() - trackerList := make([]LeaderTracker, 0) - trackerList = append(trackerList, createMock1LeaderTracker()) - - CustomizeLeaderTracker(leaderTracker, &trackerList) - - expectedLeaderTrackerData := make(map[string][]byte) - expectedLeaderTrackerData[ResourcesKey] = []byte(ref1LeaderTracker.Name) - expectedLeaderTrackerData[ResourceOwnersKey] = []byte(ref1LeaderTracker.Owner) - expectedLeaderTrackerData[ResourcePathIndicesKey] = []byte(ref1LeaderTracker.PathIndex) - expectedLeaderTrackerData[ResourcePathsKey] = []byte(ref1LeaderTracker.Path) - - tests := []Test{ - {"single entry leader tracker", expectedLeaderTrackerData, leaderTracker.Data}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCustomizeLeaderTrackerMultiple(t *testing.T) { - leaderTracker := &corev1.Secret{} - leaderTracker.Name = "leader-tracker-test" - - ref1LeaderTracker, ref2LeaderTracker := createMock1LeaderTracker(), createMock2LeaderTracker() - trackerList := make([]LeaderTracker, 0) - trackerList = append(trackerList, createMock1LeaderTracker()) - trackerList = append(trackerList, createMock2LeaderTracker()) - - CustomizeLeaderTracker(leaderTracker, &trackerList) - - expectedLeaderTrackerData := make(map[string][]byte) - expectedLeaderTrackerData[ResourcesKey] = []byte(fmt.Sprintf("%s,%s", ref1LeaderTracker.Name, ref2LeaderTracker.Name)) - expectedLeaderTrackerData[ResourceOwnersKey] = []byte(fmt.Sprintf("%s,%s", ref1LeaderTracker.Owner, ref2LeaderTracker.Owner)) - expectedLeaderTrackerData[ResourcePathIndicesKey] = []byte(fmt.Sprintf("%s,%s", ref1LeaderTracker.PathIndex, ref2LeaderTracker.PathIndex)) - expectedLeaderTrackerData[ResourcePathsKey] = []byte(fmt.Sprintf("%s,%s", ref1LeaderTracker.Path, ref2LeaderTracker.Path)) - - tests := []Test{ - {"multiple entry leader tracker", expectedLeaderTrackerData, leaderTracker.Data}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - trackerList = trackerList[1:] - CustomizeLeaderTracker(leaderTracker, &trackerList) - expectedLeaderTrackerData[ResourcesKey] = []byte(ref2LeaderTracker.Name) - expectedLeaderTrackerData[ResourceOwnersKey] = []byte(ref2LeaderTracker.Owner) - expectedLeaderTrackerData[ResourcePathIndicesKey] = []byte(ref2LeaderTracker.PathIndex) - expectedLeaderTrackerData[ResourcePathsKey] = []byte(ref2LeaderTracker.Path) - - tests = []Test{ - {"remove entry leader tracker", expectedLeaderTrackerData, leaderTracker.Data}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -// func TestEvictOwnerIfSubleaseHasExpired(t *testing.T) { -// leaderTracker, referenceLeaderTracker := createMock1LeaderTracker(), createMock1LeaderTracker() -// changeDetected := leaderTracker.EvictOwnerIfSubleaseHasExpired() - -// tests := []Test{ -// {"evict owner if sublease has expired - change detected", true, changeDetected}, -// {"evict owner if sublease has expired - name unchanged", referenceLeaderTracker.Name, leaderTracker.Name}, -// {"evict owner if sublease has expired - owner evicted", "", leaderTracker.Owner}, -// {"evict owner if sublease has expired - path unchanged", referenceLeaderTracker.Path, leaderTracker.Path}, -// {"evict owner if sublease has expired - path index unchanged", referenceLeaderTracker.PathIndex, leaderTracker.PathIndex}, -// {"evict owner if sublease has expired - sublease removed", "", leaderTracker.Sublease}, -// } -// if err := verifyTests(tests); err != nil { -// t.Fatalf("%v", err) -// } -// } - -// func TestEvictOwnerIfSubleaseHasExpiredWithValidSublease(t *testing.T) { -// leaderTracker, referenceLeaderTracker := createMock1LeaderTracker(), createMock1LeaderTracker() -// leaderTracker.SetOwner("sublease-test") -// sublease, subleaseErr := strconv.ParseInt(leaderTracker.Sublease, 10, 64) -// now := time.Now().Unix() -// timeDiff := sublease - now -// changeDetected := leaderTracker.EvictOwnerIfSubleaseHasExpired() -// nextSublease, nextSubleaseErr := strconv.ParseInt(leaderTracker.Sublease, 10, 64) - -// tests := []Test{ -// {"evict owner if sublease has expired with valid sublease - sublease can convert to int64", nil, subleaseErr}, -// {"evict owner if sublease has expired with valid sublease - sublease set in less than 20 seconds", true, timeDiff < 20}, -// {"evict owner if sublease has expired with valid sublease - next sublease can convert to int64", nil, nextSubleaseErr}, -// {"evict owner if sublease has expired with valid sublease - next sublease matches original sublease", sublease, nextSublease}, -// {"evict owner if sublease has expired with valid sublease - no change detected", false, changeDetected}, -// {"evict owner if sublease has expired with valid sublease - name unchanged", referenceLeaderTracker.Name, leaderTracker.Name}, -// {"evict owner if sublease has expired with valid sublease - owner set", "sublease-test", leaderTracker.Owner}, -// {"evict owner if sublease has expired with valid sublease - path unchanged", referenceLeaderTracker.Path, leaderTracker.Path}, -// {"evict owner if sublease has expired with valid sublease - path index unchanged", referenceLeaderTracker.PathIndex, leaderTracker.PathIndex}, -// } -// if err := verifyTests(tests); err != nil { -// t.Fatalf("%v", err) -// } -// } - -// func TestEvictOwnerIfSubleaseHasExpiredWithInvalidSublease(t *testing.T) { -// leaderTracker, referenceLeaderTracker := createMock1LeaderTracker(), createMock1LeaderTracker() -// leaderTracker.Sublease = "abc123" // when specifying an invalid sublease duration that can not be parsed into type int, the operator evicts the owner -// changeDetected := leaderTracker.EvictOwnerIfSubleaseHasExpired() - -// tests := []Test{ -// {"evict owner if sublease has expired with invalid sublease - change detected", true, changeDetected}, -// {"evict owner if sublease has expired with invalid sublease - name unchanged", referenceLeaderTracker.Name, leaderTracker.Name}, -// {"evict owner if sublease has expired with invalid sublease - owner evicted", "", leaderTracker.Owner}, -// {"evict owner if sublease has expired with invalid sublease - path unchanged", referenceLeaderTracker.Path, leaderTracker.Path}, -// {"evict owner if sublease has expired with invalid sublease - path index unchanged", referenceLeaderTracker.PathIndex, leaderTracker.PathIndex}, -// {"evict owner if sublease has expired with invalid sublease - sublease removed", "", leaderTracker.Sublease}, -// } -// if err := verifyTests(tests); err != nil { -// t.Fatalf("%v", err) -// } -// } - -func TestClearOwnerIfMatching(t *testing.T) { - leaderTracker, referenceLeaderTracker := createMock1LeaderTracker(), createMock1LeaderTracker() - leaderTracker.Owner = "test-3" - changeDetected := leaderTracker.ClearOwnerIfMatching(referenceLeaderTracker.Owner) - - tests := []Test{ - {"clear owner if matching - no change detected", false, changeDetected}, - {"clear owner if matching - name unchanged", referenceLeaderTracker.Name, leaderTracker.Name}, - {"clear owner if matching - owner unchanged", "test-3", leaderTracker.Owner}, - {"clear owner if matching - path unchanged", referenceLeaderTracker.Path, leaderTracker.Path}, - {"clear owner if matching - path index unchanged", referenceLeaderTracker.PathIndex, leaderTracker.PathIndex}, - {"clear owner if matching - sublease unchanged", referenceLeaderTracker.Sublease, leaderTracker.Sublease}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - referenceLeaderTracker.Owner = leaderTracker.Owner - changeDetected = leaderTracker.ClearOwnerIfMatching(referenceLeaderTracker.Owner) - - tests = []Test{ - {"clear owner if matching - change detected", true, changeDetected}, - {"clear owner if matching - name unchanged", referenceLeaderTracker.Name, leaderTracker.Name}, - {"clear owner if matching - owner removed", "", leaderTracker.Owner}, - {"clear owner if matching - path unchanged", referenceLeaderTracker.Path, leaderTracker.Path}, - {"clear owner if matching - path index unchanged", referenceLeaderTracker.PathIndex, leaderTracker.PathIndex}, - {"clear owner if matching - sublease unchanged", referenceLeaderTracker.Sublease, leaderTracker.Sublease}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestSetOwner(t *testing.T) { - leaderTracker, referenceLeaderTracker := createMock1LeaderTracker(), createMock1LeaderTracker() - changeDetected := leaderTracker.SetOwner("new-owner-name") - // currTime, currErr := strconv.ParseInt(leaderTracker.Sublease, 10, 64) - tests := []Test{ - {"set owner - change detected", true, changeDetected}, - {"set owner - name unchanged", referenceLeaderTracker.Name, leaderTracker.Name}, - {"set owner - owner changed", "new-owner-name", leaderTracker.Owner}, - {"set owner - path unchanged", referenceLeaderTracker.Path, leaderTracker.Path}, - {"set owner - path index unchanged", referenceLeaderTracker.PathIndex, leaderTracker.PathIndex}, - // {"set owner - sublease converts to int64 without error", nil, currErr}, - // {"set owner - new sublease time greater or equal to current time", true, currTime >= time.Now().Unix()}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestRemoveLeaderTracker(t *testing.T) { - leaderTracker1 := createMock1LeaderTracker() - leaderTracker2, ref2LeaderTracker := createMock2LeaderTracker(), createMock2LeaderTracker() - leaderTrackerList := []LeaderTracker{ - leaderTracker1, - leaderTracker2, - leaderTracker1, - } - changeDetected := RemoveLeaderTracker(&leaderTrackerList, 3) - - tests := []Test{ - {"remove leader tracker, out of bounds - no change detected", false, changeDetected}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - changeDetected = RemoveLeaderTracker(&leaderTrackerList, 2) - - tests = []Test{ - {"remove leader tracker, remove last element - change detected", true, changeDetected}, - {"remove leader tracker, remove last element - length check", 2, len(leaderTrackerList)}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - changeDetected = RemoveLeaderTracker(&leaderTrackerList, 0) - - tests = []Test{ - {"remove leader tracker, remove first element - change detected", true, changeDetected}, - {"remove leader tracker, remove first element - length check", 1, len(leaderTrackerList)}, - {"remove leader tracker, remove first element", ref2LeaderTracker.Name, leaderTrackerList[0].Name}, - {"remove leader tracker, remove first element", ref2LeaderTracker.Owner, leaderTrackerList[0].Owner}, - {"remove leader tracker, remove first element", ref2LeaderTracker.Path, leaderTrackerList[0].Path}, - {"remove leader tracker, remove first element", ref2LeaderTracker.PathIndex, leaderTrackerList[0].PathIndex}, - {"remove leader tracker, remove first element", ref2LeaderTracker.Sublease, leaderTrackerList[0].Sublease}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - changeDetected = RemoveLeaderTracker(&leaderTrackerList, 0) - - tests = []Test{ - {"remove leader tracker, remove first element - change detected", true, changeDetected}, - {"remove leader tracker, remove first element - length check", 0, len(leaderTrackerList)}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - changeDetected = RemoveLeaderTracker(&leaderTrackerList, 0) - - tests = []Test{ - {"remove leader tracker, remove first element - no change detected", false, changeDetected}, - {"remove leader tracker, remove first element - length check", 0, len(leaderTrackerList)}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - changeDetected = RemoveLeaderTracker(nil, 0) - - tests = []Test{ - {"remove leader tracker, nil array - no change detected", false, changeDetected}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestGetLeaderTrackerWithoutSecret(t *testing.T) { - spec := wlv1.WebSphereLibertyApplicationSpec{} - - // Create Liberty app - instance := createWebSphereLibertyApp(name, namespace, spec) - - // Create client - objs, s := []runtime.Object{instance}, scheme.Scheme - s.AddKnownTypes(wlv1.GroupVersion, instance) - cl := fakeclient.NewFakeClient(objs...) - - var referenceLeaderTrackers *[]LeaderTracker - leaderTrackerSecret, leaderTrackers, err := GetLeaderTracker(instance, "wlo", "ltpa", cl) - tests := []Test{ - {"get leader tracker without secret - secret name matches", "wlo-managed-leader-tracking-ltpa", leaderTrackerSecret.Name}, - {"get leader tracker without secret - leaderTrackers is nil", referenceLeaderTrackers, leaderTrackers}, - {"get leader tracker without secret - resource not found", true, kerrors.IsNotFound(err)}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestGetLeaderTrackerWithEmptySecret(t *testing.T) { - spec := wlv1.WebSphereLibertyApplicationSpec{} - - // Create Liberty app - instance := createWebSphereLibertyApp(name, namespace, spec) - - // Create client - objs, s := []runtime.Object{instance}, scheme.Scheme - s.AddKnownTypes(wlv1.GroupVersion, instance) - cl := fakeclient.NewFakeClient(objs...) - - // Create empty secret - emptySecret := &corev1.Secret{} - emptySecret.Name = "wlo-managed-leader-tracking-ltpa" - emptySecret.Namespace = namespace - createEmptySecretErr := cl.Create(context.TODO(), emptySecret) - - _, leaderTrackers, err := GetLeaderTracker(instance, "wlo", "ltpa", cl) - tests := []Test{ - {"get leader tracker with empty secret - create empty secret without error", nil, createEmptySecretErr}, - {"get leader tracker with empty secret - no error", nil, err}, - {"get leader tracker with empty secret - leaderTrackers empty", 0, len(*leaderTrackers)}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestGetLeaderTrackerWithOneSecretEntry(t *testing.T) { - spec := wlv1.WebSphereLibertyApplicationSpec{} - - // Create Liberty app - instance := createWebSphereLibertyApp(name, namespace, spec) - - // Create client - objs, s := []runtime.Object{instance}, scheme.Scheme - s.AddKnownTypes(wlv1.GroupVersion, instance) - cl := fakeclient.NewFakeClient(objs...) - - // Create one secret entry - oneSecret := &corev1.Secret{} - oneSecret.Name = "wlo-managed-leader-tracking-ltpa" - oneSecret.Namespace = namespace - mockLeaderTracker := createMock1LeaderTracker() - oneSecret.Data = make(map[string][]byte) - oneSecret.Data[ResourcesKey] = []byte(mockLeaderTracker.Name) - oneSecret.Data[ResourceOwnersKey] = []byte(mockLeaderTracker.Owner) - oneSecret.Data[ResourcePathIndicesKey] = []byte(mockLeaderTracker.PathIndex) - oneSecret.Data[ResourcePathsKey] = []byte(mockLeaderTracker.Path) - // oneSecret.Data[ResourceSubleasesKey] = []byte(mockLeaderTracker.Sublease) - createOneSecretErr := cl.Create(context.TODO(), oneSecret) - - _, leaderTrackers, err := GetLeaderTracker(instance, "wlo", "ltpa", cl) - tests := []Test{ - {"get leader tracker with one secret entry - create secret without error", nil, createOneSecretErr}, - {"get leader tracker with one secret entry - no error", nil, err}, - {"get leader tracker with one secret entry - leaderTrackers empty", 1, len(*leaderTrackers)}, - {"get leader tracker with one secret entry - name unchanged", mockLeaderTracker.Name, (*leaderTrackers)[0].Name}, - {"get leader tracker with one secret entry - owner unchanged", mockLeaderTracker.Owner, (*leaderTrackers)[0].Owner}, - {"get leader tracker with one secret entry - path index unchanged", mockLeaderTracker.PathIndex, (*leaderTrackers)[0].PathIndex}, - {"get leader tracker with one secret entry - path unchanged", mockLeaderTracker.Path, (*leaderTrackers)[0].Path}, - // {"get leader tracker with one secret entry - sublease unchanged", mockLeaderTracker.Sublease, (*leaderTrackers)[0].Sublease}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestGetLeaderTrackerWithOneSecretEntryWithMissingKey(t *testing.T) { - spec := wlv1.WebSphereLibertyApplicationSpec{} - - // Create Liberty app - instance := createWebSphereLibertyApp(name, namespace, spec) - - // Create client - objs, s := []runtime.Object{instance}, scheme.Scheme - s.AddKnownTypes(wlv1.GroupVersion, instance) - cl := fakeclient.NewFakeClient(objs...) - - // Create one secret entry - oneSecret := &corev1.Secret{} - oneSecret.Name = "wlo-managed-leader-tracking-ltpa" - oneSecret.Namespace = namespace - mockLeaderTracker := createMock1LeaderTracker() - oneSecret.Data = make(map[string][]byte) - oneSecret.Data[ResourcesKey] = []byte(mockLeaderTracker.Name) - oneSecret.Data[ResourceOwnersKey] = []byte(mockLeaderTracker.Owner) - oneSecret.Data[ResourcePathIndicesKey] = []byte(mockLeaderTracker.PathIndex) - // oneSecret.Data[ResourcePathsKey] = []byte(mockLeaderTracker.Path) // remove path key - createOneSecretErr := cl.Create(context.TODO(), oneSecret) - _, leaderTrackers, err := GetLeaderTracker(instance, "wlo", "ltpa", cl) - - // GetLeaderTracker should delete the secret - checkOneSecret := &corev1.Secret{} - checkOneSecret.Name = "wlo-managed-leader-tracking-ltpa" - checkOneSecret.Namespace = namespace - - var nilLeaderTrackers *[]LeaderTracker - checkOneSecretError := cl.Get(context.TODO(), types.NamespacedName{Name: checkOneSecret.Name, Namespace: checkOneSecret.Namespace}, checkOneSecret) - tests := []Test{ - {"get leader tracker with one secret entry and missing key - create secret without error", nil, createOneSecretErr}, - {"get leader tracker with one secret entry and missing key - errors", false, err == nil}, - {"get leader tracker with one secret entry and missing key - leader tracker invalid", nilLeaderTrackers, leaderTrackers}, - {"get leader tracker with one secret entry and missing key - secret deleted", true, kerrors.IsNotFound(checkOneSecretError)}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestGetLeaderTrackerWithTwoSecretEntries(t *testing.T) { - spec := wlv1.WebSphereLibertyApplicationSpec{} - - // Create Liberty app - instance := createWebSphereLibertyApp(name, namespace, spec) - - // Create client - objs, s := []runtime.Object{instance}, scheme.Scheme - s.AddKnownTypes(wlv1.GroupVersion, instance) - cl := fakeclient.NewFakeClient(objs...) - - // Create two secret entries - twoSecret := &corev1.Secret{} - twoSecret.Name = "wlo-managed-leader-tracking-ltpa" - twoSecret.Namespace = namespace - mock1LeaderTracker, mock2LeaderTracker := createMock1LeaderTracker(), createMock2LeaderTracker() - twoSecret.Data = make(map[string][]byte) - twoSecret.Data[ResourcesKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.Name, mock2LeaderTracker.Name)) - twoSecret.Data[ResourceOwnersKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.Owner, mock2LeaderTracker.Owner)) - twoSecret.Data[ResourcePathIndicesKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.PathIndex, mock2LeaderTracker.PathIndex)) - twoSecret.Data[ResourcePathsKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.Path, mock2LeaderTracker.Path)) - // twoSecret.Data[ResourceSubleasesKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.Sublease, mock2LeaderTracker.Sublease)) - createTwoSecretErr := cl.Create(context.TODO(), twoSecret) - _, leaderTrackers, err := GetLeaderTracker(instance, "wlo", "ltpa", cl) - - tests := []Test{ - {"get leader tracker with two secret entries - create secret without error", nil, createTwoSecretErr}, - {"get leader tracker with two secret entries - errors", nil, err}, - {"get leader tracker with two secret entries - leader tracker length", 2, len(*leaderTrackers)}, - {"get leader tracker with two secret entries - (1) name unchanged", mock1LeaderTracker.Name, (*leaderTrackers)[0].Name}, - {"get leader tracker with two secret entries - (1) owner unchanged", mock1LeaderTracker.Owner, (*leaderTrackers)[0].Owner}, - {"get leader tracker with two secret entries - (1) path index unchanged", mock1LeaderTracker.PathIndex, (*leaderTrackers)[0].PathIndex}, - {"get leader tracker with two secret entries - (1) path unchanged", mock1LeaderTracker.Path, (*leaderTrackers)[0].Path}, - // {"get leader tracker with two secret entries - (1) sublease unchanged", mock1LeaderTracker.Sublease, (*leaderTrackers)[0].Sublease}, - {"get leader tracker with two secret entries - (2) name unchanged", mock2LeaderTracker.Name, (*leaderTrackers)[1].Name}, - {"get leader tracker with two secret entries - (2) owner unchanged", mock2LeaderTracker.Owner, (*leaderTrackers)[1].Owner}, - {"get leader tracker with two secret entries - (2) path index unchanged", mock2LeaderTracker.PathIndex, (*leaderTrackers)[1].PathIndex}, - {"get leader tracker with two secret entries - (2) path unchanged", mock2LeaderTracker.Path, (*leaderTrackers)[1].Path}, - // {"get leader tracker with two secret entries - (2) sublease unchanged", mock2LeaderTracker.Sublease, (*leaderTrackers)[1].Sublease}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestGetLeaderTrackerWithTwoSecretEntriesAndMissingEntry(t *testing.T) { - spec := wlv1.WebSphereLibertyApplicationSpec{} - - // Create Liberty app - instance := createWebSphereLibertyApp(name, namespace, spec) - - // Create client - objs, s := []runtime.Object{instance}, scheme.Scheme - s.AddKnownTypes(wlv1.GroupVersion, instance) - cl := fakeclient.NewFakeClient(objs...) - - // Create two secret entries - twoSecret := &corev1.Secret{} - twoSecret.Name = "wlo-managed-leader-tracking-ltpa" - twoSecret.Namespace = namespace - mock1LeaderTracker, mock2LeaderTracker := createMock1LeaderTracker(), createMock2LeaderTracker() - twoSecret.Data = make(map[string][]byte) - twoSecret.Data[ResourcesKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.Name, mock2LeaderTracker.Name)) - twoSecret.Data[ResourceOwnersKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.Owner, mock2LeaderTracker.Owner)) - twoSecret.Data[ResourcePathIndicesKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.PathIndex, mock2LeaderTracker.PathIndex)) - // twoSecret.Data[ResourcePathsKey] = []byte(fmt.Sprintf("%s,%s", mock1LeaderTracker.Path, mock2LeaderTracker.Path)) // missing mock2LeaderTracker.Sublease - createTwoSecretErr := cl.Create(context.TODO(), twoSecret) - _, leaderTrackers, err := GetLeaderTracker(instance, "wlo", "ltpa", cl) - - // GetLeaderTracker should delete the secret - checkTwoSecret := &corev1.Secret{} - checkTwoSecret.Name = "wlo-managed-leader-tracking-ltpa" - checkTwoSecret.Namespace = namespace - - var nilLeaderTrackers *[]LeaderTracker - checkTwoSecretError := cl.Get(context.TODO(), types.NamespacedName{Name: checkTwoSecret.Name, Namespace: checkTwoSecret.Namespace}, checkTwoSecret) - tests := []Test{ - {"get leader tracker with two secret entries and missing entry - create secret without error", nil, createTwoSecretErr}, - {"get leader tracker with two secret entries and missing entry - error is not nil", false, err == nil}, - {"get leader tracker with two secret entries and missing entry - leader tracker invalid", nilLeaderTrackers, leaderTrackers}, - {"get leader tracker with two secret entries and missing entry - secret does not exist", true, kerrors.IsNotFound(checkTwoSecretError)}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func Test_getUnstructuredResourceSignature(t *testing.T) { - assetsFolder := getAssetsFolder() - unstructuredResource, err := getUnstructuredResourceSignature("ltpa", &assetsFolder) - _, hasKind := unstructuredResource["kind"] - _, hasAPIVersion := unstructuredResource["apiVersion"] - tests := []Test{ - {"get unstructured resource signature - get without error", nil, err}, - {"get unstructured resource signature - unstructured resource has kind", true, hasKind}, - {"get unstructured resource signature - unstructured resource has api version", true, hasAPIVersion}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func Test_getUnstructuredResourceSignatureWithInvalidSignature(t *testing.T) { - testsFolder := getTestsFolder() - _, err := getUnstructuredResourceSignature("invalid-wlo", &testsFolder) - tests := []Test{ - {"get unstructured resource signature, invalid YAML - get with error", false, err == nil}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func Test_getUnstructuredResourceSignatureWithMissingSignature(t *testing.T) { - testsFolder := getTestsFolder() - _, err := getUnstructuredResourceSignature("missing-wlo", &testsFolder) - tests := []Test{ - {"get unstructured resource signature, missing YAML - get with error", false, err == nil}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCreateUnstructuredResourceFromSignature(t *testing.T) { - testsFolder := getTestsFolder() - unstructuredLibertyApp, unstructuredLibertyAppName, err := CreateUnstructuredResourceFromSignature("wlo", &testsFolder, "wlo") - tests := []Test{ - {"create unstructured resource from signature - no error", nil, err}, - {"create unstructured resource from signature - liberty app kind ", "WebSphereLibertyApplication", unstructuredLibertyApp.GetKind()}, - {"create unstructured resource from signature - liberty app API version", "liberty.websphere.ibm.com/v1", unstructuredLibertyApp.GetAPIVersion()}, - {"create unstructured resource from signature - liberty app name", "wlo-managed-wlapp", unstructuredLibertyAppName}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCreateUnstructuredResourceFromSignatureWithInvalidSignature(t *testing.T) { - testsFolder := getTestsFolder() - unstructuredLibertyApp, unstructuredLibertyAppName, err := CreateUnstructuredResourceFromSignature("invalid-wlo", &testsFolder, "wlo") - var nilUnstructuredLibertyApp *unstructured.Unstructured - tests := []Test{ - {"create unstructured resource from signature, invalid YAML - has error", false, err == nil}, - {"create unstructured resource from signature, invalid YAML - liberty app is nil", nilUnstructuredLibertyApp, unstructuredLibertyApp}, - {"create unstructured resource from signature, invalid YAML - liberty app name empty", "", unstructuredLibertyAppName}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - // check when more arguments are provided than replacement tokens in wlo-signature.yaml - unstructuredLibertyApp, unstructuredLibertyAppName, err = CreateUnstructuredResourceFromSignature("wlo", &testsFolder, "wlo", "one", "two", "three", "four") - tests = []Test{ - {"create unstructured resource from signature, invalid name replacement 1 - has error", false, err == nil}, - {"create unstructured resource from signature, invalid name replacement 1 - liberty app is nil", nilUnstructuredLibertyApp, unstructuredLibertyApp}, - {"create unstructured resource from signature, invalid name replacement 1 - liberty app name empty", "", unstructuredLibertyAppName}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } - - // check when less arguments are provided than replacement tokens in wlo-signature.yaml - unstructuredLibertyApp, unstructuredLibertyAppName, err = CreateUnstructuredResourceFromSignature("invalid", &testsFolder) - tests = []Test{ - {"create unstructured resource from signature, invalid name replacement 2 - has error", false, err == nil}, - {"create unstructured resource from signature, invalid name replacement 2 - liberty app is nil", nilUnstructuredLibertyApp, unstructuredLibertyApp}, - {"create unstructured resource from signature, invalid name replacement 2 - liberty app name empty", "", unstructuredLibertyAppName}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCreateUnstructuredResourceListFromSignature(t *testing.T) { - testsFolder := getTestsFolder() - unstructuredLibertyAppList, _, err := CreateUnstructuredResourceListFromSignature("wlo", &testsFolder, "wlo") - tests := []Test{ - {"create unstructured resource list from signature - no error", nil, err}, - {"create unstructured resource list from signature - liberty app list kind ", "WebSphereLibertyApplication", unstructuredLibertyAppList.GetKind()}, - {"create unstructured resource list from signature - liberty app list API version", "liberty.websphere.ibm.com/v1", unstructuredLibertyAppList.GetAPIVersion()}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCreateUnstructuredResourceListFromSignatureWithInvalidSignature(t *testing.T) { - testsFolder := getTestsFolder() - unstructuredLibertyAppList, _, err := CreateUnstructuredResourceListFromSignature("invalid-wlo", &testsFolder, "wlo") - var nilUnstructuredLibertyAppList *unstructured.UnstructuredList - tests := []Test{ - {"create unstructured resource list from signature, invalid YAML - has error", false, err == nil}, - {"create unstructured resource list from signature, invalid YAML - liberty app is nil", nilUnstructuredLibertyAppList, unstructuredLibertyAppList}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCreateUnstructuredResourceFromSignatureWithUserError(t *testing.T) { - testsFolder := getTestsFolder() - unstructuredLibertyApp, unstructuredLibertyAppName, err := CreateUnstructuredResourceFromSignature("user-error-wlo", &testsFolder) - var nilUnstructuredLibertyApp *unstructured.Unstructured - tests := []Test{ - {"create unstructured resource from signature, invalid YAML - has error", false, err == nil}, - {"create unstructured resource from signature, invalid YAML - liberty app is nil", nilUnstructuredLibertyApp, unstructuredLibertyApp}, - {"create unstructured resource from signature, invalid YAML - liberty app name empty", "", unstructuredLibertyAppName}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func TestCreateUnstructuredResourceListFromSignatureWithUserError(t *testing.T) { - testsFolder := getTestsFolder() - unstructuredLibertyAppList, _, err := CreateUnstructuredResourceListFromSignature("user-error-wlo", &testsFolder) - var nilUnstructuredLibertyAppList *unstructured.UnstructuredList - tests := []Test{ - {"create unstructured resource from signature, invalid YAML - has error", false, err == nil}, - {"create unstructured resource from signature, invalid YAML - liberty app is nil", nilUnstructuredLibertyAppList, unstructuredLibertyAppList}, - } - if err := verifyTests(tests); err != nil { - t.Fatalf("%v", err) - } -} - -func getUtilsFolder() string { - cwd, err := os.Getwd() - if err != nil || !strings.HasSuffix(cwd, "/utils") { - return "utils" - } - return cwd -} - -func getAssetsFolder() string { - return getUtilsFolder() + "/../internal/controller/assets" -} - -func getTestsFolder() string { - return getUtilsFolder() + "/../internal/controller/tests" -} - -func createMock1LeaderTracker() LeaderTracker { - return LeaderTracker{ - Name: "-12345", - Owner: "test", - Path: "v1_0_0.hello.world", - PathIndex: "v1_0_0.0", - Sublease: "0", - } -} - -func createMock2LeaderTracker() LeaderTracker { - return LeaderTracker{ - Name: "-67890", - Owner: "test-2", - Path: "v1_0_0.hello.world", - PathIndex: "v1_0_0.1", - Sublease: "0", - } -} - -func TestMain(m *testing.M) { - LeaderTrackerMutexes.Store("ltpa", &sync.Mutex{}) - rc := m.Run() - LeaderTrackerMutexes.Delete("ltpa") - os.Exit(rc) -} diff --git a/utils/utils.go b/utils/utils.go index 8d95e6ae..692233b5 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -28,6 +28,7 @@ import ( "math/rand/v2" + "github.com/OpenLiberty/open-liberty-operator/utils/leader" wlv1 "github.com/WASdev/websphere-liberty-operator/api/v1" rcoutils "github.com/application-stacks/runtime-component-operator/utils" routev1 "github.com/openshift/api/route/v1" @@ -54,6 +55,7 @@ var log = logf.Log.WithName("websphereliberty_utils") const serviceabilityMountPath = "/serviceability" const ssoEnvVarPrefix = "SEC_SSO_" const OperandVersion = "1.4.3" +const LibertyURI = "webspherelibertyapplications.liberty.websphere.ibm.com" // LTPA constants const managedLTPAMountPath = "/config/managedLTPA" @@ -100,84 +102,6 @@ var entitlementCloudPakID = map[wlv1.LicenseEntitlement]string{ wlv1.LicenseEntitlementCP4Apps: "4df52d2cdc374ba09f631a650ad2b5bf", } -type LTPAMetadata struct { - Kind string - APIVersion string - Name string - Path string - PathIndex string -} - -func (m LTPAMetadata) GetName() string { - return m.Name -} -func (m LTPAMetadata) GetPath() string { - return m.Path -} -func (m LTPAMetadata) GetPathIndex() string { - return m.PathIndex -} -func (m LTPAMetadata) GetKind() string { - return m.Kind -} -func (m LTPAMetadata) GetAPIVersion() string { - return m.APIVersion -} - -type LTPAMetadataList struct { - Items []LeaderTrackerMetadata -} - -func (ml LTPAMetadataList) GetItems() []LeaderTrackerMetadata { - return ml.Items -} - -type PasswordEncryptionMetadata struct { - Kind string - APIVersion string - Name string - Path string - PathIndex string -} - -func (m PasswordEncryptionMetadata) GetName() string { - return m.Name -} -func (m PasswordEncryptionMetadata) GetPath() string { - return m.Path -} -func (m PasswordEncryptionMetadata) GetPathIndex() string { - return m.PathIndex -} -func (m PasswordEncryptionMetadata) GetKind() string { - return m.Kind -} -func (m PasswordEncryptionMetadata) GetAPIVersion() string { - return m.APIVersion -} - -type PasswordEncryptionMetadataList struct { - Items []LeaderTrackerMetadata -} - -func (ml PasswordEncryptionMetadataList) GetItems() []LeaderTrackerMetadata { - return ml.Items -} - -type LTPAConfig struct { - Metadata *LTPAMetadata - SecretName string - SecretInstanceName string - ConfigSecretName string - ConfigSecretInstanceName string - ServiceAccountName string - JobRequestConfigMapName string - ConfigMapName string - FileName string - EncryptionKeySecretName string - EncryptionKeySharingEnabled bool // true or false -} - // Validate if the WebSphereLibertyApplication is valid func Validate(wlapp *wlv1.WebSphereLibertyApplication) (bool, error) { // Serviceability validation @@ -289,7 +213,7 @@ func GetSecretLastRotationLabel(la *wlv1.WebSphereLibertyApplication, client cli if err != nil { return nil, errors.Wrapf(err, "Secret %q was not found in namespace %q", secretName, la.GetNamespace()) } - labelKey := GetLastRotationLabelKey(sharedResourceName) + labelKey := leader.GetLastRotationLabelKey(sharedResourceName, LibertyURI) lastRotationLabel, found := secret.Labels[labelKey] if !found { return nil, fmt.Errorf("Secret %q does not have label key %q", secretName, labelKey) @@ -306,7 +230,7 @@ func GetSecretLastRotationAsLabelMap(la *wlv1.WebSphereLibertyApplication, clien return nil, errors.Wrapf(err, "Secret %q was not found in namespace %q", secretName, la.GetNamespace()) } return map[string]string{ - GetLastRotationLabelKey(sharedResourceName): string(secret.Data["lastRotation"]), + leader.GetLastRotationLabelKey(sharedResourceName, LibertyURI): string(secret.Data["lastRotation"]), }, nil } @@ -813,7 +737,7 @@ func isVolumeFound(pts *corev1.PodTemplateSpec, name string) bool { return false } -func ConfigurePasswordEncryption(pts *corev1.PodTemplateSpec, la *wlv1.WebSphereLibertyApplication, operatorShortName string, passwordEncryptionMetadata *PasswordEncryptionMetadata) { +func ConfigurePasswordEncryption(pts *corev1.PodTemplateSpec, la *wlv1.WebSphereLibertyApplication, operatorShortName string, passwordEncryptionMetadata *leader.PasswordEncryptionMetadata) { // Mount a volume /output/liberty-operator/encryptionKey.xml to store the Liberty Password Encryption Key MountSecretAsVolume(pts, operatorShortName+ManagedEncryptionServerXML+passwordEncryptionMetadata.Name, CreateVolumeMount(SecureMountPath, EncryptionKeyXMLFileName))