Repository: FoundationDB/fdb-kubernetes-operator
Pull request #1731 (2023-07-07 08:40:09)

Correct the way how the client knobs are passed down and used
# Description

Fix the way we handled the client knobs (we want to set them for backup and restore calls).

## Type of change

*Please select one of the options below.*

- Bug fix (non-breaking change which fixes an issue)

## Discussion

-

## Testing

Corrected the unit test setup.

## Documentation

-

## Follow-up

-


foundationdb-ci: 
### Result of fdb-kubernetes-operator-pr on Linux CentOS 7

* Commit ID: 2e5847b11bd2c114c45ca8aeba5d3a938cc208ef
* Duration 2:23:46
* Result: :white_check_mark: **SUCCEEDED**
* Error: `N/A`
* [Build Log](https://d1e0l78xbkh3xa.cloudfront.net/fdb-kubernetes-operator/fdb-kubernetes-operator-pr/8c505308-d7fb-4eec-8a60-02b9d384d200.gz) terminal output (available for 30 days)
* [Build Workspace](https://d1e0l78xbkh3xa.cloudfront.net/fdb-kubernetes-operator-pr/8c505308-d7fb-4eec-8a60-02b9d384d200/workspace.zip) zip file of the working directory (available for 30 days)

foundationdb-ci: 
### Result of fdb-kubernetes-operator-pr on Linux CentOS 7

* Commit ID: 85a7d7f3f916c0e3e46aea2fc7d9aa8254ebf7b3
* Duration 3:25:40
* Result: :x: **FAILED**
* Error: `Error while executing command: if $fail_test; then exit 1; fi. Reason: exit status 1`
* [Build Log](https://d1e0l78xbkh3xa.cloudfront.net/fdb-kubernetes-operator/fdb-kubernetes-operator-pr/f4878e03-baab-446f-bef4-b51787305f19.gz) terminal output (available for 30 days)
* [Build Workspace](https://d1e0l78xbkh3xa.cloudfront.net/fdb-kubernetes-operator-pr/f4878e03-baab-446f-bef4-b51787305f19/workspace.zip) zip file of the working directory (available for 30 days)

foundationdb-ci: 
### Result of fdb-kubernetes-operator-pr on Linux CentOS 7

* Commit ID: 85a7d7f3f916c0e3e46aea2fc7d9aa8254ebf7b3
* Duration 3:31:16
* Result: :x: **FAILED**
* Error: `Error while executing command: if $fail_test; then exit 1; fi. Reason: exit status 1`
* [Build Log](https://d1e0l78xbkh3xa.cloudfront.net/fdb-kubernetes-operator/fdb-kubernetes-operator-pr/e909dee9-d0b6-4201-91fe-1876eb89b160.gz) terminal output (available for 30 days)
* [Build Workspace](https://d1e0l78xbkh3xa.cloudfront.net/fdb-kubernetes-operator-pr/e909dee9-d0b6-4201-91fe-1876eb89b160/workspace.zip) zip file of the working directory (available for 30 days)

foundationdb-ci: 
### Result of fdb-kubernetes-operator-pr on Linux CentOS 7

* Commit ID: 85a7d7f3f916c0e3e46aea2fc7d9aa8254ebf7b3
* Duration 3:43:45
* Result: :x: **FAILED**
* Error: `Error while executing command: if $fail_test; then exit 1; fi. Reason: exit status 1`
* [Build Log](https://d1e0l78xbkh3xa.cloudfront.net/fdb-kubernetes-operator/fdb-kubernetes-operator-pr/0e208996-a0ef-48e2-aa42-f5892e0235ac.gz) terminal output (available for 30 days)
* [Build Workspace](https://d1e0l78xbkh3xa.cloudfront.net/fdb-kubernetes-operator-pr/0e208996-a0ef-48e2-aa42-f5892e0235ac/workspace.zip) zip file of the working directory (available for 30 days)


Patch:
diff --git a/Makefile b/Makefile
index 649d75a2..863cddcb 100644
--- a/Makefile
+++ b/Makefile
@@ -159,7 +159,7 @@ fmt: bin/fmt_check
 # TODO johscheuer: enable those new command in a new PR.
 bin/fmt_check: ${GO_ALL}
 	# $(GO_LINES) -w .
-	go fmt .
+	go fmt $$(go list ./...)
 	# $(GO_IMPORTS) -w .
 	#$(GOLANGCI_LINT) run --fix
 	@mkdir -p bin
diff --git a/api/v1beta1/foundationdbcluster_types.go b/api/v1beta1/foundationdbcluster_types.go
index 38d4a3d3..2f618385 100644
--- a/api/v1beta1/foundationdbcluster_types.go
+++ b/api/v1beta1/foundationdbcluster_types.go
@@ -452,6 +452,10 @@ type FoundationDBClusterStatus struct {
 	// If there are more than one value in the slice the reconcile phase is not finished.
 	StorageServersPerDisk []int `json:"storageServersPerDisk,omitempty"`
 
+	// LogServersPerDisk defines the LogServersPerDisk observed in the cluster.
+	// If there are more than one value in the slice the reconcile phase is not finished.
+	LogServersPerDisk []int `json:"logServersPerDisk,omitempty"`
+
 	// ImageTypes defines the kinds of images that are in use in the cluster.
 	// If there is more than one value in the slice the reconcile phase is not
 	// finished.
diff --git a/api/v1beta1/zz_generated.deepcopy.go b/api/v1beta1/zz_generated.deepcopy.go
index 8801d958..989453e8 100644
--- a/api/v1beta1/zz_generated.deepcopy.go
+++ b/api/v1beta1/zz_generated.deepcopy.go
@@ -771,6 +771,11 @@ func (in *FoundationDBClusterStatus) DeepCopyInto(out *FoundationDBClusterStatus
 		*out = make([]int, len(*in))
 		copy(*out, *in)
 	}
+	if in.LogServersPerDisk != nil {
+		in, out := &in.LogServersPerDisk, &out.LogServersPerDisk
+		*out = make([]int, len(*in))
+		copy(*out, *in)
+	}
 	if in.ImageTypes != nil {
 		in, out := &in.ImageTypes, &out.ImageTypes
 		*out = make([]ImageType, len(*in))
diff --git a/api/v1beta2/foundationdb_labels.go b/api/v1beta2/foundationdb_labels.go
index 53458fc1..443faaef 100644
--- a/api/v1beta2/foundationdb_labels.go
+++ b/api/v1beta2/foundationdb_labels.go
@@ -37,6 +37,9 @@ const (
 	// deployments to a cluster.
 	BackupDeploymentLabel = "foundationdb.org/backup-for"
 
+	// BackupDeploymentPodLabel provides the label to select Pods for a specific Backup deployment.
+	BackupDeploymentPodLabel = "foundationdb.org/deployment-name"
+
 	// PublicIPSourceAnnotation is an annotation key that specifies where a pod
 	// gets its public IP from.
 	PublicIPSourceAnnotation = "foundationdb.org/public-ip-source"
diff --git a/api/v1beta2/foundationdb_process_class.go b/api/v1beta2/foundationdb_process_class.go
index 98f2789b..8d560803 100644
--- a/api/v1beta2/foundationdb_process_class.go
+++ b/api/v1beta2/foundationdb_process_class.go
@@ -20,6 +20,10 @@
 
 package v1beta2
 
+import (
+	"strings"
+)
+
 // ProcessClass models the class of a pod
 type ProcessClass string
 
@@ -57,3 +61,15 @@ func (pClass ProcessClass) IsStateful() bool {
 func (pClass ProcessClass) IsTransaction() bool {
 	return pClass != ProcessClassStorage && pClass != ProcessClassGeneral
 }
+
+// SupportsMultipleLogServers determines whether a process class supports multiple log servers. This includes the log
+// class and the transaction class.
+func (pClass ProcessClass) SupportsMultipleLogServers() bool {
+	return pClass == ProcessClassLog || pClass == ProcessClassTransaction
+}
+
+// GetServersPerPodEnvName returns the environment variable name for the servers per Pod.
+// TODO (johscheuer): Revisit this decision: Shouldn't this be an annotation?
+func (pClass ProcessClass) GetServersPerPodEnvName() string {
+	return strings.ToUpper(string(pClass)) + "_SERVERS_PER_POD"
+}
diff --git a/api/v1beta2/foundationdbcluster_types.go b/api/v1beta2/foundationdbcluster_types.go
index f88a8a8b..73e308fe 100644
--- a/api/v1beta2/foundationdbcluster_types.go
+++ b/api/v1beta2/foundationdbcluster_types.go
@@ -179,9 +179,16 @@ type FoundationDBClusterSpec struct {
 	// a single process group (Pod). This number defines the number of processes running
 	// in one Pod whereas the ProcessCounts defines the number of Pods created.
 	// This means that you end up with ProcessCounts["storage"] * StorageServersPerPod
-	// storage processes
+	// storage processes.
 	StorageServersPerPod int `json:"storageServersPerPod,omitempty"`
 
+	// LogServersPerPod defines how many Log Servers should run in
+	// a single process group (Pod). This number defines the number of processes running
+	// in one Pod whereas the ProcessCounts defines the number of Pods created.
+	// This means that you end up with ProcessCounts["Log"] * LogServersPerPod
+	// log processes. This also affects processes with the transaction class.
+	LogServersPerPod int `json:"logServersPerPod,omitempty"`
+
 	// MinimumUptimeSecondsForBounce defines the minimum time, in seconds, that the
 	// processes in the cluster must have been up for before the operator can
 	// execute a bounce.
@@ -273,8 +280,14 @@ type FoundationDBClusterStatus struct {
 
 	// StorageServersPerDisk defines the storageServersPerPod observed in the cluster.
 	// If there are more than one value in the slice the reconcile phase is not finished.
+	// +kubebuilder:validation:MaxItems=5
 	StorageServersPerDisk []int `json:"storageServersPerDisk,omitempty"`
 
+	// LogServersPerDisk defines the LogServersPerDisk observed in the cluster.
+	// If there are more than one value in the slice the reconcile phase is not finished.
+	// +kubebuilder:validation:MaxItems=5
+	LogServersPerDisk []int `json:"logServersPerDisk,omitempty"`
+
 	// ImageTypes defines the kinds of images that are in use in the cluster.
 	// If there is more than one value in the slice the reconcile phase is not
 	// finished.
@@ -1500,6 +1513,19 @@ func (cluster *FoundationDBCluster) CheckReconciliation(log logr.Logger) (bool,
 	return reconciled, nil
 }
 
+// GetDesiredServersPerPod will return the expected server per Pod for the provided process class.
+func (cluster *FoundationDBCluster) GetDesiredServersPerPod(pClass ProcessClass) int {
+	if pClass == ProcessClassStorage {
+		return cluster.GetStorageServersPerPod()
+	}
+
+	if pClass.SupportsMultipleLogServers() {
+		return cluster.GetLogServersPerPod()
+	}
+
+	return 1
+}
+
 // GetStorageServersPerPod returns the StorageServer per Pod.
 func (cluster *FoundationDBCluster) GetStorageServersPerPod() int {
 	if cluster.Spec.StorageServersPerPod <= 1 {
@@ -1509,6 +1535,15 @@ func (cluster *FoundationDBCluster) GetStorageServersPerPod() int {
 	return cluster.Spec.StorageServersPerPod
 }
 
+// GetLogServersPerPod returns the TLog processes per Pod.
+func (cluster *FoundationDBCluster) GetLogServersPerPod() int {
+	if cluster.Spec.LogServersPerPod <= 1 {
+		return 1
+	}
+
+	return cluster.Spec.LogServersPerPod
+}
+
 // alphanum provides the characters that are used for the generation ID in the
 // connection string.
 var alphanum = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
@@ -1993,15 +2028,26 @@ const (
 	PublicIPSourceService PublicIPSource = "service"
 )
 
-// AddStorageServerPerDisk adds serverPerDisk to the status field to keep track which ConfigMaps should be kept
-func (clusterStatus *FoundationDBClusterStatus) AddStorageServerPerDisk(serversPerDisk int) {
-	for _, curServersPerDisk := range clusterStatus.StorageServersPerDisk {
-		if curServersPerDisk == serversPerDisk {
-			return
+// AddServersPerDisk adds serverPerDisk to the status field to keep track which ConfigMaps should be kept
+func (clusterStatus *FoundationDBClusterStatus) AddServersPerDisk(serversPerDisk int, pClass ProcessClass) {
+	if pClass == ProcessClassStorage {
+		for _, curServersPerDisk := range clusterStatus.StorageServersPerDisk {
+			if curServersPerDisk == serversPerDisk {
+				return
+			}
 		}
+		clusterStatus.StorageServersPerDisk = append(clusterStatus.StorageServersPerDisk, serversPerDisk)
+		return
 	}
 
-	clusterStatus.StorageServersPerDisk = append(clusterStatus.StorageServersPerDisk, serversPerDisk)
+	if pClass.SupportsMultipleLogServers() {
+		for _, curServersPerDisk := range clusterStatus.LogServersPerDisk {
+			if curServersPerDisk == serversPerDisk {
+				return
+			}
+		}
+		clusterStatus.LogServersPerDisk = append(clusterStatus.LogServersPerDisk, serversPerDisk)
+	}
 }
 
 // GetMaxConcurrentAutomaticReplacements returns the cluster setting for MaxConcurrentReplacements, defaults to 1 if unset.
diff --git a/api/v1beta2/zz_generated.deepcopy.go b/api/v1beta2/zz_generated.deepcopy.go
index 555fb880..df63320e 100644
--- a/api/v1beta2/zz_generated.deepcopy.go
+++ b/api/v1beta2/zz_generated.deepcopy.go
@@ -729,6 +729,11 @@ func (in *FoundationDBClusterStatus) DeepCopyInto(out *FoundationDBClusterStatus
 		*out = make([]int, len(*in))
 		copy(*out, *in)
 	}
+	if in.LogServersPerDisk != nil {
+		in, out := &in.LogServersPerDisk, &out.LogServersPerDisk
+		*out = make([]int, len(*in))
+		copy(*out, *in)
+	}
 	if in.ImageTypes != nil {
 		in, out := &in.ImageTypes, &out.ImageTypes
 		*out = make([]ImageType, len(*in))
diff --git a/config/crd/bases/apps.foundationdb.org_foundationdbclusters.yaml b/config/crd/bases/apps.foundationdb.org_foundationdbclusters.yaml
index 1214825b..8992e5ce 100644
--- a/config/crd/bases/apps.foundationdb.org_foundationdbclusters.yaml
+++ b/config/crd/bases/apps.foundationdb.org_foundationdbclusters.yaml
@@ -9966,6 +9966,10 @@ spec:
                       type: string
                     type: array
                 type: object
+              logServersPerDisk:
+                items:
+                  type: integer
+                type: array
               missingProcesses:
                 additionalProperties:
                   format: int64
@@ -10465,6 +10469,8 @@ spec:
                 type: object
               logGroup:
                 type: string
+              logServersPerPod:
+                type: integer
               mainContainer:
                 properties:
                   enableLivenessProbe:
@@ -14217,6 +14223,11 @@ spec:
                       type: string
                     type: array
                 type: object
+              logServersPerDisk:
+                items:
+                  type: integer
+                maxItems: 5
+                type: array
               maintenanceModeInfo:
                 properties:
                   processGroups:
@@ -14283,6 +14294,7 @@ spec:
               storageServersPerDisk:
                 items:
                   type: integer
+                maxItems: 5
                 type: array
             type: object
         type: object
diff --git a/controllers/add_pods.go b/controllers/add_pods.go
index 13a097f1..de8278eb 100644
--- a/controllers/add_pods.go
+++ b/controllers/add_pods.go
@@ -90,7 +90,7 @@ func (a addPods) reconcile(ctx context.Context, r *FoundationDBClusterReconciler
 			return &requeue{curError: err}
 		}
 
-		serverPerPod, err := internal.GetStorageServersPerPodForPod(pod)
+		serverPerPod, err := internal.GetServersPerPodForPod(pod, processGroup.ProcessClass)
 		if err != nil {
 			return &requeue{curError: err}
 		}
diff --git a/controllers/bounce_processes.go b/controllers/bounce_processes.go
index ec12cedf..86f7f90d 100644
--- a/controllers/bounce_processes.go
+++ b/controllers/bounce_processes.go
@@ -263,6 +263,11 @@ func getProcessesReadyForRestart(logger logr.Logger, cluster *fdbv1beta2.Foundat
 		expectedProcesses += counts.Storage * (cluster.Spec.StorageServersPerPod - 1)
 	}
 
+	if cluster.Spec.LogServersPerPod > 1 {
+		expectedProcesses += counts.Log * (cluster.Spec.LogServersPerPod - 1)
+		expectedProcesses += counts.Transaction * (cluster.Spec.LogServersPerPod - 1)
+	}
+
 	if cluster.IsBeingUpgradedWithVersionIncompatibleVersion() && expectedProcesses != len(addresses) {
 		return nil, &requeue{
 			message:        fmt.Sprintf("expected %d processes, got %d processes ready to restart", expectedProcesses, len(addresses)),
diff --git a/controllers/cluster_controller.go b/controllers/cluster_controller.go
index 95bc6c64..ebf71b22 100644
--- a/controllers/cluster_controller.go
+++ b/controllers/cluster_controller.go
@@ -223,7 +223,7 @@ func runClusterSubReconciler(ctx context.Context, logger logr.Logger, subReconci
 }
 
 // SetupWithManager prepares a reconciler for use.
-func (r *FoundationDBClusterReconciler) SetupWithManager(mgr ctrl.Manager, maxConcurrentReconciles int, selector metav1.LabelSelector, watchedObjects ...client.Object) error {
+func (r *FoundationDBClusterReconciler) SetupWithManager(mgr ctrl.Manager, maxConcurrentReconciles int, enableNodeIndex bool, selector metav1.LabelSelector, watchedObjects ...client.Object) error {
 	err := mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.Pod{}, "metadata.name", func(o client.Object) []string {
 		return []string{o.(*corev1.Pod).Name}
 	})
@@ -231,11 +231,13 @@ func (r *FoundationDBClusterReconciler) SetupWithManager(mgr ctrl.Manager, maxCo
 		return err
 	}
 
-	err = mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.Node{}, "metadata.name", func(o client.Object) []string {
-		return []string{o.(*corev1.Node).Name}
-	})
-	if err != nil {
-		return err
+	if enableNodeIndex {
+		err = mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.Node{}, "metadata.name", func(o client.Object) []string {
+			return []string{o.(*corev1.Node).Name}
+		})
+		if err != nil {
+			return err
+		}
 	}
 
 	err = mgr.GetFieldIndexer().IndexField(context.Background(), &corev1.Service{}, "metadata.name", func(o client.Object) []string {
@@ -293,18 +295,14 @@ func (r *FoundationDBClusterReconciler) updatePodDynamicConf(logger logr.Logger,
 		return false, nil
 	}
 
-	serversPerPod := 1
-
 	processClass, err := podmanager.GetProcessClass(cluster, pod)
 	if err != nil {
 		return false, err
 	}
 
-	if processClass == fdbv1beta2.ProcessClassStorage {
-		serversPerPod, err = internal.GetStorageServersPerPodForPod(pod)
-		if err != nil {
-			return false, err
-		}
+	serversPerPod, err := internal.GetServersPerPodForPod(pod, processClass)
+	if err != nil {
+		return false, err
 	}
 
 	var expectedConf string
diff --git a/controllers/update_pod_config.go b/controllers/update_pod_config.go
index bc8bbed4..e73225a6 100644
--- a/controllers/update_pod_config.go
+++ b/controllers/update_pod_config.go
@@ -71,7 +71,7 @@ func (updatePodConfig) reconcile(ctx context.Context, r *FoundationDBClusterReco
 			continue
 		}
 
-		serverPerPod, err := internal.GetStorageServersPerPodForPod(pod)
+		serverPerPod, err := internal.GetServersPerPodForPod(pod, processGroup.ProcessClass)
 		if err != nil {
 			curLogger.Error(err, "Error when receiving storage server per Pod")
 			errs = append(errs, err)
diff --git a/controllers/update_status.go b/controllers/update_status.go
index 5c26025f..6e9e0dc9 100644
--- a/controllers/update_status.go
+++ b/controllers/update_status.go
@@ -58,6 +58,7 @@ func (updateStatus) reconcile(ctx context.Context, r *FoundationDBClusterReconci
 
 	// Initialize with the current desired storage servers per Pod
 	clusterStatus.StorageServersPerDisk = []int{cluster.GetStorageServersPerPod()}
+	clusterStatus.LogServersPerDisk = []int{cluster.GetLogServersPerPod()}
 	clusterStatus.ImageTypes = []fdbv1beta2.ImageType{fdbv1beta2.ImageType(internal.GetDesiredImageType(cluster))}
 
 	processMap := make(map[fdbv1beta2.ProcessGroupID][]fdbv1beta2.FoundationDBStatusProcessInfo)
@@ -234,6 +235,7 @@ func (updateStatus) reconcile(ctx context.Context, r *FoundationDBClusterReconci
 	// Sort slices that are assembled based on pods to prevent a reordering from
 	// issuing a new reconcile loop.
 	sort.Ints(clusterStatus.StorageServersPerDisk)
+	sort.Ints(clusterStatus.LogServersPerDisk)
 	sort.Slice(clusterStatus.ImageTypes, func(i int, j int) bool {
 		return string(clusterStatus.ImageTypes[i]) < string(clusterStatus.ImageTypes[j])
 	})
@@ -385,7 +387,6 @@ func checkAndSetProcessStatus(logger logr.Logger, r *FoundationDBClusterReconcil
 
 // Validate and set progressGroup's status
 func validateProcessGroups(ctx context.Context, r *FoundationDBClusterReconciler, cluster *fdbv1beta2.FoundationDBCluster, status *fdbv1beta2.FoundationDBClusterStatus, processMap map[fdbv1beta2.ProcessGroupID][]fdbv1beta2.FoundationDBStatusProcessInfo, configMap *corev1.ConfigMap, pvcs *corev1.PersistentVolumeClaimList, logger logr.Logger) ([]*fdbv1beta2.ProcessGroupStatus, error) {
-	var err error
 	processGroups := status.ProcessGroups
 	processGroupsWithoutExclusion := make(map[fdbv1beta2.ProcessGroupID]fdbv1beta2.None, len(cluster.Spec.ProcessGroupsToRemoveWithoutExclusion))
 
@@ -430,7 +431,6 @@ func validateProcessGroups(ctx context.Context, r *FoundationDBClusterReconciler
 		}
 
 		processGroup.AddAddresses(podmanager.GetPublicIPs(pod, logger), processGroup.IsMarkedForRemoval() || !status.Health.Available)
-		processCount := 1
 
 		// In this case the Pod has a DeletionTimestamp and should be deleted.
 		if !pod.ObjectMeta.DeletionTimestamp.IsZero() {
@@ -453,14 +453,11 @@ func validateProcessGroups(ctx context.Context, r *FoundationDBClusterReconciler
 
 		// Even the process group will be removed we need to keep the config around.
 		// Set the processCount for the process group specific storage servers per pod
-		if processGroup.ProcessClass == fdbv1beta2.ProcessClassStorage {
-			processCount, err = internal.GetStorageServersPerPodForPod(pod)
-			if err != nil {
-				return processGroups, err
-			}
-
-			status.AddStorageServerPerDisk(processCount)
+		processCount, err := internal.GetServersPerPodForPod(pod, processGroup.ProcessClass)
+		if err != nil {
+			return processGroups, err
 		}
+		status.AddServersPerDisk(processCount, processGroup.ProcessClass)
 
 		imageType := internal.GetImageType(pod)
 		imageTypeString := fdbv1beta2.ImageType(imageType)
diff --git a/docs/cluster_spec.md b/docs/cluster_spec.md
index 4a56bb58..ded72b5c 100644
--- a/docs/cluster_spec.md
+++ b/docs/cluster_spec.md
@@ -252,7 +252,8 @@ FoundationDBClusterSpec defines the desired state of a cluster.
 | routing | Routing defines the configuration for routing to our pods. | [RoutingConfig](#routingconfig) | false |
 | ignoreUpgradabilityChecks | IgnoreUpgradabilityChecks determines whether we should skip the check for client compatibility when performing an upgrade. | bool | false |
 | buggify | Buggify defines settings for injecting faults into a cluster for testing. | [BuggifyConfig](#buggifyconfig) | false |
-| storageServersPerPod | StorageServersPerPod defines how many Storage Servers should run in a single process group (Pod). This number defines the number of processes running in one Pod whereas the ProcessCounts defines the number of Pods created. This means that you end up with ProcessCounts[\"storage\"] * StorageServersPerPod storage processes | int | false |
+| storageServersPerPod | StorageServersPerPod defines how many Storage Servers should run in a single process group (Pod). This number defines the number of processes running in one Pod whereas the ProcessCounts defines the number of Pods created. This means that you end up with ProcessCounts[\"storage\"] * StorageServersPerPod storage processes. | int | false |
+| logServersPerPod | LogServersPerPod defines how many Log Servers should run in a single process group (Pod). This number defines the number of processes running in one Pod whereas the ProcessCounts defines the number of Pods created. This means that you end up with ProcessCounts[\"Log\"] * LogServersPerPod log processes. This also affects processes with the transaction class. | int | false |
 | minimumUptimeSecondsForBounce | MinimumUptimeSecondsForBounce defines the minimum time, in seconds, that the processes in the cluster must have been up for before the operator can execute a bounce. | int | false |
 | replaceInstancesWhenResourcesChange | ReplaceInstancesWhenResourcesChange defines if an instance should be replaced when the resource requirements are increased. This can be useful with the combination of local storage. | *bool | false |
 | skip | Skip defines if the cluster should be skipped for reconciliation. This can be useful for investigating in issues or if the environment is unstable. | bool | false |
@@ -282,6 +283,7 @@ FoundationDBClusterStatus defines the observed state of FoundationDBCluster
 | configured | Configured defines whether we have configured the database yet. | bool | false |
 | hasListenIPsForAllPods | HasListenIPsForAllPods defines whether every pod has an environment variable for its listen address. | bool | false |
 | storageServersPerDisk | StorageServersPerDisk defines the storageServersPerPod observed in the cluster. If there are more than one value in the slice the reconcile phase is not finished. | []int | false |
+| logServersPerDisk | LogServersPerDisk defines the LogServersPerDisk observed in the cluster. If there are more than one value in the slice the reconcile phase is not finished. | []int | false |
 | imageTypes | ImageTypes defines the kinds of images that are in use in the cluster. If there is more than one value in the slice the reconcile phase is not finished. | [][ImageType](#imagetype) | false |
 | processGroups | ProcessGroups contain information about a process group. This information is used in multiple places to trigger the according action. | []*[ProcessGroupStatus](#processgroupstatus) | false |
 | locks | Locks contains information about the locking system. | [LockSystemStatus](#locksystemstatus) | false |
diff --git a/docs/manual/replacements_and_deletions.md b/docs/manual/replacements_and_deletions.md
index ff00cac3..0456c104 100644
--- a/docs/manual/replacements_and_deletions.md
+++ b/docs/manual/replacements_and_deletions.md
@@ -49,7 +49,10 @@ Process groups that are set into the crash loop state with the `Buggify` setting
 If the `cluster.Spec.Buggify.EmptyMonitorConf` setting is active the operator won't replace any process groups.
 
 ## Automatic Replacements for ProcessGroups on Tainted Nodes
-The operator has an option to automatically replace ProcessGroups where the associated Pod is running on a tainted Node. This feature is disabled by default, but can be enabled by setting `automationOptions.replacements.taintReplacementOptions`.
+
+The operator has an option to automatically replace ProcessGroups where the associated Pod is running on a tainted Node.
+This feature is disabled by default, but can be enabled by setting `automationOptions.replacements.taintReplacementOptions`.
+If you want to enable this feature you should set the `--enable-node-index` command line flag to allow the operator to access the nodes with an index.
 
 We use three examples below to illustrate how to set up the feature.
 
@@ -73,6 +76,7 @@ If there are multiple Pods on tainted Nodes, the operator will simultaneously re
 ### Example Setup 2
 
 We can enable the taint feature on all taint keys except one taint key with the following  configuration:
+
 ```yaml
 spec:
     automationOptions:
diff --git a/fdbclient/admin_client.go b/fdbclient/admin_client.go
index 9f6c7333..7ca29835 100644
--- a/fdbclient/admin_client.go
+++ b/fdbclient/admin_client.go
@@ -211,9 +211,13 @@ func (client *cliAdminClient) getArgsAndTimeout(command cliCommand) ([]string, t
 		args = append(args, "--exec", command.command)
 	}
 
-	args = append(args, command.getClusterFileFlag(), client.clusterFilePath, "--log")
+	// If we want to print out the version we don't have to pass the cluster file path
+	if len(args) == 0 || args[0] != "--version" {
+		args = append(args, command.getClusterFileFlag(), client.clusterFilePath)
+	}
+
 	// We only want to pass the knobs to fdbbackup and fdbrestore
-	if command.isFdbCli() {
+	if !command.isFdbCli() {
 		args = append(args, client.knobs...)
 	}
 
diff --git a/go.mod b/go.mod
index c59d6198..c28ae027 100644
--- a/go.mod
+++ b/go.mod
@@ -8,11 +8,11 @@ require (
 	github.com/apple/foundationdb/fdbkubernetesmonitor v0.0.0-20220513200452-e6fa4d7422d2
 	github.com/chaos-mesh/chaos-mesh/api v0.0.0-20230613082117-03097981f627
 	github.com/fatih/color v1.14.1
-	github.com/go-logr/logr v1.2.3
+	github.com/go-logr/logr v1.2.4
 	github.com/google/go-cmp v0.5.9
 	github.com/hashicorp/go-retryablehttp v0.7.2
-	github.com/onsi/ginkgo/v2 v2.8.0
-	github.com/onsi/gomega v1.26.0
+	github.com/onsi/ginkgo/v2 v2.9.7
+	github.com/onsi/gomega v1.27.8
 	github.com/prometheus/client_golang v1.14.0
 	github.com/prometheus/common v0.39.0
 	github.com/spf13/cobra v1.6.1
@@ -45,12 +45,14 @@ require (
 	github.com/go-openapi/jsonpointer v0.19.5 // indirect
 	github.com/go-openapi/jsonreference v0.20.0 // indirect
 	github.com/go-openapi/swag v0.21.1 // indirect
+	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/google/btree v1.0.1 // indirect
 	github.com/google/gnostic v0.5.7-v3refs // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
+	github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 // indirect
 	github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
 	github.com/google/uuid v1.3.0 // indirect
 	github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 // indirect
@@ -87,12 +89,13 @@ require (
 	go.uber.org/atomic v1.9.0 // indirect
 	go.uber.org/multierr v1.8.0 // indirect
 	go.uber.org/zap v1.24.0 // indirect
-	golang.org/x/net v0.7.0 // indirect
+	golang.org/x/net v0.10.0 // indirect
 	golang.org/x/oauth2 v0.4.0 // indirect
-	golang.org/x/sys v0.5.0 // indirect
-	golang.org/x/term v0.5.0 // indirect
-	golang.org/x/text v0.7.0 // indirect
+	golang.org/x/sys v0.8.0 // indirect
+	golang.org/x/term v0.8.0 // indirect
+	golang.org/x/text v0.9.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
+	golang.org/x/tools v0.9.1 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
 	google.golang.org/protobuf v1.28.1 // indirect
diff --git a/go.sum b/go.sum
index 9fcb53e5..43f5960b 100644
--- a/go.sum
+++ b/go.sum
@@ -122,8 +122,8 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg
 github.com/go-logr/logr v0.4.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
 github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
-github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
-github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
+github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 github.com/go-logr/zapr v0.4.0/go.mod h1:tabnROwaDl0UNxkVeFRbY8bwB37GwRv0P8lg6aAiEnk=
 github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A=
 github.com/go-logr/zapr v1.2.3/go.mod h1:eIauM6P8qSvTw5o2ez6UEAfGjQKrxQTl5EoK+Qa2oG4=
@@ -140,6 +140,8 @@ github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.21.1 h1:wm0rhTb5z7qpJRHBdPOMuY4QjVUMbF6/kwoYeRAOrKU=
 github.com/go-openapi/swag v0.21.1/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
+github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@@ -171,8 +173,9 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
+github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
 github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
@@ -208,6 +211,8 @@ github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hf
 github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE=
+github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
 github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
@@ -299,12 +304,12 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
 github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
 github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
-github.com/onsi/ginkgo/v2 v2.8.0 h1:pAM+oBNPrpXRs+E/8spkeGx9QgekbRVyr74EUvRVOUI=
-github.com/onsi/ginkgo/v2 v2.8.0/go.mod h1:6JsQiECmxCa3V5st74AL/AmsV482EDdVrGaVW6z3oYU=
+github.com/onsi/ginkgo/v2 v2.9.7 h1:06xGQy5www2oN160RtEZoTvnP2sPhEfePYmCDc2szss=
+github.com/onsi/ginkgo/v2 v2.9.7/go.mod h1:cxrmXWykAwTwhQsJOPfdIDiJ+l2RYq7U8hFU+M/1uw0=
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
-github.com/onsi/gomega v1.26.0 h1:03cDLK28U6hWvCAns6NeydX3zIm4SF3ci69ulidS32Q=
-github.com/onsi/gomega v1.26.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
+github.com/onsi/gomega v1.27.8 h1:gegWiwZjBsf2DgiSbf5hpokZ98JVDMcWkUiigk6/KXc=
+github.com/onsi/gomega v1.27.8/go.mod h1:2J8vzI/s+2shY9XHRApDkdgPo1TKT7P2u6fXeJKFnNQ=
 github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
 github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
 github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
@@ -429,6 +434,7 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -464,8 +470,8 @@ golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwY
 golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
-golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
-golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -529,19 +535,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
-golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
-golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
-golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -600,6 +606,8 @@ golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
+golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
+golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/internal/configmap_helper.go b/internal/configmap_helper.go
index 1ff5ed11..0b12cb11 100644
--- a/internal/configmap_helper.go
+++ b/internal/configmap_helper.go
@@ -66,59 +66,48 @@ func GetConfigMap(cluster *fdbv1beta2.FoundationDBCluster) (*corev1.ConfigMap, e
 		imageTypes[FDBImageType(imageType)] = fdbv1beta2.None{}
 	}
 
-	storageServersPerDisk := cluster.Status.StorageServersPerDisk
-	// If the status field is not initialized we fallback to only the specified count
-	// in the cluster spec. This should only happen in the initial phase of a new cluster.
-	if len(cluster.Status.StorageServersPerDisk) == 0 {
-		storageServersPerDisk = []int{cluster.GetStorageServersPerPod()}
-	}
-
 	for processClass, count := range desiredCounts {
 		if count == 0 {
 			continue
 		}
 
-		if _, useUnifiedImage := imageTypes[FDBImageTypeUnified]; useUnifiedImage {
-			if processClass == fdbv1beta2.ProcessClassStorage {
-				for _, serversPerPod := range storageServersPerDisk {
-					config, err := GetMonitorProcessConfiguration(cluster, processClass, serversPerPod, FDBImageTypeUnified, nil)
-					if err != nil {
-						return nil, err
-					}
-					jsonData, err := json.Marshal(config)
-					if err != nil {
-						return nil, err
-					}
-					filename := GetConfigMapMonitorConfEntry(processClass, FDBImageTypeUnified, serversPerPod)
-					data[filename] = string(jsonData)
-				}
+		serversPerPodSlice := []int{1}
+		if processClass == fdbv1beta2.ProcessClassStorage {
+			// If the status field is not initialized we fallback to only the specified count
+			// in the cluster spec. This should only happen in the initial phase of a new cluster.
+			if len(cluster.Status.StorageServersPerDisk) == 0 {
+				serversPerPodSlice = []int{cluster.GetDesiredServersPerPod(processClass)}
 			} else {
-				config, err := GetMonitorProcessConfiguration(cluster, processClass, 1, FDBImageTypeUnified, nil)
-				if err != nil {
-					return nil, err
-				}
-				jsonData, err := json.Marshal(config)
+				serversPerPodSlice = cluster.Status.StorageServersPerDisk
+			}
+		}
+
+		if processClass.SupportsMultipleLogServers() {
+			// If the status field is not initialized we fallback to only the specified count
+			// in the cluster spec. This should only happen in the initial phase of a new cluster.
+			if len(cluster.Status.LogServersPerDisk) == 0 {
+				serversPerPodSlice = []int{cluster.GetDesiredServersPerPod(processClass)}
+			} else {
+				serversPerPodSlice = cluster.Status.LogServersPerDisk
+			}
+		}
+
+		if _, useUnifiedImage := imageTypes[FDBImageTypeUnified]; useUnifiedImage {
+			for _, serversPerPod := range serversPerPodSlice {
+				filename, jsonData, err := getDataForMonitorConf(cluster, FDBImageTypeUnified, processClass, serversPerPod)
 				if err != nil {
 					return nil, err
 				}
-				data[fmt.Sprintf("fdbmonitor-conf-%s-json", processClass)] = string(jsonData)
+				data[filename] = string(jsonData)
 			}
 		}
 
 		if _, useSplitImage := imageTypes[FDBImageTypeSplit]; useSplitImage {
-			if processClass == fdbv1beta2.ProcessClassStorage {
-				for _, serversPerPod := range storageServersPerDisk {
-					err := setMonitorConfForFilename(cluster, data, GetConfigMapMonitorConfEntry(processClass, FDBImageTypeSplit, serversPerPod), connectionString, processClass, serversPerPod)
-					if err != nil {
-						return nil, err
-					}
+			for _, serversPerPod := range serversPerPodSlice {
+				err := setMonitorConfForFilename(cluster, data, GetConfigMapMonitorConfEntry(processClass, FDBImageTypeSplit, serversPerPod), connectionString, processClass, serversPerPod)
+				if err != nil {
+					return nil, err
 				}
-				continue
-			}
-
-			err := setMonitorConfForFilename(cluster, data, GetConfigMapMonitorConfEntry(processClass, FDBImageTypeSplit, 1), connectionString, processClass, 1)
-			if err != nil {
-				return nil, err
 			}
 		}
 	}
@@ -155,6 +144,19 @@ func getConfigMapMetadata(cluster *fdbv1beta2.FoundationDBCluster) metav1.Object
 	return metadata
 }
 
+func getDataForMonitorConf(cluster *fdbv1beta2.FoundationDBCluster, imageType FDBImageType, pClass fdbv1beta2.ProcessClass, serversPerPod int) (string, []byte, error) {
+	config, err := GetMonitorProcessConfiguration(cluster, pClass, serversPerPod, imageType, nil)
+	if err != nil {
+		return "", nil, err
+	}
+	jsonData, err := json.Marshal(config)
+	if err != nil {
+		return "", nil, err
+	}
+	filename := GetConfigMapMonitorConfEntry(pClass, imageType, serversPerPod)
+	return filename, jsonData, nil
+}
+
 func setMonitorConfForFilename(cluster *fdbv1beta2.FoundationDBCluster, data map[string]string, filename string, connectionString string, processClass fdbv1beta2.ProcessClass, serversPerPod int) error {
 	if connectionString == "" {
 		data[filename] = ""
@@ -172,17 +174,14 @@ func setMonitorConfForFilename(cluster *fdbv1beta2.FoundationDBCluster, data map
 // GetConfigMapMonitorConfEntry returns the specific key for the monitor conf in the ConfigMap
 func GetConfigMapMonitorConfEntry(pClass fdbv1beta2.ProcessClass, imageType FDBImageType, serversPerPod int) string {
 	if imageType == FDBImageTypeUnified {
-		if serversPerPod > 1 && pClass == fdbv1beta2.ProcessClassStorage {
+		if serversPerPod > 1 {
 			return fmt.Sprintf("fdbmonitor-conf-%s-json-multiple", pClass)
 		}
-
 		return fmt.Sprintf("fdbmonitor-conf-%s-json", pClass)
 	}
-
-	if serversPerPod > 1 && pClass == fdbv1beta2.ProcessClassStorage {
+	if serversPerPod > 1 {
 		return fmt.Sprintf("fdbmonitor-conf-%s-density-%d", pClass, serversPerPod)
 	}
-
 	return fmt.Sprintf("fdbmonitor-conf-%s", pClass)
 }
 
diff --git a/internal/pod_models.go b/internal/pod_models.go
index 4519e33a..4da14542 100644
--- a/internal/pod_models.go
+++ b/internal/pod_models.go
@@ -200,10 +200,11 @@ func configureContainersForUnifiedImages(cluster *fdbv1beta2.FoundationDBCluster
 		"--log-path", "/var/log/fdb-trace-logs/monitor.log",
 	}
 
-	if cluster.Spec.StorageServersPerPod > 1 && processClass == fdbv1beta2.ProcessClassStorage {
-		storageServers := strconv.Itoa(cluster.Spec.StorageServersPerPod)
-		mainContainer.Args = append(mainContainer.Args, "--process-count", storageServers)
-		mainContainer.Env = append(mainContainer.Env, corev1.EnvVar{Name: "STORAGE_SERVERS_PER_POD", Value: storageServers})
+	serversPerPod := cluster.GetDesiredServersPerPod(processClass)
+	if serversPerPod > 1 {
+		desiredServersPerPod := strconv.Itoa(serversPerPod)
+		mainContainer.Args = append(mainContainer.Args, "--process-count", desiredServersPerPod)
+		mainContainer.Env = append(mainContainer.Env, corev1.EnvVar{Name: processClass.GetServersPerPodEnvName(), Value: desiredServersPerPod})
 	}
 
 	mainContainer.VolumeMounts = append(mainContainer.VolumeMounts,
@@ -308,7 +309,7 @@ func setAffinityForFaultDomain(cluster *fdbv1beta2.FoundationDBCluster, podSpec
 
 func configureVolumesForContainers(cluster *fdbv1beta2.FoundationDBCluster, podSpec *corev1.PodSpec, volumeClaimTemplate *corev1.PersistentVolumeClaim, podName string, processClass fdbv1beta2.ProcessClass) {
 	useUnifiedImages := pointer.BoolDeref(cluster.Spec.UseUnifiedImage, false)
-	monitorConfKey := GetConfigMapMonitorConfEntry(processClass, GetDesiredImageType(cluster), cluster.GetStorageServersPerPod())
+	monitorConfKey := GetConfigMapMonitorConfEntry(processClass, GetDesiredImageType(cluster), cluster.GetDesiredServersPerPod(processClass))
 
 	var monitorConfFile string
 	if useUnifiedImages {
@@ -476,8 +477,9 @@ func GetPodSpec(cluster *fdbv1beta2.FoundationDBCluster, processClass fdbv1beta2
 			return nil, err
 		}
 
-		if processClass == fdbv1beta2.ProcessClassStorage && cluster.GetStorageServersPerPod() > 1 {
-			sidecarContainer.Env = append(sidecarContainer.Env, corev1.EnvVar{Name: "STORAGE_SERVERS_PER_POD", Value: fmt.Sprintf("%d", cluster.GetStorageServersPerPod())})
+		serversPerPod := cluster.GetDesiredServersPerPod(processClass)
+		if serversPerPod > 1 {
+			sidecarContainer.Env = append(sidecarContainer.Env, corev1.EnvVar{Name: processClass.GetServersPerPodEnvName(), Value: strconv.Itoa(serversPerPod)})
 		}
 	}
 
@@ -942,9 +944,9 @@ func GetBackupDeployment(backup *fdbv1beta2.FoundationDBBackup) (*appsv1.Deploym
 	if podTemplate.ObjectMeta.Labels == nil {
 		podTemplate.ObjectMeta.Labels = make(map[string]string, 1)
 	}
-	podTemplate.ObjectMeta.Labels["foundationdb.org/deployment-name"] = deployment.ObjectMeta.Name
+	podTemplate.ObjectMeta.Labels[fdbv1beta2.BackupDeploymentPodLabel] = deployment.ObjectMeta.Name
 	deployment.Spec.Selector = &metav1.LabelSelector{MatchLabels: map[string]string{
-		"foundationdb.org/deployment-name": deployment.ObjectMeta.Name,
+		fdbv1beta2.BackupDeploymentPodLabel: deployment.ObjectMeta.Name,
 	}}
 
 	podTemplate.Spec.Volumes = append(podTemplate.Spec.Volumes,
@@ -973,23 +975,23 @@ func GetBackupDeployment(backup *fdbv1beta2.FoundationDBBackup) (*appsv1.Deploym
 	return deployment, nil
 }
 
-// GetStorageServersPerPodForPod returns the value of STORAGE_SERVERS_PER_POD from the sidecar or 1
-func GetStorageServersPerPodForPod(pod *corev1.Pod) (int, error) {
+// GetServersPerPodForPod returns the count of servers per Pod based on the processClass from the sidecar or 1
+func GetServersPerPodForPod(pod *corev1.Pod, pClass fdbv1beta2.ProcessClass) (int, error) {
 	// If not specified we will default to 1
-	storageServersPerPod := 1
+	serversPerPod := 1
 	if pod == nil {
-		return storageServersPerPod, nil
+		return serversPerPod, nil
 	}
 
 	for _, container := range pod.Spec.Containers {
 		for _, env := range container.Env {
-			if env.Name == "STORAGE_SERVERS_PER_POD" {
+			if env.Name == pClass.GetServersPerPodEnvName() {
 				return strconv.Atoi(env.Value)
 			}
 		}
 	}
 
-	return storageServersPerPod, nil
+	return serversPerPod, nil
 }
 
 // GetPodMetadata returns the metadata for a specific Pod
diff --git a/internal/replacements/replacements.go b/internal/replacements/replacements.go
index f6c6b919..6aec403e 100644
--- a/internal/replacements/replacements.go
+++ b/internal/replacements/replacements.go
@@ -172,19 +172,19 @@ func processGroupNeedsRemoval(cluster *fdbv1beta2.FoundationDBCluster, pod *core
 			"reason", fmt.Sprintf("publicIP source has changed from %s to %s", ipSource, cluster.GetPublicIPSource()))
 		return true, nil
 	}
+	serversPerPod, err := internal.GetServersPerPodForPod(pod, processGroupStatus.ProcessClass)
+	if err != nil {
+		return false, err
+	}
 
-	if processGroupStatus.ProcessClass == fdbv1beta2.ProcessClassStorage {
-		// Replace the process group if the storage servers differ
-		storageServersPerPod, err := internal.GetStorageServersPerPodForPod(pod)
-		if err != nil {
-			return false, err
-		}
-
-		if storageServersPerPod != cluster.GetStorageServersPerPod() {
-			logger.Info("Replace process group",
-				"reason", fmt.Sprintf("storageServersPerPod has changed from %d to %d", storageServersPerPod, cluster.GetStorageServersPerPod()))
-			return true, nil
-		}
+	desiredServersPerPod := cluster.GetDesiredServersPerPod(processGroupStatus.ProcessClass)
+	// Replace the process group if the expected servers differ from the desired servers
+	if serversPerPod != desiredServersPerPod {
+		logger.Info("Replace process group",
+			"serversPerPod", serversPerPod,
+			"desiredServersPerPod", desiredServersPerPod,
+			"reason", fmt.Sprintf("serversPerPod have changes from current: %d to desired: %d", serversPerPod, desiredServersPerPod))
+		return true, nil
 	}
 
 	expectedNodeSelector := cluster.GetProcessSettings(processGroupStatus.ProcessClass).PodTemplate.Spec.NodeSelector
diff --git a/pkg/fdbadminclient/mock/admin_client_mock.go b/pkg/fdbadminclient/mock/admin_client_mock.go
index b1e0c347..51a62cba 100644
--- a/pkg/fdbadminclient/mock/admin_client_mock.go
+++ b/pkg/fdbadminclient/mock/admin_client_mock.go
@@ -156,19 +156,22 @@ func (client *AdminClient) GetStatus() (*fdbv1beta2.FoundationDBStatus, error) {
 	}
 
 	for _, pod := range pods.Items {
-		podClient, _ := mock.NewMockFdbPodClient(client.Cluster, &pod)
+		processClass, err := podmanager.GetProcessClass(client.Cluster, &pod)
+		if err != nil {
+			return nil, err
+		}
 
-		processCount, err := internal.GetStorageServersPerPodForPod(&pod)
+		processCount, err := internal.GetServersPerPodForPod(&pod, processClass)
 		if err != nil {
 			return nil, err
 		}
 
 		processGroupID := podmanager.GetProcessGroupID(client.Cluster, &pod)
-
 		if _, ok := client.missingProcessGroups[processGroupID]; ok {
 			continue
 		}
 
+		podClient, _ := mock.NewMockFdbPodClient(client.Cluster, &pod)
 		subs, err := podClient.GetVariableSubstitutions()
 		if err != nil {
 			return nil, err
diff --git a/setup/setup.go b/setup/setup.go
index 3215b055..55b2fd4d 100644
--- a/setup/setup.go
+++ b/setup/setup.go
@@ -60,6 +60,7 @@ type Options struct {
 	ServerSideApply                    bool
 	EnableRecoveryState                bool
 	CacheDatabaseStatus                bool
+	EnableNodeIndex                    bool
 	MetricsAddr                        string
 	LeaderElectionID                   string
 	LogFile                            string
@@ -107,6 +108,7 @@ func (o *Options) BindFlags(fs *flag.FlagSet) {
 	fs.BoolVar(&o.ServerSideApply, "server-side-apply", false, "This flag enables server side apply.")
 	fs.BoolVar(&o.EnableRecoveryState, "enable-recovery-state", true, "This flag enables the use of the recovery state for the minimum uptime between bounced if the FDB version supports it.")
 	fs.BoolVar(&o.CacheDatabaseStatus, "cache-database-status", true, "Defines the default value for caching the database status.")
+	fs.BoolVar(&o.EnableNodeIndex, "enable-node-index", false, "Defines if the operator should add an index for accessing node objects. This requires a ClusterRoleBinding with node access. If the taint feature should be used, this setting should be set to true.")
 }
 
 // StartManager will start the FoundationDB operator manager.
@@ -187,7 +189,7 @@ func StartManager(
 		clusterReconciler.EnableRecoveryState = operatorOpts.EnableRecoveryState
 		clusterReconciler.CacheDatabaseStatusForReconciliationDefault = operatorOpts.CacheDatabaseStatus
 
-		if err := clusterReconciler.SetupWithManager(mgr, operatorOpts.MaxConcurrentReconciles, *labelSelector, watchedObjects...); err != nil {
+		if err := clusterReconciler.SetupWithManager(mgr, operatorOpts.MaxConcurrentReconciles, operatorOpts.EnableNodeIndex, *labelSelector, watchedObjects...); err != nil {
 			setupLog.Error(err, "unable to create controller", "controller", "FoundationDBCluster")
 			os.Exit(1)
 		}


Test patch:
diff --git a/api/v1beta2/foundationdbcluster_types_test.go b/api/v1beta2/foundationdbcluster_types_test.go
index 7e2ca945..20c26999 100644
--- a/api/v1beta2/foundationdbcluster_types_test.go
+++ b/api/v1beta2/foundationdbcluster_types_test.go
@@ -28,12 +28,11 @@ import (
 
 	logf "sigs.k8s.io/controller-runtime/pkg/log"
 
+	. "github.com/onsi/ginkgo/v2"
+	. "github.com/onsi/gomega"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/utils/pointer"
-
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
 )
 
 var _ = Describe("[api] FoundationDBCluster", func() {
@@ -3626,7 +3625,7 @@ var _ = Describe("[api] FoundationDBCluster", func() {
 				}
 
 				for _, val := range tc.ValuesToAdd {
-					status.AddStorageServerPerDisk(val)
+					status.AddServersPerDisk(val, ProcessClassStorage)
 				}
 
 				Expect(len(status.StorageServersPerDisk)).To(BeNumerically("==", tc.ExpectedLen))
@@ -3653,6 +3652,47 @@ var _ = Describe("[api] FoundationDBCluster", func() {
 		)
 	})
 
+	When("adding LogServersPerDisk", func() {
+		type testCase struct {
+			ValuesToAdd               []int
+			ExpectedLen               int
+			ExpectedLogServersPerDisk []int
+		}
+
+		DescribeTable("should generate the status correctly",
+			func(tc testCase) {
+				status := FoundationDBClusterStatus{
+					LogServersPerDisk: []int{},
+				}
+
+				for _, val := range tc.ValuesToAdd {
+					status.AddServersPerDisk(val, ProcessClassLog)
+				}
+
+				Expect(len(status.LogServersPerDisk)).To(BeNumerically("==", tc.ExpectedLen))
+				Expect(status.LogServersPerDisk).To(Equal(tc.ExpectedLogServersPerDisk))
+			},
+			Entry("Add missing element",
+				testCase{
+					ValuesToAdd:               []int{1},
+					ExpectedLen:               1,
+					ExpectedLogServersPerDisk: []int{1},
+				}),
+			Entry("Duplicates should only inserted once",
+				testCase{
+					ValuesToAdd:               []int{1, 1},
+					ExpectedLen:               1,
+					ExpectedLogServersPerDisk: []int{1},
+				}),
+			Entry("Multiple elements should be added",
+				testCase{
+					ValuesToAdd:               []int{1, 2},
+					ExpectedLen:               2,
+					ExpectedLogServersPerDisk: []int{1, 2},
+				}),
+		)
+	})
+
 	When("adding addresses to a process group", func() {
 		type testCase struct {
 			initialProcessGroup  ProcessGroupStatus
diff --git a/controllers/backup_controller_test.go b/controllers/backup_controller_test.go
index 19e8f893..7b2a3fa7 100644
--- a/controllers/backup_controller_test.go
+++ b/controllers/backup_controller_test.go
@@ -22,7 +22,6 @@ package controllers
 
 import (
 	"fmt"
-
 	"github.com/FoundationDB/fdb-kubernetes-operator/pkg/fdbadminclient/mock"
 
 	"github.com/FoundationDB/fdb-kubernetes-operator/internal"
diff --git a/controllers/bounce_processes_test.go b/controllers/bounce_processes_test.go
index 1fed94f4..bbf40503 100644
--- a/controllers/bounce_processes_test.go
+++ b/controllers/bounce_processes_test.go
@@ -285,6 +285,84 @@ var _ = Describe("bounceProcesses", func() {
 		})
 	})
 
+	Context("with multiple log servers per pod", func() {
+		BeforeEach(func() {
+			cluster.Spec.LogServersPerPod = 2
+			Expect(k8sClient.Update(context.TODO(), cluster)).NotTo(HaveOccurred())
+			result, err := reconcileCluster(cluster)
+			Expect(err).NotTo(HaveOccurred())
+			Expect(result.Requeue).To(BeFalse())
+			_, err = reloadCluster(cluster)
+			Expect(err).NotTo(HaveOccurred())
+
+			for idx, processGroup := range cluster.Status.ProcessGroups {
+				if processGroup.ProcessGroupID != "log-5" && processGroup.ProcessGroupID != "log-6" {
+					continue
+				}
+
+				cluster.Status.ProcessGroups[idx].UpdateCondition(fdbv1beta2.IncorrectCommandLine, true)
+			}
+		})
+
+		It("should not requeue", func() {
+			Expect(requeue).To(BeNil())
+		})
+
+		It("should kill the targeted processes", func() {
+			addresses := make(map[string]fdbv1beta2.None, 2)
+			for _, processGroupID := range []fdbv1beta2.ProcessGroupID{"log-5", "log-6"} {
+				processGroupAddresses := fdbv1beta2.FindProcessGroupByID(cluster.Status.ProcessGroups, processGroupID).Addresses
+				for _, address := range processGroupAddresses {
+					addresses[fmt.Sprintf("%s:4501", address)] = fdbv1beta2.None{}
+					addresses[fmt.Sprintf("%s:4503", address)] = fdbv1beta2.None{}
+				}
+			}
+			Expect(adminClient.KilledAddresses).To(Equal(addresses))
+		})
+
+		When("doing an upgrade", func() {
+			BeforeEach(func() {
+				cluster.Spec.Version = fdbv1beta2.Versions.NextMajorVersion.String()
+				for _, processGroup := range cluster.Status.ProcessGroups {
+					processGroup.UpdateCondition(fdbv1beta2.IncorrectCommandLine, true)
+				}
+			})
+
+			It("should requeue", func() {
+				Expect(requeue).NotTo(BeNil())
+			})
+
+			It("should kill all the processes", func() {
+				addresses := make(map[string]fdbv1beta2.None, len(cluster.Status.ProcessGroups))
+				for _, processGroup := range cluster.Status.ProcessGroups {
+					for _, address := range processGroup.Addresses {
+						addresses[fmt.Sprintf("%s:4501", address)] = fdbv1beta2.None{}
+						if processGroup.ProcessClass == fdbv1beta2.ProcessClassLog {
+							addresses[fmt.Sprintf("%s:4503", address)] = fdbv1beta2.None{}
+						}
+					}
+				}
+				Expect(adminClient.KilledAddresses).To(Equal(addresses))
+			})
+
+			It("should update the running version in the status", func() {
+				_, err = reloadCluster(cluster)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(cluster.Status.RunningVersion).To(Equal(fdbv1beta2.Versions.NextMajorVersion.String()))
+			})
+
+			It("should submit pending upgrade information for all the processes", func() {
+				expectedUpgrades := make(map[fdbv1beta2.ProcessGroupID]bool, len(cluster.Status.ProcessGroups))
+				for _, processGroup := range cluster.Status.ProcessGroups {
+					expectedUpgrades[processGroup.ProcessGroupID] = true
+				}
+				pendingUpgrades, err := lockClient.GetPendingUpgrades(fdbv1beta2.Versions.NextMajorVersion)
+				Expect(err).NotTo(HaveOccurred())
+				Expect(pendingUpgrades).To(Equal(expectedUpgrades))
+			})
+		})
+	})
+
 	Context("with a pending upgrade", func() {
 		BeforeEach(func() {
 			cluster.Spec.Version = fdbv1beta2.Versions.NextMajorVersion.String()
diff --git a/controllers/cluster_controller_test.go b/controllers/cluster_controller_test.go
index af299d98..98e40ee8 100644
--- a/controllers/cluster_controller_test.go
+++ b/controllers/cluster_controller_test.go
@@ -1088,6 +1088,7 @@ var _ = Describe("cluster_controller", func() {
 					adminClient.UnfreezeStatus()
 					Expect(err).NotTo(HaveOccurred())
 					err = k8sClient.Update(context.TODO(), cluster)
+					Expect(err).NotTo(HaveOccurred())
 
 					result, err := reconcileCluster(cluster)
 					Expect(err).NotTo(HaveOccurred())
@@ -1124,6 +1125,51 @@ var _ = Describe("cluster_controller", func() {
 					Expect(adminClient.KilledAddresses).To(Equal(addresses))
 				})
 			})
+
+			Context("with multiple log servers per pod", func() {
+				BeforeEach(func() {
+					cluster.Spec.Processes = map[fdbv1beta2.ProcessClass]fdbv1beta2.ProcessSettings{fdbv1beta2.ProcessClassGeneral: {CustomParameters: fdbv1beta2.FoundationDBCustomParameters{}}}
+					cluster.Spec.LogServersPerPod = 2
+					adminClient.UnfreezeStatus()
+					Expect(err).NotTo(HaveOccurred())
+					err = k8sClient.Update(context.TODO(), cluster)
+					Expect(err).NotTo(HaveOccurred())
+
+					result, err := reconcileCluster(cluster)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(result.Requeue).To(BeFalse())
+
+					generation, err := reloadCluster(cluster)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(generation).To(Equal(originalVersion + generationGap))
+					originalVersion = cluster.ObjectMeta.Generation
+
+					err = k8sClient.List(context.TODO(), originalPods, getListOptions(cluster)...)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(len(originalPods.Items)).To(Equal(17))
+
+					sortPodsByName(originalPods)
+
+					err = adminClient.FreezeStatus()
+					Expect(err).NotTo(HaveOccurred())
+
+					cluster.Spec.Processes = map[fdbv1beta2.ProcessClass]fdbv1beta2.ProcessSettings{fdbv1beta2.ProcessClassGeneral: {CustomParameters: fdbv1beta2.FoundationDBCustomParameters{"knob_disable_posix_kernel_aio=1"}}}
+					err = k8sClient.Update(context.TODO(), cluster)
+					Expect(err).NotTo(HaveOccurred())
+				})
+
+				It("should bounce the processes", func() {
+					addresses := make(map[string]fdbv1beta2.None, len(originalPods.Items))
+					for _, pod := range originalPods.Items {
+						addresses[cluster.GetFullAddress(pod.Status.PodIP, 1).String()] = fdbv1beta2.None{}
+						if internal.ProcessClassFromLabels(cluster, pod.ObjectMeta.Labels) == fdbv1beta2.ProcessClassLog {
+							addresses[cluster.GetFullAddress(pod.Status.PodIP, 2).String()] = fdbv1beta2.None{}
+						}
+					}
+
+					Expect(adminClient.KilledAddresses).To(Equal(addresses))
+				})
+			})
 		})
 
 		Context("with a configuration change", func() {
@@ -3107,6 +3153,48 @@ var _ = Describe("cluster_controller", func() {
 			})
 		})
 
+		Context("with a basic log process group with multiple log servers per Pod", func() {
+			BeforeEach(func() {
+				cluster.Spec.LogServersPerPod = 2
+				conf, err = internal.GetMonitorConf(cluster, fdbv1beta2.ProcessClassLog, nil, cluster.GetLogServersPerPod())
+				Expect(err).NotTo(HaveOccurred())
+			})
+
+			It("should generate the log conf with two processes", func() {
+				Expect(conf).To(Equal(strings.Join([]string{
+					"[general]",
+					"kill_on_configuration_change = false",
+					"restart_delay = 60",
+					"[fdbserver.1]",
+					"command = $BINARY_DIR/fdbserver",
+					"cluster_file = /var/fdb/data/fdb.cluster",
+					"seed_cluster_file = /var/dynamic-conf/fdb.cluster",
+					"public_address = $FDB_PUBLIC_IP:4501",
+					"class = log",
+					"logdir = /var/log/fdb-trace-logs",
+					"loggroup = " + cluster.Name,
+					"datadir = /var/fdb/data/1",
+					"locality_process_id = $FDB_INSTANCE_ID-1",
+					"locality_instance_id = $FDB_INSTANCE_ID",
+					"locality_machineid = $FDB_MACHINE_ID",
+					"locality_zoneid = $FDB_ZONE_ID",
+					"[fdbserver.2]",
+					"command = $BINARY_DIR/fdbserver",
+					"cluster_file = /var/fdb/data/fdb.cluster",
+					"seed_cluster_file = /var/dynamic-conf/fdb.cluster",
+					"public_address = $FDB_PUBLIC_IP:4503",
+					"class = log",
+					"logdir = /var/log/fdb-trace-logs",
+					"loggroup = " + cluster.Name,
+					"datadir = /var/fdb/data/2",
+					"locality_process_id = $FDB_INSTANCE_ID-2",
+					"locality_instance_id = $FDB_INSTANCE_ID",
+					"locality_machineid = $FDB_MACHINE_ID",
+					"locality_zoneid = $FDB_ZONE_ID",
+				}, "\n")))
+			})
+		})
+
 		Context("with the public IP from the pod", func() {
 			BeforeEach(func() {
 				source := fdbv1beta2.PublicIPSourcePod
@@ -3677,7 +3765,7 @@ func getConfigMapHash(cluster *fdbv1beta2.FoundationDBCluster, pClass fdbv1beta2
 		return "", err
 	}
 
-	serversPerPod, err := internal.GetStorageServersPerPodForPod(pod)
+	serversPerPod, err := internal.GetServersPerPodForPod(pod, pClass)
 	if err != nil {
 		return "", err
 	}
diff --git a/e2e/fixtures/cluster_config.go b/e2e/fixtures/cluster_config.go
index bba85595..288c77e8 100644
--- a/e2e/fixtures/cluster_config.go
+++ b/e2e/fixtures/cluster_config.go
@@ -79,6 +79,8 @@ type ClusterConfig struct {
 	DisksPerMachine int
 	// StorageServerPerPod defines the value that is set in the FoundationDBClusterSpec for this setting.
 	StorageServerPerPod int
+	// LogServersPerPod defines the value that is set in the FoundationDBClusterSpec for this setting.
+	LogServersPerPod int
 	// VolumeSize the size of the volumes that should be created for stateful Pods.
 	VolumeSize string
 	// Namespace to create the cluster in, if empty will use a randomly generated namespace. The setup won't create the
@@ -203,6 +205,11 @@ func (config *ClusterConfig) generatePodResources(
 		memory *= config.StorageServerPerPod
 	}
 
+	if processClass == fdbv1beta2.ProcessClassLog && config.LogServersPerPod > 1 {
+		cpu *= config.LogServersPerPod
+		memory *= config.LogServersPerPod
+	}
+
 	return corev1.ResourceList{
 		corev1.ResourceCPU:    resource.MustParse(strconv.Itoa(cpu)),
 		corev1.ResourceMemory: resource.MustParse(fmt.Sprintf("%dGi", memory)),
diff --git a/e2e/fixtures/fdb_cluster.go b/e2e/fixtures/fdb_cluster.go
index c7c49d70..a04ed769 100644
--- a/e2e/fixtures/fdb_cluster.go
+++ b/e2e/fixtures/fdb_cluster.go
@@ -504,6 +504,11 @@ func (fdbCluster *FdbCluster) GetStoragePods() *corev1.PodList {
 	return fdbCluster.getPodsByProcessClass(fdbv1beta2.ProcessClassStorage)
 }
 
+// GetTransactionPods returns all Pods of this cluster that have the process class transaction.
+func (fdbCluster *FdbCluster) GetTransactionPods() *corev1.PodList {
+	return fdbCluster.getPodsByProcessClass(fdbv1beta2.ProcessClassTransaction)
+}
+
 // GetPod returns the Pod with the given name that runs in the same namespace as the FoundationDBCluster.
 func (fdbCluster *FdbCluster) GetPod(name string) *corev1.Pod {
 	pod := &corev1.Pod{}
@@ -550,6 +555,25 @@ func (fdbCluster *FdbCluster) GetVolumeClaimsForProcesses(
 	return volumeClaimList
 }
 
+// GetLogServersPerPod returns the current expected Log server per pod.
+func (fdbCluster *FdbCluster) GetLogServersPerPod() int {
+	return fdbCluster.cluster.GetLogServersPerPod()
+}
+
+// SetLogServersPerPod set the LogServersPerPod field in the cluster spec.
+func (fdbCluster *FdbCluster) SetLogServersPerPod(
+	serverPerPod int,
+	waitForReconcile bool,
+) error {
+	fdbCluster.cluster.Spec.LogServersPerPod = serverPerPod
+	fdbCluster.UpdateClusterSpec()
+
+	if !waitForReconcile {
+		return nil
+	}
+	return fdbCluster.WaitForReconciliation()
+}
+
 // GetStorageServerPerPod returns the current expected storage server per pod.
 func (fdbCluster *FdbCluster) GetStorageServerPerPod() int {
 	return fdbCluster.cluster.GetStorageServersPerPod()
@@ -573,6 +597,23 @@ func (fdbCluster *FdbCluster) SetStorageServerPerPod(serverPerPod int) error {
 	return fdbCluster.setStorageServerPerPod(serverPerPod, true)
 }
 
+// SetTransactionServerPerPod set the LogServersPerPod field in the cluster spec and changes log Pods to transaction Pods.
+func (fdbCluster *FdbCluster) SetTransactionServerPerPod(
+	serverPerPod int,
+	processCount int,
+	waitForReconcile bool,
+) error {
+	fdbCluster.cluster.Spec.LogServersPerPod = serverPerPod
+	fdbCluster.cluster.Spec.ProcessCounts.Transaction = processCount
+	fdbCluster.cluster.Spec.ProcessCounts.Log = 0
+	fdbCluster.UpdateClusterSpec()
+
+	if !waitForReconcile {
+		return nil
+	}
+	return fdbCluster.WaitForReconciliation()
+}
+
 // ReplacePod replaces the provided Pod if it's part of the FoundationDBCluster.
 func (fdbCluster *FdbCluster) ReplacePod(pod corev1.Pod, waitForReconcile bool) {
 	fdbCluster.cluster.Spec.ProcessGroupsToRemove = []fdbv1beta2.ProcessGroupID{GetProcessGroupID(pod)}
diff --git a/e2e/fixtures/fdb_cluster_specs.go b/e2e/fixtures/fdb_cluster_specs.go
index 52c837be..d8db9a3f 100644
--- a/e2e/fixtures/fdb_cluster_specs.go
+++ b/e2e/fixtures/fdb_cluster_specs.go
@@ -38,6 +38,7 @@ func (factory *Factory) GenerateFDBClusterSpec(config *ClusterConfig) *fdbv1beta
 		factory.createProcesses(config),
 		config.CreateDatabaseConfiguration(),
 		config.StorageServerPerPod,
+		config.LogServersPerPod,
 		mainOverrides,
 		sidecarOverrides)
 }
@@ -49,6 +50,7 @@ func (factory *Factory) createFDBClusterSpec(
 	processes map[fdbv1beta2.ProcessClass]fdbv1beta2.ProcessSettings,
 	databaseConfiguration fdbv1beta2.DatabaseConfiguration,
 	storageServersPerPod int,
+	logServersPerPod int,
 	mainContainerOverrides fdbv1beta2.ContainerOverrides,
 	sidecarContainerOverrides fdbv1beta2.ContainerOverrides,
 ) *fdbv1beta2.FoundationDBCluster {
@@ -63,6 +65,7 @@ func (factory *Factory) createFDBClusterSpec(
 			Processes:                     processes,
 			DatabaseConfiguration:         databaseConfiguration,
 			StorageServersPerPod:          storageServersPerPod,
+			LogServersPerPod:              logServersPerPod,
 			LogGroup:                      namespace + "-" + clusterName,
 			MainContainer:                 mainContainerOverrides,
 			SidecarContainer:              sidecarContainerOverrides,
diff --git a/e2e/fixtures/ha_fdb_cluster.go b/e2e/fixtures/ha_fdb_cluster.go
index a0d5badd..8d9f56b1 100644
--- a/e2e/fixtures/ha_fdb_cluster.go
+++ b/e2e/fixtures/ha_fdb_cluster.go
@@ -224,6 +224,7 @@ func (factory Factory) createHaFdbClusterSpec(
 		*databaseConfiguration,
 		// TODO(johscheuer): make this configurable.
 		1,
+		1,
 		mainContainerOverrides,
 		sidecarContainerOverrides,
 	)
diff --git a/e2e/fixtures/status.go b/e2e/fixtures/status.go
index 56589f40..a54e0cf6 100644
--- a/e2e/fixtures/status.go
+++ b/e2e/fixtures/status.go
@@ -307,6 +307,20 @@ func (fdbCluster *FdbCluster) GetProcessCount(targetRole fdbv1beta2.ProcessRole)
 	return pCounter
 }
 
+// GetProcessCountByProcessClass returns the number of processes based on process class
+func (fdbCluster *FdbCluster) GetProcessCountByProcessClass(pClass fdbv1beta2.ProcessClass) int {
+	pCounter := 0
+	status := fdbCluster.GetStatus()
+
+	for _, process := range status.Cluster.Processes {
+		if process.ProcessClass == pClass {
+			pCounter++
+		}
+	}
+
+	return pCounter
+}
+
 // HasTLSEnabled returns true if the cluster is running with TLS enabled.
 func (fdbCluster *FdbCluster) HasTLSEnabled() bool {
 	status := fdbCluster.GetStatus()
@@ -384,13 +398,17 @@ func (fdbCluster *FdbCluster) GetPodsWithRole(role fdbv1beta2.ProcessRole) []cor
 	var matches []corev1.Pod
 	for _, p := range pods.Items {
 		roles := roleMap[GetProcessGroupID(p)]
+		rolesAdded := map[string]fdbv1beta2.None{}
 		for _, r := range roles {
 			if r.Role == string(role) {
-				matches = append(matches, p)
+				_, ok := rolesAdded[r.Role]
+				if !ok {
+					rolesAdded[r.Role] = fdbv1beta2.None{}
+					matches = append(matches, p)
+				}
 			}
 		}
 	}
-
 	return matches
 }
 
diff --git a/e2e/test_operator/operator_test.go b/e2e/test_operator/operator_test.go
index d1f7f8c5..d77caf71 100644
--- a/e2e/test_operator/operator_test.go
+++ b/e2e/test_operator/operator_test.go
@@ -58,18 +58,36 @@ func init() {
 	testOptions = fixtures.InitFlags()
 }
 
-func validateStorageProcesses(
+func validateProcessesCount(
 	fdbCluster *fixtures.FdbCluster,
-	countStoragePods int,
-	countStorageServer int,
+	processRole fdbv1beta2.ProcessRole,
+	countPods int,
+	countServer int,
 ) {
 	// Using Eventually here to prevent some weird timing from the test runner
+	if processRole == fdbv1beta2.ProcessRoleStorage {
+		Eventually(func() int {
+			return len(fdbCluster.GetStoragePods().Items)
+		}).Should(BeNumerically("==", countPods))
+		Eventually(func() int {
+			return fdbCluster.GetProcessCount(processRole)
+		}).Should(BeNumerically("==", countServer))
+	} else if processRole == fdbv1beta2.ProcessRoleLog {
+		Eventually(func() int {
+			return len(fdbCluster.GetLogPods().Items)
+		}).Should(BeNumerically("==", countPods))
+	} else if processRole == fdbv1beta2.ProcessRole(fdbv1beta2.ProcessClassTransaction) {
+		Eventually(func() int {
+			return len(fdbCluster.GetTransactionPods().Items)
+		}).Should(BeNumerically("==", countPods))
+	} else {
+		Eventually(func() int {
+			return len(fdbCluster.GetPodsWithRole(processRole))
+		}).Should(BeNumerically("==", countPods))
+	}
 	Eventually(func() int {
-		return len(fdbCluster.GetStoragePods().Items)
-	}).Should(BeNumerically("==", countStoragePods))
-	Eventually(func() int {
-		return fdbCluster.GetProcessCount(fdbv1beta2.ProcessRoleStorage)
-	}).Should(BeNumerically("==", countStorageServer))
+		return fdbCluster.GetProcessCountByProcessClass(fdbv1beta2.ProcessClass(processRole))
+	}).Should(BeNumerically("==", countServer))
 }
 
 func validateStorageClass(processClass fdbv1beta2.ProcessClass, targetStorageClass string) {
@@ -394,7 +412,7 @@ var _ = Describe("Operator", Label("e2e", "pr"), func() {
 				expectedPodCnt,
 				expectedStorageProcessesCnt,
 			)
-			validateStorageProcesses(fdbCluster, expectedPodCnt, expectedStorageProcessesCnt)
+			validateProcessesCount(fdbCluster, fdbv1beta2.ProcessRoleStorage, expectedPodCnt, expectedStorageProcessesCnt)
 		})
 
 		AfterEach(func() {
@@ -405,8 +423,9 @@ var _ = Describe("Operator", Label("e2e", "pr"), func() {
 				expectedPodCnt,
 				expectedPodCnt*initialStorageServerPerPod,
 			)
-			validateStorageProcesses(
+			validateProcessesCount(
 				fdbCluster,
+				fdbv1beta2.ProcessRoleStorage,
 				expectedPodCnt,
 				expectedPodCnt*initialStorageServerPerPod,
 			)
@@ -422,7 +441,7 @@ var _ = Describe("Operator", Label("e2e", "pr"), func() {
 				expectedPodCnt,
 				expectedPodCnt*serverPerPod,
 			)
-			validateStorageProcesses(fdbCluster, expectedPodCnt, expectedPodCnt*serverPerPod)
+			validateProcessesCount(fdbCluster, fdbv1beta2.ProcessRoleStorage, expectedPodCnt, expectedPodCnt*serverPerPod)
 		})
 	})
 
@@ -892,6 +911,93 @@ var _ = Describe("Operator", Label("e2e", "pr"), func() {
 		})
 	})
 
+	When("setting 2 logs per disk", func() {
+		var initialLogServerPerPod, expectedPodCnt, expectedLogProcessesCnt int
+
+		BeforeEach(func() {
+			initialLogServerPerPod = fdbCluster.GetLogServersPerPod()
+			initialPods := fdbCluster.GetLogPods()
+			expectedPodCnt = len(initialPods.Items)
+			expectedLogProcessesCnt = expectedPodCnt * initialLogServerPerPod
+			log.Printf(
+				"expectedPodCnt: %d, expectedProcessesCnt: %d",
+				expectedPodCnt,
+				expectedLogProcessesCnt,
+			)
+			Eventually(func() int {
+				return len(fdbCluster.GetLogPods().Items)
+			}).Should(BeNumerically("==", expectedPodCnt))
+		})
+
+		AfterEach(func() {
+			log.Printf("set log servers per Pod to %d", initialLogServerPerPod)
+			Expect(fdbCluster.SetLogServersPerPod(initialLogServerPerPod, true)).ShouldNot(HaveOccurred())
+			log.Printf(
+				"expectedPodCnt: %d, expectedProcessesCnt: %d",
+				expectedPodCnt,
+				expectedPodCnt*initialLogServerPerPod,
+			)
+			Eventually(func() int {
+				return len(fdbCluster.GetLogPods().Items)
+			}).Should(BeNumerically("==", expectedPodCnt))
+		})
+
+		It("should update the log servers to the expected amount", func() {
+			serverPerPod := initialLogServerPerPod * 2
+			log.Printf("set log servers per Pod to %d", initialLogServerPerPod)
+			Expect(fdbCluster.SetLogServersPerPod(serverPerPod, true)).ShouldNot(HaveOccurred())
+			log.Printf(
+				"expectedPodCnt: %d, expectedStorageProcessesCnt: %d",
+				expectedPodCnt,
+				expectedPodCnt*serverPerPod,
+			)
+			validateProcessesCount(fdbCluster, fdbv1beta2.ProcessRoleLog, expectedPodCnt, expectedPodCnt*serverPerPod)
+		})
+	})
+
+	When("setting 2 logs per disk to use transaction process", func() {
+		var initialLogServerPerPod, expectedPodCnt, expectedLogProcessesCnt int
+
+		BeforeEach(func() {
+			initialLogServerPerPod = fdbCluster.GetLogServersPerPod()
+			initialPods := fdbCluster.GetLogPods()
+			expectedPodCnt = len(initialPods.Items)
+			expectedLogProcessesCnt = expectedPodCnt * initialLogServerPerPod
+			log.Printf(
+				"expectedPodCnt: %d, expectedProcessesCnt: %d",
+				expectedPodCnt,
+				expectedLogProcessesCnt,
+			)
+			Eventually(func() int {
+				return len(fdbCluster.GetLogPods().Items)
+			}).Should(BeNumerically("==", expectedPodCnt))
+		})
+
+		AfterEach(func() {
+			log.Printf("set log servers per Pod to %d", initialLogServerPerPod)
+			Expect(fdbCluster.SetTransactionServerPerPod(initialLogServerPerPod, expectedLogProcessesCnt, true)).ShouldNot(HaveOccurred())
+			log.Printf(
+				"expectedPodCnt: %d, expectedProcessesCnt: %d",
+				expectedPodCnt,
+				expectedPodCnt*initialLogServerPerPod,
+			)
+			Eventually(func() int {
+				return len(fdbCluster.GetLogPods().Items)
+			}).Should(BeNumerically("==", expectedPodCnt))
+		})
+
+		It("should update the log servers to the expected amount and should create transaction Pods", func() {
+			serverPerPod := initialLogServerPerPod * 2
+			Expect(fdbCluster.SetTransactionServerPerPod(serverPerPod, expectedLogProcessesCnt, true)).ShouldNot(HaveOccurred())
+			log.Printf(
+				"expectedPodCnt: %d, expectedProcessesCnt: %d",
+				expectedPodCnt,
+				expectedPodCnt*serverPerPod,
+			)
+			validateProcessesCount(fdbCluster, fdbv1beta2.ProcessRole(fdbv1beta2.ProcessClassTransaction), expectedPodCnt, expectedPodCnt*serverPerPod)
+		})
+	})
+
 	When("Migrating a cluster to a different storage class", func() {
 		var defaultStorageClass, targetStorageClass string
 
diff --git a/e2e/test_operator_ha_upgrades/operator_ha_upgrade_test.go b/e2e/test_operator_ha_upgrades/operator_ha_upgrade_test.go
index 403ce577..9a8eed6a 100644
--- a/e2e/test_operator_ha_upgrades/operator_ha_upgrade_test.go
+++ b/e2e/test_operator_ha_upgrades/operator_ha_upgrade_test.go
@@ -262,9 +262,10 @@ var _ = Describe("Operator HA Upgrades", Label("e2e", "pr"), func() {
 
 			// Verify that the cluster generation number didn't increase by more
 			// than 80 (in an ideal case the number of recoveries that should happen
-			// during an upgrade is 9, but in reality that number could be higher
-			// because different server processes may get bounced at different times).
-			// See: https://github.com/FoundationDB/fdb-kubernetes-operator/issues/1607
+			// during an upgrade, because of bouncing of server processes, is 9, but
+			// in reality that number could be higher because different server processes
+			// may get bounced at different times and also because of cluster controller
+			// change, which happens a number of times, during an upgrade).
 			Expect(finalGeneration).To(BeNumerically("<=", initialGeneration+80))
 		},
 		EntryDescription("Upgrade from %[1]s to %[2]s"),
diff --git a/e2e/test_operator_upgrades/operator_upgrades_test.go b/e2e/test_operator_upgrades/operator_upgrades_test.go
index 8e8f8020..30bc4726 100644
--- a/e2e/test_operator_upgrades/operator_upgrades_test.go
+++ b/e2e/test_operator_upgrades/operator_upgrades_test.go
@@ -1135,4 +1135,60 @@ var _ = Describe("Operator Upgrades", Label("e2e", "pr"), func() {
 		EntryDescription("Upgrade from %[1]s to %[2]s"),
 		fixtures.GenerateUpgradeTableEntries(testOptions),
 	)
+
+	DescribeTable(
+		"with 2 log servers per Pod",
+		func(beforeVersion string, targetVersion string) {
+			clusterSetupWithConfig(beforeVersion, true, &fixtures.ClusterConfig{
+				DebugSymbols:     false,
+				LogServersPerPod: 2,
+			})
+
+			Expect(fdbCluster.UpgradeCluster(targetVersion, false)).NotTo(HaveOccurred())
+			// Make sure the cluster is still running with 2 log server per Pod.
+			Expect(fdbCluster.GetCluster().Spec.LogServersPerPod).To(Equal(2))
+
+			if !fixtures.VersionsAreProtocolCompatible(beforeVersion, targetVersion) {
+				// Ensure that the operator is setting the IncorrectConfigMap and IncorrectCommandLine conditions during the upgrade
+				// process.
+				expectedConditions := map[fdbv1beta2.ProcessGroupConditionType]bool{
+					fdbv1beta2.IncorrectConfigMap:   true,
+					fdbv1beta2.IncorrectCommandLine: true,
+				}
+				Eventually(func() bool {
+					cluster := fdbCluster.GetCluster()
+
+					for _, processGroup := range cluster.Status.ProcessGroups {
+						if !processGroup.MatchesConditions(expectedConditions) {
+							return false
+						}
+					}
+
+					return true
+				}).WithTimeout(10 * time.Minute).WithPolling(5 * time.Second).Should(BeTrue())
+			}
+
+			transactionSystemProcessGroups := make(map[fdbv1beta2.ProcessGroupID]fdbv1beta2.None)
+			// Wait until the cluster is upgraded and fully reconciled.
+			Expect(fdbCluster.WaitUntilWithForceReconcile(2, 600, func(cluster *fdbv1beta2.FoundationDBCluster) bool {
+				for _, processGroup := range cluster.Status.ProcessGroups {
+					if processGroup.ProcessClass == fdbv1beta2.ProcessClassStorage {
+						continue
+					}
+					transactionSystemProcessGroups[processGroup.ProcessGroupID] = fdbv1beta2.None{}
+				}
+
+				// Allow soft reconciliation and make sure the running version was updated
+				return cluster.Status.Generations.Reconciled == cluster.Generation && cluster.Status.RunningVersion == targetVersion
+			})).NotTo(HaveOccurred())
+
+			// Get the desired process counts based on the current cluster configuration
+			processCounts, err := fdbCluster.GetProcessCounts()
+			Expect(err).NotTo(HaveOccurred())
+			expectedProcessCounts := (processCounts.Total()-processCounts.Storage)*2 + 5
+			Expect(len(transactionSystemProcessGroups)).To(BeNumerically("<=", expectedProcessCounts))
+		},
+		EntryDescription("Upgrade from %[1]s to %[2]s"),
+		fixtures.GenerateUpgradeTableEntries(testOptions),
+	)
 })
diff --git a/fdbclient/admin_client_test.go b/fdbclient/admin_client_test.go
index aa8b669e..ded59168 100644
--- a/fdbclient/admin_client_test.go
+++ b/fdbclient/admin_client_test.go
@@ -193,170 +193,337 @@ var _ = Describe("admin_client_test", func() {
 		)
 	})
 
-	When("getting the args for the command", func() {
-		var command cliCommand
-		var client *cliAdminClient
-		var args, expectedArgs []string
-		var timeout, expectedTimeout time.Duration
-
-		JustBeforeEach(func() {
-			Expect(client).NotTo(BeNil())
-			args, timeout = client.getArgsAndTimeout(command)
-			Expect(timeout).To(Equal(expectedTimeout))
-			Expect(args).To(ContainElements(expectedArgs))
-			Expect(len(args)).To(BeNumerically("==", len(expectedArgs)))
-		})
-
-		When("the used command defines args", func() {
-			BeforeEach(func() {
-				command = cliCommand{
-					args:    []string{"--version"},
-					version: "7.1.25",
-					timeout: 1 * time.Second,
-				}
-
-				client = &cliAdminClient{
-					Cluster:         nil,
-					clusterFilePath: "test",
-					log:             logr.Discard(),
-				}
-			})
-
-			When("trace options are disabled", func() {
-				BeforeEach(func() {
-					expectedTimeout = 2 * time.Second
-					expectedArgs = []string{
-						"--version",
-						"--timeout",
-						"1",
-					}
-				})
-			})
-		})
-
-		When("the used command is a fdbcli command", func() {
-			BeforeEach(func() {
-				command = cliCommand{
-					command: "maintenance off",
-					timeout: 1 * time.Second,
-				}
-
-				client = &cliAdminClient{
-					Cluster:         nil,
-					clusterFilePath: "test",
-					log:             logr.Discard(),
-				}
-			})
-
-			When("trace options are disabled", func() {
-				BeforeEach(func() {
-					expectedTimeout = 2 * time.Second
-					expectedArgs = []string{
-						"--exec",
-						"maintenance off",
-						"test",
-						"--timeout",
-						"1",
-					}
-				})
-			})
-
-			When("trace options are enabled", func() {
-				BeforeEach(func() {
-					GinkgoT().Setenv("FDB_NETWORK_OPTION_TRACE_ENABLE", "/tmp")
-					expectedTimeout = 2 * time.Second
-					expectedArgs = []string{
-						"--exec",
-						"maintenance off",
-						"test",
-						"--log",
-						"--trace_format",
-						"xml",
-						"--log-dir",
-						"/tmp",
-						"--timeout",
-						"1",
-					}
-				})
-
-				When("a different trace format is defined", func() {
-					BeforeEach(func() {
-						GinkgoT().Setenv("FDB_NETWORK_OPTION_TRACE_FORMAT", "json")
-						expectedTimeout = 2 * time.Second
-						expectedArgs = []string{
-							"--exec",
-							"maintenance off",
-							"test",
-							"--log",
-							"--trace_format",
-							"json",
-							"--log-dir",
-							"/tmp",
-							"--timeout",
-							"1",
-						}
-					})
-				})
-			})
-		})
-
-		When("the used command is a fdbrestore command", func() {
-			BeforeEach(func() {
-				command = cliCommand{
-					binary: fdbrestoreStr,
-					args: []string{
-						"status",
-					},
-					timeout: 1 * time.Second,
-				}
-
-				client = &cliAdminClient{
-					Cluster:         nil,
-					clusterFilePath: "test",
-					log:             logr.Discard(),
-				}
-			})
-
-			When("trace options are disabled", func() {
-				BeforeEach(func() {
-					expectedTimeout = 2 * time.Second
-					expectedArgs = []string{
-						"status",
-					}
-				})
-			})
-
-			When("trace options are enabled", func() {
-				BeforeEach(func() {
-					GinkgoT().Setenv("FDB_NETWORK_OPTION_TRACE_ENABLE", "/tmp")
-					expectedTimeout = 2 * time.Second
-					expectedArgs = []string{
-						"status",
-						"--log",
-						"--trace_format",
-						"xml",
-						"--logdir",
-						"/tmp",
-					}
-				})
-
-				When("a different trace format is defined", func() {
-					BeforeEach(func() {
-						GinkgoT().Setenv("FDB_NETWORK_OPTION_TRACE_FORMAT", "json")
-						expectedTimeout = 2 * time.Second
-						expectedArgs = []string{
-							"status",
-							"--log",
-							"--trace_format",
-							"json",
-							"--logdir",
-							"/tmp",
-						}
-					})
-				})
-			})
-		})
-	})
+	DescribeTable("getting the args for the command", func(command cliCommand, client *cliAdminClient, traceOption string, traceFormat string, expectedArgs []string, expectedTimeout time.Duration) {
+		Expect(client).NotTo(BeNil())
+		if traceOption != "" {
+			GinkgoT().Setenv("FDB_NETWORK_OPTION_TRACE_ENABLE", traceOption)
+		}
+		if traceFormat != "" {
+			GinkgoT().Setenv("FDB_NETWORK_OPTION_TRACE_FORMAT", traceFormat)
+		}
+
+		args, timeout := client.getArgsAndTimeout(command)
+		Expect(timeout).To(Equal(expectedTimeout))
+		Expect(args).To(HaveExactElements(expectedArgs))
+	},
+		Entry("using fdbcli and trace options are disabled",
+			cliCommand{
+				args:    []string{"--version"},
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"",
+			"",
+			[]string{
+				"--version",
+				"--timeout",
+				"1",
+			},
+			2*time.Second,
+		),
+		Entry("using fdbcli and trace options are disabled",
+			cliCommand{
+				command: "maintenance off",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"",
+			"",
+			[]string{
+				"--exec",
+				"maintenance off",
+				"-C",
+				"test",
+				"--timeout",
+				"1",
+			},
+			2*time.Second,
+		),
+		Entry("using fdbcli and trace options are enabled",
+			cliCommand{
+				command: "maintenance off",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"/tmp",
+			"",
+			[]string{
+				"--exec",
+				"maintenance off",
+				"-C",
+				"test",
+				"--log",
+				"--trace_format",
+				"xml",
+				"--log-dir",
+				"/tmp",
+				"--timeout",
+				"1",
+			},
+			2*time.Second,
+		),
+		Entry("using fdbcli and trace options are enabled with a different format",
+			cliCommand{
+				command: "maintenance off",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"/tmp",
+			"json",
+			[]string{
+				"--exec",
+				"maintenance off",
+				"-C",
+				"test",
+				"--log",
+				"--trace_format",
+				"json",
+				"--log-dir",
+				"/tmp",
+				"--timeout",
+				"1",
+			},
+			2*time.Second,
+		),
+		Entry("using fdbcli and trace options are disabled and client knobs are passed",
+			cliCommand{
+				command: "maintenance off",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+				knobs:           []string{"--knob-testing=1"},
+			},
+			"",
+			"",
+			[]string{
+				"--exec",
+				"maintenance off",
+				"-C",
+				"test",
+				"--timeout",
+				"1",
+			},
+			2*time.Second,
+		),
+		// Tests for fdbrestore
+		Entry("using fdbrestore and trace options are disabled",
+			cliCommand{
+				binary:  fdbrestoreStr,
+				command: "status",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"",
+			"",
+			[]string{
+				"--exec",
+				"status",
+				"--dest_cluster_file",
+				"test",
+			},
+			1*time.Second,
+		),
+		Entry("using fdbrestore and trace options are enabled",
+			cliCommand{
+				binary:  fdbrestoreStr,
+				command: "status",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"/tmp",
+			"",
+			[]string{
+				"--exec",
+				"status",
+				"--dest_cluster_file",
+				"test",
+				"--log",
+				"--logdir",
+				"/tmp",
+			},
+			1*time.Second,
+		),
+		Entry("using fdbrestore and trace options are enabled and a different format is set",
+			cliCommand{
+				binary:  fdbrestoreStr,
+				command: "status",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"/tmp",
+			"json",
+			[]string{
+				"--exec",
+				"status",
+				"--dest_cluster_file",
+				"test",
+				"--log",
+				"--logdir",
+				"/tmp",
+			},
+			1*time.Second,
+		),
+		Entry("using fdbrestore and trace options are disabled and client knobs are set",
+			cliCommand{
+				binary:  fdbrestoreStr,
+				command: "status",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+				knobs:           []string{"--knob-testing=1"},
+			},
+			"",
+			"",
+			[]string{
+				"--exec",
+				"status",
+				"--dest_cluster_file",
+				"test",
+				"--knob-testing=1",
+			},
+			1*time.Second,
+		),
+		// Tests for fdbbackup
+		Entry("using fdbbackup and trace options are disabled",
+			cliCommand{
+				binary:  fdbbackupStr,
+				command: "status",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"",
+			"",
+			[]string{
+				"--exec",
+				"status",
+				"-C",
+				"test",
+			},
+			1*time.Second,
+		),
+		Entry("using fdbbackup and trace options are enabled",
+			cliCommand{
+				binary:  fdbbackupStr,
+				command: "status",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"/tmp",
+			"",
+			[]string{
+				"--exec",
+				"status",
+				"-C",
+				"test",
+				"--log",
+				"--logdir",
+				"/tmp",
+			},
+			1*time.Second,
+		),
+		Entry("using fdbbackup and trace options are enabled and a different format is set",
+			cliCommand{
+				binary:  fdbbackupStr,
+				command: "status",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+			},
+			"/tmp",
+			"json",
+			[]string{
+				"--exec",
+				"status",
+				"-C",
+				"test",
+				"--log",
+				"--logdir",
+				"/tmp",
+			},
+			1*time.Second,
+		),
+		Entry("using fdbbackup and trace options are disabled and client knobs are set",
+			cliCommand{
+				binary:  fdbbackupStr,
+				command: "status",
+				version: "7.1.25",
+				timeout: 1 * time.Second,
+			},
+			&cliAdminClient{
+				Cluster:         nil,
+				clusterFilePath: "test",
+				log:             logr.Discard(),
+				knobs:           []string{"--knob-testing=1"},
+			},
+			"",
+			"",
+			[]string{
+				"--exec",
+				"status",
+				"-C",
+				"test",
+				"--knob-testing=1",
+			},
+			1*time.Second,
+		),
+	)
 
 	When("getting the protocol version from fdbcli", func() {
 		var mockRunner *mockCommandRunner
diff --git a/internal/configmap_helper_test.go b/internal/configmap_helper_test.go
index c88c3ed3..06347ae2 100644
--- a/internal/configmap_helper_test.go
+++ b/internal/configmap_helper_test.go
@@ -182,6 +182,54 @@ var _ = Describe("configmap_helper", func() {
 			})
 		})
 
+		Context("with multiple log servers per disk", func() {
+			BeforeEach(func() {
+				cluster.Status.LogServersPerDisk = []int{1, 2}
+			})
+
+			When("using the split image", func() {
+				BeforeEach(func() {
+					cluster.Status.ImageTypes = []fdbv1beta2.ImageType{"split"}
+				})
+				It("includes the data for both configurations", func() {
+					expectedConf, err := GetMonitorConf(cluster, fdbv1beta2.ProcessClassLog, nil, 1)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(configMap.Data["fdbmonitor-conf-log"]).To(Equal(expectedConf))
+
+					expectedConf, err = GetMonitorConf(cluster, fdbv1beta2.ProcessClassLog, nil, 2)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(configMap.Data["fdbmonitor-conf-log-density-2"]).To(Equal(expectedConf))
+				})
+			})
+
+			When("using the unified image", func() {
+				BeforeEach(func() {
+					cluster.Status.ImageTypes = []fdbv1beta2.ImageType{"unified"}
+				})
+
+				It("includes the data for both configurations", func() {
+					jsonData, present := configMap.Data["fdbmonitor-conf-log-json"]
+					Expect(present).To(BeTrue())
+					config := monitorapi.ProcessConfiguration{}
+					err = json.Unmarshal([]byte(jsonData), &config)
+					Expect(err).NotTo(HaveOccurred())
+					expectedConfig, err := GetMonitorProcessConfiguration(cluster, fdbv1beta2.ProcessClassLog, 1, FDBImageTypeUnified, nil)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(config).To(Equal(expectedConfig))
+
+					jsonData, present = configMap.Data["fdbmonitor-conf-log-json-multiple"]
+					Expect(present).To(BeTrue())
+					config = monitorapi.ProcessConfiguration{}
+					err = json.Unmarshal([]byte(jsonData), &config)
+					Expect(err).NotTo(HaveOccurred())
+					expectedConfig, err = GetMonitorProcessConfiguration(cluster, fdbv1beta2.ProcessClassLog, 2, FDBImageTypeUnified, nil)
+					Expect(err).NotTo(HaveOccurred())
+					Expect(config).To(Equal(expectedConfig))
+				})
+
+			})
+		})
+
 		Context("with custom resource labels", func() {
 			BeforeEach(func() {
 				cluster.Spec.LabelConfig = fdbv1beta2.LabelConfig{
diff --git a/internal/pod_models_test.go b/internal/pod_models_test.go
index fd321d09..e555e9b1 100644
--- a/internal/pod_models_test.go
+++ b/internal/pod_models_test.go
@@ -660,7 +660,7 @@ var _ = Describe("pod_models", func() {
 				})
 			})
 
-			When("running running a log with multiple storage servers per disk", func() {
+			When("creating a log with multiple storage servers per disk", func() {
 				BeforeEach(func() {
 					cluster.Spec.StorageServersPerPod = 2
 					spec, err = GetPodSpec(cluster, fdbv1beta2.ProcessClassLog, 1)
@@ -714,6 +714,62 @@ var _ = Describe("pod_models", func() {
 				})
 			})
 
+			When("running multiple log servers per disk", func() {
+				BeforeEach(func() {
+					cluster.Spec.LogServersPerPod = 2
+					spec, err = GetPodSpec(cluster, fdbv1beta2.ProcessClassLog, 1)
+					Expect(err).NotTo(HaveOccurred())
+				})
+
+				It("should pass the process count to the main container", func() {
+					mainContainer := spec.Containers[0]
+					Expect(mainContainer.Name).To(Equal(fdbv1beta2.MainContainerName))
+					Expect(mainContainer.Args).To(Equal([]string{
+						"--input-dir", "/var/dynamic-conf",
+						"--log-path", "/var/log/fdb-trace-logs/monitor.log",
+						"--process-count", "2",
+					}))
+
+					Expect(mainContainer.Env).To(Equal([]corev1.EnvVar{
+						{Name: "FDB_CLUSTER_FILE", Value: "/var/dynamic-conf/fdb.cluster"},
+						{Name: "LOG_SERVERS_PER_POD", Value: "2"},
+						{Name: "FDB_PUBLIC_IP", ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"},
+						}},
+						{Name: "FDB_POD_IP", ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{FieldPath: "status.podIP"},
+						}},
+						{Name: "FDB_MACHINE_ID", ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"},
+						}},
+						{Name: "FDB_ZONE_ID", ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"},
+						}},
+						{Name: "FDB_INSTANCE_ID", Value: "log-1"},
+						{Name: "FDB_IMAGE_TYPE", Value: "unified"},
+						{Name: "FDB_POD_NAME", ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.name"},
+						}},
+						{Name: "FDB_POD_NAMESPACE", ValueFrom: &corev1.EnvVarSource{
+							FieldRef: &corev1.ObjectFieldSelector{FieldPath: "metadata.namespace"},
+						}},
+					}))
+				})
+
+				It("mounts the multiple-storage config map", func() {
+					Expect(spec.Volumes[2]).To(Equal(corev1.Volume{
+						Name: "config-map",
+						VolumeSource: corev1.VolumeSource{ConfigMap: &corev1.ConfigMapVolumeSource{
+							LocalObjectReference: corev1.LocalObjectReference{Name: fmt.Sprintf("%s-config", cluster.Name)},
+							Items: []corev1.KeyToPath{
+								{Key: "fdbmonitor-conf-log-json-multiple", Path: "config.json"},
+								{Key: ClusterFileKey, Path: "fdb.cluster"},
+							},
+						}},
+					}))
+				})
+			})
+
 			Context("with an instance that is crash looping", func() {
 				BeforeEach(func() {
 					cluster.Spec.Buggify.CrashLoop = []fdbv1beta2.ProcessGroupID{"storage-1"}
@@ -2899,7 +2955,7 @@ var _ = Describe("pod_models", func() {
 				Expect(len(deployment.ObjectMeta.OwnerReferences)).To(Equal(1))
 				Expect(deployment.ObjectMeta.OwnerReferences[0].UID).To(Equal(cluster.ObjectMeta.UID))
 				Expect(deployment.ObjectMeta.Labels).To(Equal(map[string]string{
-					"foundationdb.org/backup-for": string(cluster.ObjectMeta.UID),
+					fdbv1beta2.BackupDeploymentLabel: string(cluster.ObjectMeta.UID),
 				}))
 				Expect(deployment.ObjectMeta.Annotations).To(Equal(map[string]string{
 					"foundationdb.org/last-applied-spec": "a1d6ee086624f097243eb14aefbedf84a9016ba0f85640f11020bf50ec9e6d9b",
@@ -2913,10 +2969,10 @@ var _ = Describe("pod_models", func() {
 
 			It("should set the labels for the pod selector", func() {
 				Expect(*deployment.Spec.Selector).To(Equal(metav1.LabelSelector{MatchLabels: map[string]string{
-					"foundationdb.org/deployment-name": "operator-test-1-backup-agents",
+					fdbv1beta2.BackupDeploymentPodLabel: "operator-test-1-backup-agents",
 				}}))
 				Expect(deployment.Spec.Template.ObjectMeta.Labels).To(Equal(map[string]string{
-					"foundationdb.org/deployment-name": "operator-test-1-backup-agents",
+					fdbv1beta2.BackupDeploymentPodLabel: "operator-test-1-backup-agents",
 				}))
 			})
 
@@ -3332,7 +3388,7 @@ var _ = Describe("pod_models", func() {
 					},
 				}
 
-				storageServersPerPod, err := GetStorageServersPerPodForPod(pod)
+				storageServersPerPod, err := GetServersPerPodForPod(pod, fdbv1beta2.ProcessClassStorage)
 				Expect(err).NotTo(HaveOccurred())
 				Expect(storageServersPerPod).To(Equal(1))
 			})
@@ -3353,7 +3409,7 @@ var _ = Describe("pod_models", func() {
 					},
 				}
 
-				storageServersPerPod, err := GetStorageServersPerPodForPod(pod)
+				storageServersPerPod, err := GetServersPerPodForPod(pod, fdbv1beta2.ProcessClassStorage)
 				Expect(err).NotTo(HaveOccurred())
 				Expect(storageServersPerPod).To(Equal(2))
 			})
@@ -3369,7 +3425,7 @@ var _ = Describe("pod_models", func() {
 					},
 				}
 
-				storageServersPerPod, err := GetStorageServersPerPodForPod(pod)
+				storageServersPerPod, err := GetServersPerPodForPod(pod, fdbv1beta2.ProcessClassStorage)
 				Expect(err).NotTo(HaveOccurred())
 				Expect(storageServersPerPod).To(Equal(1))
 			})
@@ -3377,7 +3433,7 @@ var _ = Describe("pod_models", func() {
 
 		Context("when pod is nil", func() {
 			It("should return 1", func() {
-				storageServersPerPod, err := GetStorageServersPerPodForPod(nil)
+				storageServersPerPod, err := GetServersPerPodForPod(nil, fdbv1beta2.ProcessClassStorage)
 				Expect(err).NotTo(HaveOccurred())
 				Expect(storageServersPerPod).To(Equal(1))
 			})
@@ -3387,7 +3443,7 @@ var _ = Describe("pod_models", func() {
 			It("should return 1", func() {
 				pod := &corev1.Pod{}
 
-				storageServersPerPod, err := GetStorageServersPerPodForPod(pod)
+				storageServersPerPod, err := GetServersPerPodForPod(pod, fdbv1beta2.ProcessClassStorage)
 				Expect(err).NotTo(HaveOccurred())
 				Expect(storageServersPerPod).To(Equal(1))
 			})
@@ -3399,7 +3455,7 @@ var _ = Describe("pod_models", func() {
 					Spec: corev1.PodSpec{},
 				}
 
-				storageServersPerPod, err := GetStorageServersPerPodForPod(pod)
+				storageServersPerPod, err := GetServersPerPodForPod(pod, fdbv1beta2.ProcessClassStorage)
 				Expect(err).NotTo(HaveOccurred())
 				Expect(storageServersPerPod).To(Equal(1))
 			})