Repository: clusternet/clusternet
Pull request #297 (2022-04-10 12:23:39)

populate Localizations for dividing scheduling
Signed-off-by: Di Xu <stephenhsu90@gmail.com>

<!--  Thanks for sending a pull request!

-->

#### What type of PR is this?
kind/feature

#### What this PR does / why we need it:
For dividing scheduling, overrides for `replicas` are needed per child cluster.
In this PR, **Localizations** are used to set overrides for `replicas` based on the `replicaJsonPath` from `FeedInventory`.

#### Which issue(s) this PR fixes:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Fixes #

#### Special notes for your reviewer:


codecov-commenter: # [Codecov](https://codecov.io/gh/clusternet/clusternet/pull/297?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet) Report
> Merging [#297](https://codecov.io/gh/clusternet/clusternet/pull/297?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet) (6dc96ec) into [main](https://codecov.io/gh/clusternet/clusternet/commit/4ad4105a83488c0e3a584991de37ca4f68c0b39d?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet) (4ad4105) will **decrease** coverage by `0.06%`.
> The diff coverage is `n/a`.

```diff
@@            Coverage Diff             @@
##             main     #297      +/-   ##
==========================================
- Coverage   11.10%   11.04%   -0.07%     
==========================================
  Files          47       47              
  Lines        4970     4970              
==========================================
- Hits          552      549       -3     
- Misses       4380     4382       +2     
- Partials       38       39       +1     
```


| [Impacted Files](https://codecov.io/gh/clusternet/clusternet/pull/297?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet) | Coverage Δ | |
|---|---|---|
| [...ernet/clusternet/pkg/hub/apiserver/shadow/utils.go](https://codecov.io/gh/clusternet/clusternet/pull/297/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet#diff-c3JjL2dpdGh1Yi5jb20vY2x1c3Rlcm5ldC9jbHVzdGVybmV0L3BrZy9odWIvYXBpc2VydmVyL3NoYWRvdy91dGlscy5nbw==) | `89.28% <0.00%> (-10.72%)` | :arrow_down: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/clusternet/clusternet/pull/297?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/clusternet/clusternet/pull/297?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet). Last update [4ad4105...6dc96ec](https://codecov.io/gh/clusternet/clusternet/pull/297?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=clusternet).


Patch:
diff --git a/pkg/hub/deployer/deployer.go b/pkg/hub/deployer/deployer.go
index 3c182f4..fc6125e 100644
--- a/pkg/hub/deployer/deployer.go
+++ b/pkg/hub/deployer/deployer.go
@@ -81,6 +81,10 @@ type Deployer struct {
 	mfstSynced  cache.InformerSynced
 	subLister   applisters.SubscriptionLister
 	subSynced   cache.InformerSynced
+	finvLister  applisters.FeedInventoryLister
+	finvSynced  cache.InformerSynced
+	locLister   applisters.LocalizationLister
+	locSynced   cache.InformerSynced
 	nsLister    corev1lister.NamespaceLister
 	nsSynced    cache.InformerSynced
 
@@ -126,6 +130,10 @@ func NewDeployer(apiserverURL, systemNamespace, reservedNamespace string,
 		mfstSynced:        clusternetInformerFactory.Apps().V1alpha1().Manifests().Informer().HasSynced,
 		subLister:         clusternetInformerFactory.Apps().V1alpha1().Subscriptions().Lister(),
 		subSynced:         clusternetInformerFactory.Apps().V1alpha1().Subscriptions().Informer().HasSynced,
+		finvLister:        clusternetInformerFactory.Apps().V1alpha1().FeedInventories().Lister(),
+		finvSynced:        clusternetInformerFactory.Apps().V1alpha1().FeedInventories().Informer().HasSynced,
+		locLister:         clusternetInformerFactory.Apps().V1alpha1().Localizations().Lister(),
+		locSynced:         clusternetInformerFactory.Apps().V1alpha1().Localizations().Informer().HasSynced,
 		nsLister:          kubeInformerFactory.Core().V1().Namespaces().Lister(),
 		nsSynced:          kubeInformerFactory.Core().V1().Namespaces().Informer().HasSynced,
 		clusternetClient:  clusternetclient,
@@ -225,6 +233,8 @@ func (deployer *Deployer) Run(workers int, stopCh <-chan struct{}) {
 		deployer.mfstSynced,
 		deployer.subSynced,
 		deployer.nsSynced,
+		deployer.finvSynced,
+		deployer.locSynced,
 	) {
 		return
 	}
@@ -289,7 +299,8 @@ func (deployer *Deployer) handleSubscription(sub *appsapi.Subscription) error {
 		return err
 	}
 
-	err := deployer.populateBases(sub)
+	// populate Base and Localization (for dividing scheduling)
+	err := deployer.populateBasesAndLocalizations(sub)
 	if err != nil {
 		return err
 	}
@@ -297,7 +308,9 @@ func (deployer *Deployer) handleSubscription(sub *appsapi.Subscription) error {
 	return nil
 }
 
-func (deployer *Deployer) populateBases(sub *appsapi.Subscription) error {
+// populateBasesAndLocalizations will populate a group of Base(s) from Subscription.
+// Localization(s) will be populated as well for dividing scheduling.
+func (deployer *Deployer) populateBasesAndLocalizations(sub *appsapi.Subscription) error {
 	allExistingBases, listErr := deployer.baseLister.List(labels.SelectorFromSet(labels.Set{
 		known.ConfigKindLabel:      subscriptionKind.Kind,
 		known.ConfigNameLabel:      sub.Name,
@@ -314,7 +327,7 @@ func (deployer *Deployer) populateBases(sub *appsapi.Subscription) error {
 	}
 
 	var allErrs []error
-	for _, namespacedName := range sub.Status.BindingClusters {
+	for idx, namespacedName := range sub.Status.BindingClusters {
 		// Convert the namespacedName/name string into a distinct namespacedName and name
 		namespace, _, err := cache.SplitMetaNamespaceKey(namespacedName)
 		if err != nil {
@@ -322,9 +335,9 @@ func (deployer *Deployer) populateBases(sub *appsapi.Subscription) error {
 			continue
 		}
 
-		ns, listErr := deployer.nsLister.Get(namespace)
-		if listErr != nil {
-			if apierrors.IsNotFound(listErr) {
+		ns, err2 := deployer.nsLister.Get(namespace)
+		if err2 != nil {
+			if apierrors.IsNotFound(err2) {
 				continue
 			}
 			return fmt.Errorf("failed to populate Bases for Subscription %s: %v", klog.KObj(sub), err)
@@ -367,6 +380,13 @@ func (deployer *Deployer) populateBases(sub *appsapi.Subscription) error {
 			deployer.recorder.Event(sub, corev1.EventTypeWarning, "FailedSyncingBase", msg)
 		}
 		basesToBeDeleted.Delete(klog.KObj(base).String())
+
+		// populate Localizations for dividing scheduling.
+		err = deployer.populateLocalizations(sub, base, idx)
+		if err != nil {
+			allErrs = append(allErrs, err)
+			klog.ErrorDepth(5, fmt.Sprintf("Failed to sync Localizations: %v", err))
+		}
 	}
 
 	for key := range basesToBeDeleted {
@@ -380,48 +400,48 @@ func (deployer *Deployer) populateBases(sub *appsapi.Subscription) error {
 }
 
 func (deployer *Deployer) syncBase(sub *appsapi.Subscription, base *appsapi.Base) error {
-	if curBase, err := deployer.baseLister.Bases(base.Namespace).Get(base.Name); err == nil {
-		if curBase.DeletionTimestamp != nil {
-			return fmt.Errorf("Base %s is deleting, will resync later", klog.KObj(curBase))
-		}
+	_, err := deployer.clusternetClient.AppsV1alpha1().Bases(base.Namespace).Create(context.TODO(),
+		base, metav1.CreateOptions{})
+	if err == nil {
+		msg := fmt.Sprintf("Base %s is created successfully", klog.KObj(base))
+		klog.V(4).Info(msg)
+		deployer.recorder.Event(sub, corev1.EventTypeNormal, "BaseCreated", msg)
+		return nil
+	}
 
-		// update it
-		if !reflect.DeepEqual(curBase.Spec, base.Spec) {
-			curBaseCopy := curBase.DeepCopy()
-			if curBaseCopy.Labels == nil {
-				curBaseCopy.Labels = make(map[string]string)
-			}
-			for key, value := range base.Labels {
-				curBaseCopy.Labels[key] = value
-			}
+	if !apierrors.IsAlreadyExists(err) {
+		return err
+	}
 
-			curBaseCopy.Spec = base.Spec
-			if !utils.ContainsString(curBaseCopy.Finalizers, known.AppFinalizer) {
-				curBaseCopy.Finalizers = append(curBaseCopy.Finalizers, known.AppFinalizer)
-			}
+	// update it
+	curBase, err := deployer.baseLister.Bases(base.Namespace).Get(base.Name)
+	if err != nil {
+		return err
+	}
 
-			_, err = deployer.clusternetClient.AppsV1alpha1().Bases(curBaseCopy.Namespace).Update(context.TODO(),
-				curBaseCopy, metav1.UpdateOptions{})
-			if err == nil {
-				msg := fmt.Sprintf("Base %s is updated successfully", klog.KObj(curBaseCopy))
-				klog.V(4).Info(msg)
-				deployer.recorder.Event(sub, corev1.EventTypeNormal, "BaseUpdated", msg)
-			}
-			return err
-		}
-		return nil
-	} else {
-		if !apierrors.IsNotFound(err) {
-			return err
-		}
+	if curBase.DeletionTimestamp != nil {
+		return fmt.Errorf("Base %s is deleting, will resync later", klog.KObj(curBase))
 	}
 
-	_, err := deployer.clusternetClient.AppsV1alpha1().Bases(base.Namespace).Create(context.TODO(),
-		base, metav1.CreateOptions{})
+	curBaseCopy := curBase.DeepCopy()
+	if curBaseCopy.Labels == nil {
+		curBaseCopy.Labels = make(map[string]string)
+	}
+	for key, value := range base.Labels {
+		curBaseCopy.Labels[key] = value
+	}
+
+	curBaseCopy.Spec = base.Spec
+	if !utils.ContainsString(curBaseCopy.Finalizers, known.AppFinalizer) {
+		curBaseCopy.Finalizers = append(curBaseCopy.Finalizers, known.AppFinalizer)
+	}
+
+	base, err = deployer.clusternetClient.AppsV1alpha1().Bases(curBaseCopy.Namespace).Update(context.TODO(),
+		curBaseCopy, metav1.UpdateOptions{})
 	if err == nil {
-		msg := fmt.Sprintf("Base %s is created successfully", klog.KObj(base))
+		msg := fmt.Sprintf("Base %s is updated successfully", klog.KObj(curBaseCopy))
 		klog.V(4).Info(msg)
-		deployer.recorder.Event(sub, corev1.EventTypeNormal, "BaseCreated", msg)
+		deployer.recorder.Event(sub, corev1.EventTypeNormal, "BaseUpdated", msg)
 	}
 	return err
 }
@@ -451,6 +471,152 @@ func (deployer *Deployer) deleteBase(ctx context.Context, namespacedKey string)
 	return err
 }
 
+func (deployer *Deployer) populateLocalizations(sub *appsapi.Subscription, base *appsapi.Base, clusterIndex int) error {
+	if sub.Spec.SchedulingStrategy != appsapi.DividingSchedulingStrategyType {
+		return nil
+	}
+
+	finv, err := deployer.finvLister.FeedInventories(sub.Namespace).Get(sub.Name)
+	if err != nil {
+		klog.WarningDepth(5, fmt.Sprintf("failed to get FeedInventory %s: %v", klog.KObj(sub), err))
+		return err
+	}
+
+	allExistingLocalizations, err := deployer.locLister.Localizations(base.Namespace).List(labels.SelectorFromSet(labels.Set{
+		string(sub.UID): subscriptionKind.Kind,
+	}))
+	if err != nil {
+		return err
+	}
+	// Localizations to be deleted
+	locsToBeDeleted := sets.String{}
+	for _, loc := range allExistingLocalizations {
+		locsToBeDeleted.Insert(klog.KObj(loc).String())
+	}
+
+	var allErrs []error
+	for _, feedOrder := range finv.Spec.Feeds {
+		replicas, ok := sub.Status.Replicas[utils.GetFeedKey(feedOrder.Feed)]
+		if !ok {
+			continue
+		}
+
+		if len(replicas) == 0 {
+			continue
+		}
+
+		if len(feedOrder.ReplicaJsonPath) == 0 {
+			msg := fmt.Sprintf("no valid JSONPath is set for %s in FeedInventory %s",
+				utils.FormatFeed(feedOrder.Feed), klog.KObj(finv))
+			klog.ErrorDepth(5, msg)
+			allErrs = append(allErrs, errors.New(msg))
+			deployer.recorder.Event(finv, corev1.EventTypeWarning, "ReplicaJsonPathUnset", msg)
+			continue
+		}
+
+		if len(replicas) < clusterIndex {
+			msg := fmt.Sprintf("the length of status.Replicas for %s in Subscription %s is not matched with status.BindingClusters",
+				utils.FormatFeed(feedOrder.Feed), klog.KObj(sub))
+			klog.ErrorDepth(5, msg)
+			allErrs = append(allErrs, errors.New(msg))
+			deployer.recorder.Event(sub, corev1.EventTypeWarning, "BadSchedulingResult", msg)
+			continue
+		}
+
+		suffixName := feedOrder.Feed.Name
+		if len(feedOrder.Feed.Namespace) > 0 {
+			suffixName = fmt.Sprintf("%s.%s", feedOrder.Feed.Namespace, feedOrder.Feed.Name)
+		}
+		loc := GenerateLocalizationTemplate(base, appsapi.ApplyNow)
+		loc.Name = fmt.Sprintf("%s-%s-%s", base.Name, strings.ToLower(feedOrder.Feed.Kind), suffixName)
+		loc.Labels[string(sub.UID)] = subscriptionKind.Kind
+		loc.Spec.Feed = feedOrder.Feed
+		loc.Spec.Overrides = []appsapi.OverrideConfig{
+			{
+				Name:  "dividing scheduling replicas",
+				Value: fmt.Sprintf(`[{"path":%q,"value":%d,"op":"replace"}]`, feedOrder.ReplicaJsonPath, replicas[clusterIndex]),
+				Type:  appsapi.JSONPatchType,
+			},
+		}
+
+		err = deployer.syncLocalization(loc)
+		if err != nil {
+			allErrs = append(allErrs, err)
+			msg := fmt.Sprintf("Failed to sync Localization %s: %v", klog.KObj(loc), err)
+			klog.ErrorDepth(5, msg)
+			deployer.recorder.Event(sub, corev1.EventTypeWarning, "FailedSyncingLocalization", msg)
+		}
+		locsToBeDeleted.Delete(klog.KObj(loc).String())
+	}
+
+	for key := range locsToBeDeleted {
+		err := deployer.deleteLocalization(context.TODO(), key)
+		if err != nil {
+			allErrs = append(allErrs, err)
+		}
+	}
+
+	return utilerrors.NewAggregate(allErrs)
+}
+
+func (deployer *Deployer) syncLocalization(loc *appsapi.Localization) error {
+	_, err := deployer.clusternetClient.AppsV1alpha1().Localizations(loc.Namespace).Create(context.TODO(), loc, metav1.CreateOptions{})
+	if err == nil {
+		klog.V(4).Infof("Localization %s is created successfully", klog.KObj(loc))
+		return nil
+	}
+
+	if !apierrors.IsAlreadyExists(err) {
+		return err
+	}
+
+	// update it
+	curLoc, err := deployer.locLister.Localizations(loc.Namespace).Get(loc.Name)
+	if err != nil {
+		return err
+	}
+	curLocCopy := curLoc.DeepCopy()
+
+	if curLocCopy.Labels == nil {
+		curLocCopy.Labels = make(map[string]string)
+	}
+	for key, value := range loc.Labels {
+		curLocCopy.Labels[key] = value
+	}
+
+	if curLocCopy.Annotations == nil {
+		curLocCopy.Annotations = make(map[string]string)
+	}
+	for key, value := range loc.Annotations {
+		curLocCopy.Annotations[key] = value
+	}
+
+	curLocCopy.Spec = loc.Spec
+
+	_, err = deployer.clusternetClient.AppsV1alpha1().Localizations(curLocCopy.Namespace).Update(context.TODO(), curLocCopy, metav1.UpdateOptions{})
+	if err == nil {
+		klog.V(4).Infof("Localization %s is updated successfully", klog.KObj(curLocCopy))
+	}
+
+	return err
+}
+
+func (deployer *Deployer) deleteLocalization(ctx context.Context, namespacedKey string) error {
+	// Convert the namespace/name string into a distinct namespace and name
+	ns, name, err := cache.SplitMetaNamespaceKey(namespacedKey)
+	if err != nil {
+		return err
+	}
+
+	err = deployer.clusternetClient.AppsV1alpha1().Localizations(ns).Delete(ctx, name, metav1.DeleteOptions{
+		PropagationPolicy: &deletePropagationBackground,
+	})
+	if err != nil && apierrors.IsNotFound(err) {
+		return nil
+	}
+	return err
+}
+
 func (deployer *Deployer) handleBase(base *appsapi.Base) error {
 	klog.V(5).Infof("handle Base %s", klog.KObj(base))
 	if base.DeletionTimestamp != nil {
diff --git a/pkg/hub/deployer/utils.go b/pkg/hub/deployer/utils.go
index 0b20b5c..2c6ae26 100644
--- a/pkg/hub/deployer/utils.go
+++ b/pkg/hub/deployer/utils.go
@@ -21,6 +21,7 @@ import (
 	"strings"
 	"sync"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	utilerrors "k8s.io/apimachinery/pkg/util/errors"
 	"k8s.io/apimachinery/pkg/util/sets"
@@ -104,3 +105,20 @@ func removeFeedFromAllMatchingSubscriptions(clusternetClient *clusternetclientse
 	}
 	return utilerrors.NewAggregate(allErrs)
 }
+
+func GenerateLocalizationTemplate(base *appsapi.Base, overridePolicy appsapi.OverridePolicy) *appsapi.Localization {
+	loc := &appsapi.Localization{
+		ObjectMeta: metav1.ObjectMeta{
+			Namespace: base.Namespace,
+			Labels: map[string]string{
+				known.ObjectCreatedByLabel: known.ClusternetHubName,
+			},
+		},
+		Spec: appsapi.LocalizationSpec{
+			OverridePolicy: overridePolicy,
+			Priority:       500, // use default
+		},
+	}
+	loc.SetOwnerReferences([]metav1.OwnerReference{*metav1.NewControllerRef(base, baseKind)})
+	return loc
+}
diff --git a/pkg/scheduler/algorithm/dividing/static.go b/pkg/scheduler/algorithm/dividing/static.go
index defad0d..b504546 100644
--- a/pkg/scheduler/algorithm/dividing/static.go
+++ b/pkg/scheduler/algorithm/dividing/static.go
@@ -36,9 +36,9 @@ func StaticDivideReplicas(selected *framework.TargetClusters, sub *appsapi.Subsc
 		}
 		if order != nil && order.DesiredReplicas != nil {
 			replicas := staticDivideReplicas(*order.DesiredReplicas, &wl)
-			selected.Replicas[utils.GetFeedKey(&order.Feed)] = replicas
+			selected.Replicas[utils.GetFeedKey(order.Feed)] = replicas
 		} else {
-			selected.Replicas[utils.GetFeedKey(&order.Feed)] = []int32{}
+			selected.Replicas[utils.GetFeedKey(order.Feed)] = []int32{}
 		}
 	}
 	return nil
diff --git a/pkg/utils/feed.go b/pkg/utils/feed.go
index 43e9fd6..18c9d9f 100644
--- a/pkg/utils/feed.go
+++ b/pkg/utils/feed.go
@@ -175,7 +175,7 @@ func HashSubscriptionSpec(subscriptionSpec *appsapi.SubscriptionSpec) uint64 {
 	return uint64(hasher.Sum32())
 }
 
-func GetFeedKey(feed *appsapi.Feed) string {
+func GetFeedKey(feed appsapi.Feed) string {
 	if len(feed.Namespace) != 0 {
 		return fmt.Sprintf("%s/%s/%s/%s", feed.APIVersion, feed.Kind, feed.Namespace, feed.Name)
 	} else {


Test patch:
diff --git a/pkg/scheduler/algorithm/generic_test.go b/pkg/scheduler/algorithm/generic_test.go
index 5e0b033..50b31b6 100644
--- a/pkg/scheduler/algorithm/generic_test.go
+++ b/pkg/scheduler/algorithm/generic_test.go
@@ -29,8 +29,8 @@ func newClusterWithLabels(name, namespace string, labels map[string]string) *clu
 	}
 }
 
-func newDeploymentFeed(name string) *appsapi.Feed {
-	return &appsapi.Feed{
+func newDeploymentFeed(name string) appsapi.Feed {
+	return appsapi.Feed{
 		Kind:       "Deployment",
 		APIVersion: "apps/v1",
 		Namespace:  "default",
@@ -71,13 +71,13 @@ func TestDivideReplicas(t *testing.T) {
 								Weight: 1,
 							},
 						},
-						Feeds: []appsapi.Feed{*newDeploymentFeed("example-1")},
+						Feeds: []appsapi.Feed{newDeploymentFeed("example-1")},
 					},
 				},
 				finv: &appsapi.FeedInventory{
 					Spec: appsapi.FeedInventorySpec{Feeds: []appsapi.FeedOrder{
 						{
-							Feed:            *newDeploymentFeed("example-1"),
+							Feed:            newDeploymentFeed("example-1"),
 							DesiredReplicas: &desiredReplicas,
 						},
 					}},
@@ -116,13 +116,13 @@ func TestDivideReplicas(t *testing.T) {
 								Weight: 2,
 							},
 						},
-						Feeds: []appsapi.Feed{*newDeploymentFeed("example-1")},
+						Feeds: []appsapi.Feed{newDeploymentFeed("example-1")},
 					},
 				},
 				finv: &appsapi.FeedInventory{
 					Spec: appsapi.FeedInventorySpec{Feeds: []appsapi.FeedOrder{
 						{
-							Feed:            *newDeploymentFeed("example-1"),
+							Feed:            newDeploymentFeed("example-1"),
 							DesiredReplicas: &desiredReplicas,
 						},
 					}},
@@ -161,13 +161,13 @@ func TestDivideReplicas(t *testing.T) {
 								Weight: 2,
 							},
 						},
-						Feeds: []appsapi.Feed{*newDeploymentFeed("example-1")},
+						Feeds: []appsapi.Feed{newDeploymentFeed("example-1")},
 					},
 				},
 				finv: &appsapi.FeedInventory{
 					Spec: appsapi.FeedInventorySpec{Feeds: []appsapi.FeedOrder{
 						{
-							Feed:            *newDeploymentFeed("example-1"),
+							Feed:            newDeploymentFeed("example-1"),
 							DesiredReplicas: &desiredReplicas,
 						},
 					}},
@@ -214,13 +214,13 @@ func TestDivideReplicas(t *testing.T) {
 								Weight: 2,
 							},
 						},
-						Feeds: []appsapi.Feed{*newDeploymentFeed("example-1")},
+						Feeds: []appsapi.Feed{newDeploymentFeed("example-1")},
 					},
 				},
 				finv: &appsapi.FeedInventory{
 					Spec: appsapi.FeedInventorySpec{Feeds: []appsapi.FeedOrder{
 						{
-							Feed:            *newDeploymentFeed("example-1"),
+							Feed:            newDeploymentFeed("example-1"),
 							DesiredReplicas: &desiredReplicas,
 						},
 					}},
@@ -267,17 +267,17 @@ func TestDivideReplicas(t *testing.T) {
 								Weight: 2,
 							},
 						},
-						Feeds: []appsapi.Feed{*newDeploymentFeed("example-1"), *newDeploymentFeed("example-2")},
+						Feeds: []appsapi.Feed{newDeploymentFeed("example-1"), newDeploymentFeed("example-2")},
 					},
 				},
 				finv: &appsapi.FeedInventory{
 					Spec: appsapi.FeedInventorySpec{Feeds: []appsapi.FeedOrder{
 						{
-							Feed:            *newDeploymentFeed("example-1"),
+							Feed:            newDeploymentFeed("example-1"),
 							DesiredReplicas: &desiredReplicas,
 						},
 						{
-							Feed:            *newDeploymentFeed("example-2"),
+							Feed:            newDeploymentFeed("example-2"),
 							DesiredReplicas: &desiredReplicas,
 						},
 					}},