Skip to content

Commit

Permalink
Add Route labels for active Revision traffic targets (#745)
Browse files Browse the repository at this point in the history
Fixes #697

When a route is added, Route labels are attached to the revision
targets. Either, direct revision target specified in the route is labeled
or if route traffic target is a configuration, latest ready revision is labeled.
  • Loading branch information
johnugeorge authored and google-prow-robot committed May 1, 2018
1 parent 720a7d2 commit c5af8a1
Show file tree
Hide file tree
Showing 2 changed files with 216 additions and 15 deletions.
115 changes: 108 additions & 7 deletions pkg/controller/route/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,12 +391,23 @@ func (c *Controller) syncTrafficTargetsAndUpdateRouteStatus(route *v1alpha1.Rout
if err := c.extendConfigurationsWithIndirectTrafficTargets(route, configMap, revMap); err != nil {
return nil, err
}
if err := c.setLabelForGivenConfigurations(route, configMap); err != nil {
if err := c.extendRevisionsWithIndirectTrafficTargets(route, configMap, revMap); err != nil {
return nil, err
}

if err := c.deleteLabelForOutsideOfGivenConfigurations(route, configMap); err != nil {
return nil, err
}
if err := c.setLabelForGivenConfigurations(route, configMap); err != nil {
return nil, err
}

if err := c.deleteLabelForOutsideOfGivenRevisions(route, revMap); err != nil {
return nil, err
}
if err := c.setLabelForGivenRevisions(route, revMap); err != nil {
return nil, err
}

// Then create the actual route rules.
glog.Info("Creating Istio route rules")
Expand Down Expand Up @@ -583,6 +594,38 @@ func (c *Controller) extendConfigurationsWithIndirectTrafficTargets(
return nil
}

func (c *Controller) extendRevisionsWithIndirectTrafficTargets(
route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration, revMap map[string]*v1alpha1.Revision) error {
ns := route.Namespace
revisionClient := c.elaclientset.ElafrosV1alpha1().Revisions(ns)

for _, tt := range route.Spec.Traffic {
if tt.ConfigurationName != "" {
configName := tt.ConfigurationName
if config, ok := configMap[configName]; ok {

revName := config.Status.LatestReadyRevisionName
if revName == "" {
glog.Infof("Configuration %s is not ready. Skipping Configuration %q during route reconcile",
tt.ConfigurationName)
continue
}
// Check if it is a duplicated revision
if _, ok := revMap[revName]; !ok {
rev, err := revisionClient.Get(revName, metav1.GetOptions{})
if err != nil {
glog.Errorf("Failed to fetch Revision %s: %s", revName, err)
return err
}
revMap[revName] = rev
}
}
}
}

return nil
}

func (c *Controller) setLabelForGivenConfigurations(
route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration) error {
configClient := c.elaclientset.ElafrosV1alpha1().Configurations(route.Namespace)
Expand Down Expand Up @@ -617,6 +660,38 @@ func (c *Controller) setLabelForGivenConfigurations(
return nil
}

func (c *Controller) setLabelForGivenRevisions(
route *v1alpha1.Route, revMap map[string]*v1alpha1.Revision) error {
revisionClient := c.elaclientset.ElafrosV1alpha1().Revisions(route.Namespace)

// Validate revision if it already has a route label
for _, rev := range revMap {
if routeName, ok := rev.Labels[ela.RouteLabelKey]; ok {
if routeName != route.Name {
errMsg := fmt.Sprintf("Revision %q is already in use by %q, and cannot be used by %q",
rev.Name, routeName, route.Name)
c.recorder.Event(route, corev1.EventTypeWarning, "RevisionInUse", errMsg)
return errors.New(errMsg)
}
}
}

for _, rev := range revMap {
if rev.Labels == nil {
rev.Labels = make(map[string]string)
} else if _, ok := rev.Labels[ela.RouteLabelKey]; ok {
continue
}
rev.Labels[ela.RouteLabelKey] = route.Name
if _, err := revisionClient.Update(rev); err != nil {
glog.Errorf("Failed to add route label to Revision %s: %s", rev.Name, err)
return err
}
}

return nil
}

func (c *Controller) deleteLabelForOutsideOfGivenConfigurations(
route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration) error {
configClient := c.elaclientset.ElafrosV1alpha1().Configurations(route.Namespace)
Expand Down Expand Up @@ -646,12 +721,40 @@ func (c *Controller) deleteLabelForOutsideOfGivenConfigurations(
return nil
}

func (c *Controller) deleteLabelForOutsideOfGivenRevisions(
route *v1alpha1.Route, revMap map[string]*v1alpha1.Revision) error {
revClient := c.elaclientset.ElafrosV1alpha1().Revisions(route.Namespace)

oldRevList, err := revClient.List(
metav1.ListOptions{
LabelSelector: fmt.Sprintf("%s=%s", ela.RouteLabelKey, route.Name),
},
)
if err != nil {
glog.Errorf("Failed to fetch revisions with label '%s=%s': %s",
ela.RouteLabelKey, route.Name, err)
return err
}

// Delete label for newly removed revisions as traffic target.
for _, rev := range oldRevList.Items {
if _, ok := revMap[rev.Name]; !ok {
delete(rev.Labels, ela.RouteLabelKey)
if _, err := revClient.Update(&rev); err != nil {
glog.Errorf("Failed to remove route label from Revision %s: %s", rev.Name, err)
return err
}
}
}

return nil
}

func (c *Controller) computeRevisionRoutes(
route *v1alpha1.Route, configMap map[string]*v1alpha1.Configuration, revMap map[string]*v1alpha1.Revision) ([]RevisionRoute, error) {
glog.Infof("Figuring out routes for Route: %s", route.Name)
ns := route.Namespace
elaNS := controller.GetElaNamespaceName(ns)
revClient := c.elaclientset.ElafrosV1alpha1().Revisions(ns)
ret := []RevisionRoute{}

for _, tt := range route.Spec.Traffic {
Expand All @@ -666,11 +769,9 @@ func (c *Controller) computeRevisionRoutes(
tt.ConfigurationName)
return nil, nil
}
rev, err = revClient.Get(revName, metav1.GetOptions{})
if err != nil {
glog.Errorf("Failed to fetch Revision %s: %s", revName, err)
return nil, err
}
// Revision has been already fetched indirectly in extendRevisionsWithIndirectTrafficTargets
rev = revMap[revName]

} else {
// Direct revision has already been fetched
rev = revMap[revName]
Expand Down
116 changes: 108 additions & 8 deletions pkg/controller/route/route_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,63 @@ func TestSetLabelToConfigurationDirectlyConfigured(t *testing.T) {
}
}

func TestSetLabelToConfigurationIndirectlyConfigured(t *testing.T) {
func TestSetLabelToRevisionDirectlyConfigured(t *testing.T) {
_, elaClient, controller, _, elaInformer := newTestController(t)
config := getTestConfiguration()
rev := getTestRevisionForConfig(config)
route := getTestRouteWithTrafficTargets(
[]v1alpha1.TrafficTarget{
v1alpha1.TrafficTarget{
ConfigurationName: config.Name,
Percent: 100,
},
},
)

elaClient.ElafrosV1alpha1().Configurations(testNamespace).Create(config)
elaClient.ElafrosV1alpha1().Revisions(testNamespace).Create(rev)
elaClient.ElafrosV1alpha1().Routes(testNamespace).Create(route)
// Since updateRouteEvent looks in the lister, we need to add it to the informer
elaInformer.Elafros().V1alpha1().Routes().Informer().GetIndexer().Add(route)
controller.updateRouteEvent(KeyOrDie(route))

rev, err := elaClient.ElafrosV1alpha1().Revisions(testNamespace).Get(rev.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("error getting revision: %v", err)
}

// Revision should not have route label as the revision is not marked as the Config's latest ready revision
expectedLabels := map[string]string{
ela.ConfigurationLabelKey: config.Name,
}

if diff := cmp.Diff(expectedLabels, rev.Labels); diff != "" {
t.Errorf("Unexpected label diff (-want +got): %v", diff)
}

// Mark the revision as the Config's latest ready revision
config.Status.LatestReadyRevisionName = rev.Name

elaClient.ElafrosV1alpha1().Configurations(testNamespace).Update(config)
controller.updateRouteEvent(KeyOrDie(route))

rev, err = elaClient.ElafrosV1alpha1().Revisions(testNamespace).Get(rev.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("error getting revision: %v", err)
}

// Revision should have the route label
expectedLabels = map[string]string{
ela.ConfigurationLabelKey: config.Name,
ela.RouteLabelKey: route.Name,
}

if diff := cmp.Diff(expectedLabels, rev.Labels); diff != "" {
t.Errorf("Unexpected label diff (-want +got): %v", diff)
}
}

func TestSetLabelToConfigurationAndRevisionIndirectlyConfigured(t *testing.T) {
_, elaClient, controller, _, elaInformer := newTestController(t)
config := getTestConfiguration()
rev := getTestRevisionForConfig(config)
Expand Down Expand Up @@ -774,7 +830,22 @@ func TestSetLabelToConfigurationIndirectlyConfigured(t *testing.T) {
// Configuration should be labeled for this route
expectedLabels := map[string]string{ela.RouteLabelKey: route.Name}
if diff := cmp.Diff(expectedLabels, config.Labels); diff != "" {
t.Errorf("Unexpected label diff (-want +got): %v", diff)
t.Errorf("Unexpected label in configuration diff (-want +got): %v", diff)
}

rev, err = elaClient.ElafrosV1alpha1().Revisions(testNamespace).Get(rev.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("error getting revision: %v", err)
}

// Revision should have the route label
expectedLabels = map[string]string{
ela.ConfigurationLabelKey: config.Name,
ela.RouteLabelKey: route.Name,
}

if diff := cmp.Diff(expectedLabels, rev.Labels); diff != "" {
t.Errorf("Unexpected label in revision diff (-want +got): %v", diff)
}
}

Expand Down Expand Up @@ -823,7 +894,7 @@ func TestCreateRouteWithInvalidConfigurationShouldReturnError(t *testing.T) {
}
}

func TestSetLabelNotChangeConfigurationLabelIfLabelExists(t *testing.T) {
func TestSetLabelNotChangeConfigurationAndRevisionLabelIfLabelExists(t *testing.T) {
_, elaClient, controller, _, elaInformer := newTestController(t)
config := getTestConfiguration()
rev := getTestRevisionForConfig(config)
Expand All @@ -839,30 +910,44 @@ func TestSetLabelNotChangeConfigurationLabelIfLabelExists(t *testing.T) {
// by function setLabelForGivenConfigurations.
config.Labels = map[string]string{ela.RouteLabelKey: route.Name}

// Set revision's route label with route name to make sure revision's label will not be set
// by function setLabelForGivenRevisions.
rev.Labels[ela.RouteLabelKey] = route.Name

elaClient.ElafrosV1alpha1().Configurations(testNamespace).Create(config)
elaClient.ElafrosV1alpha1().Revisions(testNamespace).Create(rev)
elaClient.ElafrosV1alpha1().Routes(testNamespace).Create(route)
// Since updateRouteEvent looks in the lister, we need to add it to the informer
elaInformer.Elafros().V1alpha1().Routes().Informer().GetIndexer().Add(route)

// No configuration updates
// Assert that the configuration is not updated when updateRouteEvent is called.
elaClient.Fake.PrependReactor("update", "configurations",
func(a kubetesting.Action) (bool, runtime.Object, error) {
t.Error("Configuration was updated unexpectedly")
return true, nil, nil
},
)

// Assert that the revision is not updated when updateRouteEvent is called.
elaClient.Fake.PrependReactor("update", "revision",
func(a kubetesting.Action) (bool, runtime.Object, error) {
t.Error("Revision was updated unexpectedly")
return true, nil, nil
},
)

controller.updateRouteEvent(route.Namespace + "/" + route.Name)
}

func TestDeleteLabelOfConfigurationWhenUnconfigured(t *testing.T) {
func TestDeleteLabelOfConfigurationAndRevisionWhenUnconfigured(t *testing.T) {
_, elaClient, controller, _, elaInformer := newTestController(t)
route := getTestRouteWithTrafficTargets([]v1alpha1.TrafficTarget{})
config := getTestConfiguration()
// Set a label which is expected to be deleted.
// Set a route label in configuration which is expected to be deleted.
config.Labels = map[string]string{ela.RouteLabelKey: route.Name}
rev := getTestRevisionForConfig(config)
// Set a route label in revision which is expected to be deleted.
rev.Labels[ela.RouteLabelKey] = route.Name

elaClient.ElafrosV1alpha1().Configurations(testNamespace).Create(config)
elaClient.ElafrosV1alpha1().Revisions(testNamespace).Create(rev)
Expand All @@ -876,11 +961,26 @@ func TestDeleteLabelOfConfigurationWhenUnconfigured(t *testing.T) {
t.Fatalf("error getting config: %v", err)
}

// Check labels, should be empty.
// Check labels in configuration, should be empty.
expectedLabels := map[string]string{}
if diff := cmp.Diff(expectedLabels, config.Labels); diff != "" {
t.Errorf("Unexpected label diff (-want +got): %v", diff)
t.Errorf("Unexpected label in Configuration diff (-want +got): %v", diff)
}

rev, err = elaClient.ElafrosV1alpha1().Revisions(testNamespace).Get(rev.Name, metav1.GetOptions{})
if err != nil {
t.Fatalf("error getting revision: %v", err)
}

expectedLabels = map[string]string{
ela.ConfigurationLabelKey: config.Name,
}

// Check labels in revision, should be empty.
if diff := cmp.Diff(expectedLabels, rev.Labels); diff != "" {
t.Errorf("Unexpected label in revision diff (-want +got): %v", diff)
}

}

func TestUpdateRouteDomainWhenRouteLabelChanges(t *testing.T) {
Expand Down

0 comments on commit c5af8a1

Please sign in to comment.